mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-22 23:59:26 +00:00
Compare commits
716 Commits
20220118.0
...
Add-Redire
Author | SHA1 | Date | |
---|---|---|---|
![]() |
191f81d9fe | ||
![]() |
2751f8f33b | ||
![]() |
57f2df3b3e | ||
![]() |
6822f0d067 | ||
![]() |
cfba957313 | ||
![]() |
3149ffbf19 | ||
![]() |
4cd8b76d7e | ||
![]() |
4b644d8bc5 | ||
![]() |
307cd5ad8c | ||
![]() |
ebc807a6a4 | ||
![]() |
66adecdfc9 | ||
![]() |
2cc6432a0f | ||
![]() |
a2c0c0474a | ||
![]() |
27884b9a54 | ||
![]() |
293df61872 | ||
![]() |
f82dada3e5 | ||
![]() |
e5824c4794 | ||
![]() |
186550229c | ||
![]() |
7877dd8e6b | ||
![]() |
b03abc249b | ||
![]() |
fda03918b9 | ||
![]() |
6747375a1b | ||
![]() |
53b6e31881 | ||
![]() |
fa004de2d1 | ||
![]() |
3605f7b70f | ||
![]() |
5348c54c91 | ||
![]() |
684e4421bc | ||
![]() |
28f5611df5 | ||
![]() |
8da73d49d7 | ||
![]() |
049ddd5f84 | ||
![]() |
8ae2d4e93a | ||
![]() |
824bb9ba35 | ||
![]() |
d550b1a18e | ||
![]() |
dea6c0e761 | ||
![]() |
9caee357c0 | ||
![]() |
35d892c418 | ||
![]() |
9572a2a46b | ||
![]() |
8996361b26 | ||
![]() |
02ee731602 | ||
![]() |
bb1e6bf35b | ||
![]() |
c1b65285c1 | ||
![]() |
8b8d6e5fa3 | ||
![]() |
c34fe184e8 | ||
![]() |
7363838f86 | ||
![]() |
3081425ccd | ||
![]() |
95d494a54c | ||
![]() |
145e5d7bc6 | ||
![]() |
876fd9e85a | ||
![]() |
e8c30cabca | ||
![]() |
490f84a7b1 | ||
![]() |
ca28178b86 | ||
![]() |
2fceb0aeee | ||
![]() |
86f39d1d43 | ||
![]() |
1faf60444d | ||
![]() |
e927091d21 | ||
![]() |
cff2f856b3 | ||
![]() |
a743e3bbba | ||
![]() |
f8a52d250e | ||
![]() |
b70a523bdf | ||
![]() |
8f2ed747e6 | ||
![]() |
5deccefb15 | ||
![]() |
3f04abfa9d | ||
![]() |
8e55c83996 | ||
![]() |
dee59486ba | ||
![]() |
77ef509aea | ||
![]() |
bfa7bccfa6 | ||
![]() |
a8c365edc8 | ||
![]() |
94953ddf6c | ||
![]() |
6b67546daf | ||
![]() |
3e188d1f87 | ||
![]() |
f69eb15a90 | ||
![]() |
dfe348187f | ||
![]() |
9706c56c5c | ||
![]() |
3677c5be2c | ||
![]() |
bd339fa963 | ||
![]() |
28f1b6bdf4 | ||
![]() |
c5aac3b81d | ||
![]() |
70836597e9 | ||
![]() |
958a1de2fd | ||
![]() |
36d30266e3 | ||
![]() |
558ab9761d | ||
![]() |
269ef370e4 | ||
![]() |
ba2958ecd2 | ||
![]() |
3b8b6eb315 | ||
![]() |
4f13db3178 | ||
![]() |
ee7aa54ab4 | ||
![]() |
c305dd4cd5 | ||
![]() |
6865791596 | ||
![]() |
2099259393 | ||
![]() |
27ca45dc70 | ||
![]() |
d290c11219 | ||
![]() |
cabe10ffdb | ||
![]() |
aa562c21a8 | ||
![]() |
22175a7271 | ||
![]() |
1e0647c0d1 | ||
![]() |
58d94da8b3 | ||
![]() |
d97763a3e8 | ||
![]() |
aa129aa123 | ||
![]() |
f648317206 | ||
![]() |
0685fdf7c6 | ||
![]() |
6fd4cda534 | ||
![]() |
511368da13 | ||
![]() |
76e1721c58 | ||
![]() |
bad5a389b5 | ||
![]() |
85d1f49763 | ||
![]() |
7723d47ac1 | ||
![]() |
30b130ca74 | ||
![]() |
a124ec0717 | ||
![]() |
323d98ecf7 | ||
![]() |
125a601ae3 | ||
![]() |
3c549c6b31 | ||
![]() |
9c1494c74d | ||
![]() |
e751abd775 | ||
![]() |
714f2447b7 | ||
![]() |
d900e40d04 | ||
![]() |
8b82383790 | ||
![]() |
5a2cc2646c | ||
![]() |
16a0902989 | ||
![]() |
8f67aa38af | ||
![]() |
34184cf2ab | ||
![]() |
611cd2818e | ||
![]() |
0a4e8fd5d0 | ||
![]() |
11f0361f48 | ||
![]() |
cfa048ea4e | ||
![]() |
bbca7b762b | ||
![]() |
1dba849567 | ||
![]() |
aff1ec10bf | ||
![]() |
351ec08a71 | ||
![]() |
a1a6a2cd30 | ||
![]() |
4e82c23b29 | ||
![]() |
59595aabde | ||
![]() |
358f91c2a9 | ||
![]() |
e0e01e68b4 | ||
![]() |
61dc4eaaea | ||
![]() |
65c4d02452 | ||
![]() |
f78ce2c844 | ||
![]() |
4d1ab83b30 | ||
![]() |
fb4b40b828 | ||
![]() |
db0c4ef941 | ||
![]() |
c5b60b826b | ||
![]() |
718f0330a7 | ||
![]() |
89e31486c5 | ||
![]() |
717eec1860 | ||
![]() |
b6e51352e3 | ||
![]() |
2ade728bc3 | ||
![]() |
62f227da83 | ||
![]() |
9557b604da | ||
![]() |
b45c355c9f | ||
![]() |
0b47d2c687 | ||
![]() |
8baa0b2a9b | ||
![]() |
c68a1d21ff | ||
![]() |
419d659311 | ||
![]() |
ba8b20d877 | ||
![]() |
8de542388f | ||
![]() |
e6c580aadc | ||
![]() |
11696c566a | ||
![]() |
edc15940a2 | ||
![]() |
bf35ee549d | ||
![]() |
4c3baa678c | ||
![]() |
0bb2767696 | ||
![]() |
80a4852325 | ||
![]() |
9c3e0fc997 | ||
![]() |
9e4bee123f | ||
![]() |
c2c09b1284 | ||
![]() |
bad776b979 | ||
![]() |
396791b805 | ||
![]() |
2b1457e1cd | ||
![]() |
b5861869e3 | ||
![]() |
9444228907 | ||
![]() |
86afd883a5 | ||
![]() |
062f21aa91 | ||
![]() |
ba235ac797 | ||
![]() |
505c22248b | ||
![]() |
624cb48f78 | ||
![]() |
7ab54ee5ce | ||
![]() |
f5af63a50e | ||
![]() |
ff80ab34ee | ||
![]() |
cfc1999a28 | ||
![]() |
7ca28469b7 | ||
![]() |
ac670614b4 | ||
![]() |
e263b57296 | ||
![]() |
c7050e4676 | ||
![]() |
00cbd1d9e6 | ||
![]() |
2a12172eeb | ||
![]() |
85d3011625 | ||
![]() |
ca22ec6340 | ||
![]() |
61f6e8855b | ||
![]() |
a44b8981e1 | ||
![]() |
b080bca9ce | ||
![]() |
d30e8ee9d8 | ||
![]() |
637e4203e5 | ||
![]() |
2648a53bbc | ||
![]() |
b3fa0cccb4 | ||
![]() |
dd963be723 | ||
![]() |
224df896a1 | ||
![]() |
a58b4fb262 | ||
![]() |
27ca61ec85 | ||
![]() |
859f49f3eb | ||
![]() |
40d878689f | ||
![]() |
420e8fe1ff | ||
![]() |
df96199433 | ||
![]() |
f493280f0a | ||
![]() |
cbd030a379 | ||
![]() |
95b80accc9 | ||
![]() |
c522670815 | ||
![]() |
7b6d3c0e36 | ||
![]() |
504b043159 | ||
![]() |
dffc66ccc3 | ||
![]() |
c7e9ee785d | ||
![]() |
079cc39a6e | ||
![]() |
d6a1d5af79 | ||
![]() |
c0dce08e19 | ||
![]() |
a7a347ed05 | ||
![]() |
2d9b50defc | ||
![]() |
840858b18c | ||
![]() |
afd2e71f6c | ||
![]() |
88af0aa788 | ||
![]() |
49124f6f09 | ||
![]() |
73f5580555 | ||
![]() |
bdde5268c6 | ||
![]() |
15e972c158 | ||
![]() |
0fc4c24f5a | ||
![]() |
9eba50df0c | ||
![]() |
0e0e07437f | ||
![]() |
6ac51ede52 | ||
![]() |
ccf1fb573a | ||
![]() |
fa537968c4 | ||
![]() |
6bf2111a3c | ||
![]() |
f5f8be8276 | ||
![]() |
ddf1cc0733 | ||
![]() |
9c1d1cb6f6 | ||
![]() |
470225abde | ||
![]() |
ee230b86c1 | ||
![]() |
f927fc64a9 | ||
![]() |
03677c33f7 | ||
![]() |
bc36a206da | ||
![]() |
af06ab1e2d | ||
![]() |
3e2135a485 | ||
![]() |
2e7f8fb46f | ||
![]() |
102568c4bd | ||
![]() |
4fcdae842e | ||
![]() |
ea19740f5a | ||
![]() |
3e0942b631 | ||
![]() |
0261cea796 | ||
![]() |
5247b2813f | ||
![]() |
8a5090684e | ||
![]() |
1784ba5e68 | ||
![]() |
4fbe9a7b10 | ||
![]() |
1ca9c7838a | ||
![]() |
4fc2c3ef05 | ||
![]() |
73ff8e28a8 | ||
![]() |
dde1c5e03c | ||
![]() |
01eed22592 | ||
![]() |
94ebb63589 | ||
![]() |
29119db5ce | ||
![]() |
9908162ac2 | ||
![]() |
1e929ae78a | ||
![]() |
ab5df0fe6e | ||
![]() |
d5010dda9e | ||
![]() |
4ac097f32b | ||
![]() |
5d3d15072f | ||
![]() |
5c53bc4225 | ||
![]() |
d5a307f8f4 | ||
![]() |
a27dd1e7f1 | ||
![]() |
c86ed1fb3e | ||
![]() |
7fa7a48072 | ||
![]() |
4e0fc8ee08 | ||
![]() |
5f6490e54e | ||
![]() |
db78b046a2 | ||
![]() |
c37fe1e7ff | ||
![]() |
f1ec479d41 | ||
![]() |
e01cb3ca82 | ||
![]() |
b8d3c68a7a | ||
![]() |
641003bb2a | ||
![]() |
3358fc2b18 | ||
![]() |
f9ccfa00a2 | ||
![]() |
070e11a2db | ||
![]() |
dcf50e055b | ||
![]() |
fc1c6cea24 | ||
![]() |
1fa04baa16 | ||
![]() |
c6a103bd30 | ||
![]() |
84ffa2369a | ||
![]() |
9d1618024e | ||
![]() |
307aa161a6 | ||
![]() |
affa6a92e7 | ||
![]() |
1e5ec241d5 | ||
![]() |
27a98a32fc | ||
![]() |
2c8ac58f97 | ||
![]() |
6b995969b1 | ||
![]() |
72c107484a | ||
![]() |
d1085b6657 | ||
![]() |
cc27ddb362 | ||
![]() |
c4dc6bfb0d | ||
![]() |
4fbcc30a37 | ||
![]() |
4916527e5f | ||
![]() |
fad8a27232 | ||
![]() |
a993d3a753 | ||
![]() |
5dfe17a43a | ||
![]() |
9b6c935ffb | ||
![]() |
f4e28da0a3 | ||
![]() |
294a69d7e4 | ||
![]() |
f89b8cffcf | ||
![]() |
99fd3a1b6f | ||
![]() |
246e426182 | ||
![]() |
9f1e9b43fe | ||
![]() |
8301ae262c | ||
![]() |
d968fe41ee | ||
![]() |
db830e9014 | ||
![]() |
fc6b594a27 | ||
![]() |
86dbf99ebe | ||
![]() |
68e7ce1883 | ||
![]() |
e9003ac35e | ||
![]() |
1dd5214b42 | ||
![]() |
96738350bb | ||
![]() |
5bdecf57cf | ||
![]() |
ec12282f8c | ||
![]() |
552dbca201 | ||
![]() |
0bbc0ebb3c | ||
![]() |
ac7acc5802 | ||
![]() |
64e1d160d1 | ||
![]() |
8e51878b6d | ||
![]() |
7c94ced303 | ||
![]() |
a040e1d5e0 | ||
![]() |
87c7407857 | ||
![]() |
d0d0c44ec7 | ||
![]() |
4cdff3faea | ||
![]() |
0dac10aa23 | ||
![]() |
4b8b14a69d | ||
![]() |
9d28df31bd | ||
![]() |
8258641443 | ||
![]() |
dfcb0f6ba0 | ||
![]() |
2e10eb04b6 | ||
![]() |
b4b52d3872 | ||
![]() |
3873203721 | ||
![]() |
ccb91e0b49 | ||
![]() |
bd20c15a55 | ||
![]() |
0936fd9ae4 | ||
![]() |
adefc7a4e2 | ||
![]() |
8f8017ecff | ||
![]() |
604b79696e | ||
![]() |
8c445f6409 | ||
![]() |
797c871137 | ||
![]() |
24829bd903 | ||
![]() |
add92a559d | ||
![]() |
7f086c0900 | ||
![]() |
17018c0f26 | ||
![]() |
cd6a478130 | ||
![]() |
4f6d7ca5c9 | ||
![]() |
c2994343b4 | ||
![]() |
e5f77c35d4 | ||
![]() |
a9e5a5dd44 | ||
![]() |
1159798b8d | ||
![]() |
437de42c55 | ||
![]() |
89e0bb3f16 | ||
![]() |
28c9631b6c | ||
![]() |
8882624618 | ||
![]() |
a769f84755 | ||
![]() |
7abf9c2473 | ||
![]() |
298296a81f | ||
![]() |
6907fa5c8e | ||
![]() |
546461b70f | ||
![]() |
4031009c26 | ||
![]() |
91e4557625 | ||
![]() |
f0c4b92dbb | ||
![]() |
ffac3d055e | ||
![]() |
04ae8c9d14 | ||
![]() |
0158610d42 | ||
![]() |
5ab6121581 | ||
![]() |
3d9c31aef9 | ||
![]() |
acfeea5c92 | ||
![]() |
75e8e17073 | ||
![]() |
976fd4b32d | ||
![]() |
49beafbe5f | ||
![]() |
151f8d5524 | ||
![]() |
48355aa98e | ||
![]() |
fc31929f41 | ||
![]() |
b7c149fcc1 | ||
![]() |
02d058561b | ||
![]() |
4e57fb1ec1 | ||
![]() |
30f79c5a46 | ||
![]() |
30f7252d84 | ||
![]() |
8af795a7ce | ||
![]() |
8576eeae41 | ||
![]() |
cd740ed135 | ||
![]() |
892f774792 | ||
![]() |
aa504fe1f8 | ||
![]() |
be491451d5 | ||
![]() |
bad184210d | ||
![]() |
a43b3b64b3 | ||
![]() |
aa831a9adf | ||
![]() |
43d4f55392 | ||
![]() |
130c66fb24 | ||
![]() |
684c232c8c | ||
![]() |
09f8f816d1 | ||
![]() |
1719d062b3 | ||
![]() |
87290c4330 | ||
![]() |
fec0dc0032 | ||
![]() |
70ca27c8c9 | ||
![]() |
9ae1f01ad6 | ||
![]() |
0113cc3cf6 | ||
![]() |
2a98ace0b3 | ||
![]() |
5f69a4c165 | ||
![]() |
8db22d4f88 | ||
![]() |
3204dbfc4d | ||
![]() |
430b47fc4a | ||
![]() |
5d8b3227f3 | ||
![]() |
b341ee9d38 | ||
![]() |
e6dbbc31a8 | ||
![]() |
0010bf5a8f | ||
![]() |
6e2e80a297 | ||
![]() |
aa9ff01030 | ||
![]() |
7f8ecf57d7 | ||
![]() |
6be6755f6f | ||
![]() |
64459a06c6 | ||
![]() |
df35496c6e | ||
![]() |
aa988c758d | ||
![]() |
1dd1095d19 | ||
![]() |
7e68393c84 | ||
![]() |
540c06c9f7 | ||
![]() |
f633cc2b0d | ||
![]() |
1baaf76471 | ||
![]() |
8263e299a8 | ||
![]() |
ebd6a26554 | ||
![]() |
5335772a7a | ||
![]() |
f5b5414461 | ||
![]() |
1e6f402d0f | ||
![]() |
ed9d886009 | ||
![]() |
940f5c0002 | ||
![]() |
15d1b8b2ac | ||
![]() |
73855e6f99 | ||
![]() |
d230541256 | ||
![]() |
b1f369a355 | ||
![]() |
e6d1e86c64 | ||
![]() |
eb1f94c370 | ||
![]() |
27750b8b5d | ||
![]() |
564a725284 | ||
![]() |
a5ee610af5 | ||
![]() |
eaf97ee7f5 | ||
![]() |
a14d75deec | ||
![]() |
72b5721c88 | ||
![]() |
94b4b818aa | ||
![]() |
98699b640a | ||
![]() |
decc0d3e0d | ||
![]() |
2281f5bafa | ||
![]() |
6cac7eeff0 | ||
![]() |
794bc161c8 | ||
![]() |
28cd9b6408 | ||
![]() |
9b4c6eea63 | ||
![]() |
afe044d152 | ||
![]() |
dc2038916b | ||
![]() |
cf8e2a6d02 | ||
![]() |
3269b2878b | ||
![]() |
29e1b7b452 | ||
![]() |
3d6d07e5bd | ||
![]() |
7bac41fe41 | ||
![]() |
6e4b027575 | ||
![]() |
728c391b5d | ||
![]() |
8999ca2ea0 | ||
![]() |
4fc0617289 | ||
![]() |
494cc3a569 | ||
![]() |
cc177ef911 | ||
![]() |
41ec65ef3d | ||
![]() |
79e1e195a0 | ||
![]() |
dfbf7fb436 | ||
![]() |
f37a5fa021 | ||
![]() |
5e2fcf928c | ||
![]() |
bc6ef7780c | ||
![]() |
b29563a254 | ||
![]() |
fe8a1152c4 | ||
![]() |
26689a0a85 | ||
![]() |
4f6a241817 | ||
![]() |
eae7e82127 | ||
![]() |
9500ac498c | ||
![]() |
5c5459bcaf | ||
![]() |
246724c59e | ||
![]() |
8f5c9295d3 | ||
![]() |
0abafff4c9 | ||
![]() |
f88ce269a7 | ||
![]() |
0dc56d7983 | ||
![]() |
cbd0ef6b65 | ||
![]() |
f923228078 | ||
![]() |
b55c7edd70 | ||
![]() |
bfb90632ac | ||
![]() |
3a664d45a9 | ||
![]() |
53607fe8c6 | ||
![]() |
9dec0f8ccd | ||
![]() |
89f4fe9d20 | ||
![]() |
f43655eea5 | ||
![]() |
6563984fdd | ||
![]() |
16d8eb0be3 | ||
![]() |
965fc9bc4e | ||
![]() |
56cb958a47 | ||
![]() |
f5feb1d8aa | ||
![]() |
e95065ed08 | ||
![]() |
68a411838d | ||
![]() |
ba63ab8b7a | ||
![]() |
26d4599ef4 | ||
![]() |
d049990f04 | ||
![]() |
9c8d683a19 | ||
![]() |
901677bbdf | ||
![]() |
8bb2374b1b | ||
![]() |
520896a3c2 | ||
![]() |
92db272759 | ||
![]() |
fc654d86c6 | ||
![]() |
523afe2f6f | ||
![]() |
460b9003fc | ||
![]() |
2ac0ad1d98 | ||
![]() |
a321432175 | ||
![]() |
63c9b3f830 | ||
![]() |
806b1296b0 | ||
![]() |
7f90ffa82f | ||
![]() |
db33c38e21 | ||
![]() |
a8c1fdd21e | ||
![]() |
d86a18b80b | ||
![]() |
bef6591548 | ||
![]() |
e1c07f109c | ||
![]() |
fb66d224ae | ||
![]() |
ee1fd3e865 | ||
![]() |
a9bfea233c | ||
![]() |
35cc291118 | ||
![]() |
35a41b3490 | ||
![]() |
db7cac5782 | ||
![]() |
099fa706a0 | ||
![]() |
f59cb661cd | ||
![]() |
ed84ce9692 | ||
![]() |
9912d427f2 | ||
![]() |
76f574f875 | ||
![]() |
ac90bb7088 | ||
![]() |
ce9f83e9a2 | ||
![]() |
fca7d2c5b0 | ||
![]() |
d7a5921e7b | ||
![]() |
cefa2ee183 | ||
![]() |
0eeed85193 | ||
![]() |
fd80408de2 | ||
![]() |
467a5169c0 | ||
![]() |
b0b3222b33 | ||
![]() |
b053881cef | ||
![]() |
92a9ed7080 | ||
![]() |
830b449006 | ||
![]() |
d38a8a317e | ||
![]() |
a0aed9112c | ||
![]() |
ce3b8544b9 | ||
![]() |
134ed7d303 | ||
![]() |
dc27871189 | ||
![]() |
9c9bfa2b77 | ||
![]() |
f02dd39619 | ||
![]() |
d37d99223d | ||
![]() |
4db943c5ff | ||
![]() |
ed001fb10b | ||
![]() |
5f43715dd8 | ||
![]() |
5435218187 | ||
![]() |
4ef5f3af89 | ||
![]() |
9eea17b793 | ||
![]() |
6a51e2aaad | ||
![]() |
2d33327d88 | ||
![]() |
e9ec2da917 | ||
![]() |
09d46dac61 | ||
![]() |
236fa14ec3 | ||
![]() |
2cb37820df | ||
![]() |
869fa91ae5 | ||
![]() |
22df03427f | ||
![]() |
e72a4e4a20 | ||
![]() |
ca8d31c6bb | ||
![]() |
354ea88984 | ||
![]() |
76af6e48cd | ||
![]() |
d05f807b9d | ||
![]() |
4092f7f75d | ||
![]() |
04668ad809 | ||
![]() |
9be5a15c77 | ||
![]() |
21d86f4797 | ||
![]() |
45e6ec1ee2 | ||
![]() |
9b97faa5e3 | ||
![]() |
8730c122fd | ||
![]() |
0046252e32 | ||
![]() |
f47440083e | ||
![]() |
bfaf44f9d1 | ||
![]() |
deba6a0db4 | ||
![]() |
51938fb51f | ||
![]() |
890ad9a1c8 | ||
![]() |
8466ef371a | ||
![]() |
4e55460799 | ||
![]() |
5fde6e659d | ||
![]() |
148bb99d89 | ||
![]() |
0540bae707 | ||
![]() |
0c6f647f53 | ||
![]() |
3aca67d511 | ||
![]() |
0e41a408e7 | ||
![]() |
19e1eaf2d7 | ||
![]() |
5e80a2b465 | ||
![]() |
866a57cde4 | ||
![]() |
c85236e251 | ||
![]() |
a88da0e39a | ||
![]() |
21a8fac477 | ||
![]() |
ca5ce04a38 | ||
![]() |
7c4b9a0410 | ||
![]() |
de6f06ea6d | ||
![]() |
bbc8e323e8 | ||
![]() |
89b6863ae3 | ||
![]() |
3f1850e9eb | ||
![]() |
54d6b5b6f3 | ||
![]() |
fb55ab197f | ||
![]() |
cc2db9a761 | ||
![]() |
58ba3e5c22 | ||
![]() |
182ffccd0c | ||
![]() |
ce99d14ee0 | ||
![]() |
8ce160b9ce | ||
![]() |
fe33714c8b | ||
![]() |
afbe85625c | ||
![]() |
cb47ee7721 | ||
![]() |
5caa256f1b | ||
![]() |
c66dfb84f9 | ||
![]() |
df1d703e4e | ||
![]() |
ce0ced0b6a | ||
![]() |
730e9b144d | ||
![]() |
69ff8dd0c4 | ||
![]() |
8d2c716fbe | ||
![]() |
389a100b46 | ||
![]() |
9fee7a2829 | ||
![]() |
a91897821a | ||
![]() |
815a2a07ff | ||
![]() |
b8d3eb76ac | ||
![]() |
ba75c2e7af | ||
![]() |
f04b844223 | ||
![]() |
242bad0a29 | ||
![]() |
8b20b2b63c | ||
![]() |
e0c8efc5e6 | ||
![]() |
f59c30ac04 | ||
![]() |
e4b9c08b45 | ||
![]() |
04e63eefe2 | ||
![]() |
a064ca0856 | ||
![]() |
6044ea92ad | ||
![]() |
17e8215420 | ||
![]() |
a4ae1bee79 | ||
![]() |
7d335d7d85 | ||
![]() |
7c194d8910 | ||
![]() |
a92100bb0a | ||
![]() |
303af611d1 | ||
![]() |
559b6e9d5b | ||
![]() |
75a95ff675 | ||
![]() |
3024ee43f9 | ||
![]() |
b34b92fa87 | ||
![]() |
1832ed0a48 | ||
![]() |
f398692e75 | ||
![]() |
68bee4dd58 | ||
![]() |
f1297e1f36 | ||
![]() |
953e3e060b | ||
![]() |
c37f660718 | ||
![]() |
02754369a6 | ||
![]() |
0df9e9932f | ||
![]() |
eddb392ad0 | ||
![]() |
e8ba349447 | ||
![]() |
5be22d46ab | ||
![]() |
ffaff30b46 | ||
![]() |
c4cad5bccd | ||
![]() |
e4085fe1f6 | ||
![]() |
8bfef92c86 | ||
![]() |
0c07178c0a | ||
![]() |
1010777139 | ||
![]() |
e57477c16a | ||
![]() |
30fa92c120 | ||
![]() |
b32438dc18 | ||
![]() |
614bd2f451 | ||
![]() |
6c12a5a4b1 | ||
![]() |
bbcec38450 | ||
![]() |
416e2e26c0 | ||
![]() |
1a7164b466 | ||
![]() |
3ddcd2d0f6 | ||
![]() |
648c02e622 | ||
![]() |
b0b953bfac | ||
![]() |
abeaa63005 | ||
![]() |
9cd23374f4 | ||
![]() |
72bd5f84d6 | ||
![]() |
22b4550fdf | ||
![]() |
87c22229e0 | ||
![]() |
971fd8dc60 | ||
![]() |
049c3caadd | ||
![]() |
fb2a24d11e | ||
![]() |
d4646bac01 | ||
![]() |
14e5b2a7a5 | ||
![]() |
734a733a4c | ||
![]() |
8f31c182f6 | ||
![]() |
e51a819bfd | ||
![]() |
05d7e85aa3 | ||
![]() |
cf527e4bc2 | ||
![]() |
197b581e8e | ||
![]() |
f75bf1f676 | ||
![]() |
28df79cfda | ||
![]() |
3bf19883a8 | ||
![]() |
303e065433 | ||
![]() |
7ad0b37a9e | ||
![]() |
930c7e4afa | ||
![]() |
81faae6f74 | ||
![]() |
f7fc83ac12 | ||
![]() |
21a099ee9f | ||
![]() |
7d1ce1b240 | ||
![]() |
d1f1309198 | ||
![]() |
68dd818f7a | ||
![]() |
50bea33a19 | ||
![]() |
069f08b55e | ||
![]() |
204ccf8b40 | ||
![]() |
0ab8f8fd7c | ||
![]() |
9b0b2c5b71 | ||
![]() |
0800c702fb | ||
![]() |
b7bd7c1065 | ||
![]() |
61bae5da64 | ||
![]() |
bdd13db8cf | ||
![]() |
cdc3d11181 | ||
![]() |
8f729e2a95 | ||
![]() |
bc9195f7d5 | ||
![]() |
7f1a321075 | ||
![]() |
72b9f8636d | ||
![]() |
c9cd316c0c | ||
![]() |
6cf3580fb4 | ||
![]() |
5d91aefb55 | ||
![]() |
e3c0530941 | ||
![]() |
2c9223ed80 |
@@ -16,6 +16,9 @@
|
||||
"runem.lit-plugin",
|
||||
"ms-python.vscode-pylance"
|
||||
],
|
||||
"containerEnv": {
|
||||
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
||||
},
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"files.eol": "\n",
|
||||
|
17
.github/workflows/release.yaml
vendored
17
.github/workflows/release.yaml
vendored
@@ -10,10 +10,18 @@ env:
|
||||
NODE_VERSION: 14
|
||||
NODE_OPTIONS: --max_old_space_size=6144
|
||||
|
||||
# Set default workflow permissions
|
||||
# All scopes not mentioned here are set to no access
|
||||
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
|
||||
permissions:
|
||||
actions: none
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v2
|
||||
@@ -41,12 +49,19 @@ jobs:
|
||||
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
||||
- name: Build and release package
|
||||
run: |
|
||||
python3 -m pip install twine
|
||||
python3 -m pip install twine build
|
||||
export TWINE_USERNAME="__token__"
|
||||
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
|
||||
|
||||
script/release
|
||||
|
||||
- name: Upload release assets
|
||||
uses: softprops/action-gh-release@v0.1.14
|
||||
with:
|
||||
files: |
|
||||
dist/*.whl
|
||||
dist/*.tar.gz
|
||||
|
||||
wheels-init:
|
||||
name: Init wheels build
|
||||
needs: release
|
||||
|
File diff suppressed because one or more lines are too long
@@ -1,11 +1,10 @@
|
||||
diff --git a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
|
||||
index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc32e26b8d 100644
|
||||
--- a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
|
||||
+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
|
||||
@@ -1,14 +1,15 @@
|
||||
-let _ET, ET;
|
||||
+let _ET;
|
||||
+let ET;
|
||||
diff --git a/polyfillLoaders/EventTarget.js b/polyfillLoaders/EventTarget.js
|
||||
index 4e18ade7ba485849f17f28c94c42f0e0e01ac387..8f34f4f646c7f7becc208fb5a546c96034fc74dc 100644
|
||||
--- a/polyfillLoaders/EventTarget.js
|
||||
+++ b/polyfillLoaders/EventTarget.js
|
||||
@@ -6,16 +6,15 @@
|
||||
let _ET;
|
||||
let ET;
|
||||
export default async function EventTarget() {
|
||||
- return ET || init();
|
||||
+ return ET || init();
|
||||
@@ -26,4 +25,5 @@ index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc
|
||||
+ _ET = (await import("event-target-shim")).default.EventTarget;
|
||||
+ }
|
||||
+ return (ET = _ET);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=EventTarget.js.map
|
631
.yarn/releases/yarn-3.0.2.cjs
vendored
631
.yarn/releases/yarn-3.0.2.cjs
vendored
File diff suppressed because one or more lines are too long
785
.yarn/releases/yarn-3.2.0.cjs
vendored
Executable file
785
.yarn/releases/yarn-3.2.0.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
@@ -6,4 +6,4 @@ plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||
spec: "@yarnpkg/plugin-interactive-tools"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.0.2.cjs
|
||||
yarnPath: .yarn/releases/yarn-3.2.0.cjs
|
||||
|
@@ -1,5 +1,4 @@
|
||||
include README.md
|
||||
include LICENSE.md
|
||||
graft hass_frontend
|
||||
graft hass_frontend_es5
|
||||
recursive-exclude * *.py[co]
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
|
||||
|
||||
[](https://demo.home-assistant.io/)
|
||||
[](https://demo.home-assistant.io/)
|
||||
|
||||
- [View demo of Home Assistant](https://demo.home-assistant.io/)
|
||||
- [More information about Home Assistant](https://home-assistant.io)
|
||||
|
@@ -10,7 +10,7 @@ module.exports.ignorePackages = ({ latestBuild }) => [
|
||||
];
|
||||
|
||||
// Files from NPM packages that we should replace with empty file
|
||||
module.exports.emptyPackages = ({ latestBuild }) =>
|
||||
module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
|
||||
[
|
||||
// Contains all color definitions for all material color sets.
|
||||
// We don't use it
|
||||
@@ -28,6 +28,15 @@ module.exports.emptyPackages = ({ latestBuild }) =>
|
||||
),
|
||||
// This polyfill is loaded in workers to support ES5, filter it out.
|
||||
latestBuild && require.resolve("proxy-polyfill/src/index.js"),
|
||||
// Icons in supervisor conflict with icons in HA so we don't load.
|
||||
isHassioBuild &&
|
||||
require.resolve(
|
||||
path.resolve(paths.polymer_dir, "src/components/ha-icon.ts")
|
||||
),
|
||||
isHassioBuild &&
|
||||
require.resolve(
|
||||
path.resolve(paths.polymer_dir, "src/components/ha-icon-picker.ts")
|
||||
),
|
||||
].filter(Boolean);
|
||||
|
||||
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
||||
@@ -196,6 +205,7 @@ module.exports.config = {
|
||||
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isHassioBuild: true,
|
||||
defineOverlay: {
|
||||
__SUPERVISOR__: true,
|
||||
},
|
||||
|
@@ -26,11 +26,11 @@ module.exports = {
|
||||
},
|
||||
version() {
|
||||
const version = fs
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "setup.py"), "utf8")
|
||||
.match(/\d{8}\.\d+/);
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "setup.cfg"), "utf8")
|
||||
.match(/version\W+=\W(\d{8}\.\d)/);
|
||||
if (!version) {
|
||||
throw Error("Version not found");
|
||||
}
|
||||
return version[0];
|
||||
return version[1];
|
||||
},
|
||||
};
|
||||
|
@@ -3,7 +3,7 @@
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const marked = require("marked");
|
||||
const { marked } = require("marked");
|
||||
const glob = require("glob");
|
||||
const yaml = require("js-yaml");
|
||||
|
||||
|
@@ -7,7 +7,7 @@ const source = require("vinyl-source-stream");
|
||||
const vinylBuffer = require("vinyl-buffer");
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const foreach = require("gulp-foreach");
|
||||
const flatmap = require("gulp-flatmap");
|
||||
const merge = require("gulp-merge-json");
|
||||
const rename = require("gulp-rename");
|
||||
const transform = require("gulp-json-transform");
|
||||
@@ -183,7 +183,7 @@ gulp.task("build-merged-translations", () =>
|
||||
})
|
||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
||||
.pipe(
|
||||
foreach((stream, file) => {
|
||||
flatmap((stream, file) => {
|
||||
// For each language generate a merged json file. It begins with the master
|
||||
// translation as a failsafe for untranslated strings, and merges all parent
|
||||
// tags into one file for each specific subtag
|
||||
|
@@ -3,10 +3,10 @@ const webpack = require("webpack");
|
||||
const path = require("path");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
||||
const paths = require("./paths.js");
|
||||
const bundle = require("./bundle.js");
|
||||
const log = require("fancy-log");
|
||||
const WebpackBar = require("webpackbar");
|
||||
const paths = require("./paths.js");
|
||||
const bundle = require("./bundle.js");
|
||||
|
||||
class LogStartCompilePlugin {
|
||||
ignoredFirst = false;
|
||||
@@ -30,6 +30,7 @@ const createWebpackConfig = ({
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
isHassioBuild,
|
||||
dontHash,
|
||||
}) => {
|
||||
if (!dontHash) {
|
||||
@@ -117,7 +118,9 @@ const createWebpackConfig = ({
|
||||
},
|
||||
}),
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
|
||||
new RegExp(
|
||||
bundle.emptyPackages({ latestBuild, isHassioBuild }).join("|")
|
||||
),
|
||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||
),
|
||||
!isProdBuild && new LogStartCompilePlugin(),
|
||||
@@ -135,6 +138,8 @@ const createWebpackConfig = ({
|
||||
"lit/directives/cache$": "lit/directives/cache.js",
|
||||
"lit/directives/repeat$": "lit/directives/repeat.js",
|
||||
"lit/polyfill-support$": "lit/polyfill-support.js",
|
||||
"@lit-labs/virtualizer/layouts/grid":
|
||||
"@lit-labs/virtualizer/layouts/grid.js",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { LovelaceConfig } from "../../../../src/data/lovelace";
|
||||
import { Lovelace } from "../../../../src/panels/lovelace/types";
|
||||
@@ -7,6 +7,9 @@ import "../../../../src/panels/lovelace/views/hui-view";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "./hc-launch-screen";
|
||||
|
||||
(window as any).loadCardHelpers = () =>
|
||||
import("../../../../src/panels/lovelace/custom-card-helpers");
|
||||
|
||||
@customElement("hc-lovelace")
|
||||
class HcLovelace extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -17,6 +20,8 @@ class HcLovelace extends LitElement {
|
||||
|
||||
@property() public urlPath: string | null = null;
|
||||
|
||||
@query("hui-view") private _huiView?: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const index = this._viewIndex;
|
||||
if (index === undefined) {
|
||||
@@ -75,12 +80,12 @@ class HcLovelace extends LitElement {
|
||||
this.lovelaceConfig.background;
|
||||
|
||||
if (configBackground) {
|
||||
(this.shadowRoot!.querySelector(
|
||||
"hui-view"
|
||||
) as HTMLElement)!.style.setProperty(
|
||||
this._huiView!.style.setProperty(
|
||||
"--lovelace-background",
|
||||
configBackground
|
||||
);
|
||||
} else {
|
||||
this._huiView!.style.removeProperty("--lovelace-background");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,6 +118,9 @@ class HcLovelace extends LitElement {
|
||||
:host > * {
|
||||
flex: 1;
|
||||
}
|
||||
hui-view {
|
||||
background: var(--lovelace-background, var(--primary-background-color));
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import "web-animations-js/web-animations-next-lite.min";
|
||||
import "../../../src/resources/ha-style";
|
||||
import "../../../src/resources/roboto";
|
||||
import "./layout/hc-lovelace";
|
||||
|
@@ -2,8 +2,3 @@ import "../../src/resources/ha-style";
|
||||
import "../../src/resources/roboto";
|
||||
import "../../src/resources/safari-14-attachshadow-patch";
|
||||
import "./ha-demo";
|
||||
|
||||
/* polyfill for paper-dropdown */
|
||||
setTimeout(() => {
|
||||
import("web-animations-js/web-animations-next-lite.min");
|
||||
}, 1000);
|
||||
|
@@ -31,7 +31,7 @@ export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
|
||||
version_latest: "3.6.2",
|
||||
update_available: false,
|
||||
repository: "a0d7b954",
|
||||
icon: true,
|
||||
icon: false,
|
||||
logo: true,
|
||||
},
|
||||
{
|
||||
|
BIN
gallery/public/api/hassio/addons/core_zwave_js/icon
Normal file
BIN
gallery/public/api/hassio/addons/core_zwave_js/icon
Normal file
Binary file not shown.
BIN
gallery/public/images/clearspace.png
Normal file
BIN
gallery/public/images/clearspace.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
BIN
gallery/public/images/logo-variants.png
Normal file
BIN
gallery/public/images/logo-variants.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
BIN
gallery/public/images/logo-with-text.png
Normal file
BIN
gallery/public/images/logo-with-text.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
BIN
gallery/public/images/logo.png
Normal file
BIN
gallery/public/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
gallery/public/images/using-our-logo.png
Normal file
BIN
gallery/public/images/using-our-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
TARGET_LABEL="Needs design preview"
|
||||
TARGET_LABEL="needs design preview"
|
||||
|
||||
if [[ "$NETLIFY" != "true" ]]; then
|
||||
echo "This script can only be run on Netlify"
|
||||
@@ -23,7 +23,7 @@ if [[ "${PULL_REQUEST}" == "true" ]]; then
|
||||
createStatus "pending" "Building design preview" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
|
||||
gulp build-gallery
|
||||
if [ $? -eq 0 ]; then
|
||||
createStatus "success" "Build complete" "$DEPLOY_URL"
|
||||
createStatus "success" "Build complete" "$DEPLOY_PRIME_URL"
|
||||
else
|
||||
createStatus "error" "Build failed" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
|
||||
fi
|
||||
|
@@ -36,8 +36,17 @@ module.exports = [
|
||||
category: "misc",
|
||||
header: "Miscelaneous",
|
||||
},
|
||||
{
|
||||
category: "brand",
|
||||
header: "Brand",
|
||||
},
|
||||
{
|
||||
category: "user-test",
|
||||
header: "Users",
|
||||
pages: ["user-types", "configuration-menu"],
|
||||
},
|
||||
{
|
||||
category: "design.home-assistant.io",
|
||||
header: "Design Documentation",
|
||||
header: "About",
|
||||
},
|
||||
];
|
||||
|
@@ -3,6 +3,7 @@ import { html, LitElement, css, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/ha-card";
|
||||
|
||||
@customElement("demo-black-white-row")
|
||||
class DemoBlackWhiteRow extends LitElement {
|
||||
@@ -52,13 +53,19 @@ class DemoBlackWhiteRow extends LitElement {
|
||||
|
||||
firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
applyThemesOnElement(this.shadowRoot!.querySelector(".dark"), {
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
});
|
||||
applyThemesOnElement(
|
||||
this.shadowRoot!.querySelector(".dark"),
|
||||
{
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
handleSubmit(ev) {
|
||||
|
@@ -78,6 +78,9 @@ class DemoCards extends LitElement {
|
||||
ha-formfield {
|
||||
margin-right: 16px;
|
||||
}
|
||||
#container {
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -12,7 +12,14 @@ class PageDescription extends HaMarkdown {
|
||||
if (!PAGES[this.page].description) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="heading">
|
||||
<div class="title">
|
||||
${PAGES[this.page].metadata.title || this.page.split("/")[1]}
|
||||
</div>
|
||||
<div class="subtitle">${PAGES[this.page].metadata.subtitle}</div>
|
||||
</div>
|
||||
${until(
|
||||
PAGES[this.page]
|
||||
.description()
|
||||
@@ -25,9 +32,22 @@ class PageDescription extends HaMarkdown {
|
||||
static styles = [
|
||||
HaMarkdown.styles,
|
||||
css`
|
||||
.heading {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--secondary-background-color);
|
||||
}
|
||||
.title {
|
||||
font-size: 42px;
|
||||
line-height: 56px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.root {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
margin: 16px auto;
|
||||
}
|
||||
.root > *:first-child {
|
||||
margin-top: 0;
|
||||
|
11
gallery/src/data/text.ts
Normal file
11
gallery/src/data/text.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const LONG_TEXT = `
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc laoreet velit ut elit volutpat, eget ultrices odio lacinia. In imperdiet malesuada est, nec sagittis metus ultricies quis. Sed nisl ex, convallis porttitor ante quis, hendrerit tristique justo. Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque sed consequat risus. Suspendisse facilisis ligula a odio consectetur condimentum. Curabitur vehicula elit nec augue mollis, et volutpat massa dictum.
|
||||
|
||||
Nam pellentesque auctor rutrum. Suspendisse elit est, sodales vel diam nec, porttitor faucibus massa. Ut pretium ac orci eu pharetra. Praesent in nibh at magna viverra rutrum eu vitae tortor. Etiam eget sem ex. Fusce tristique odio nec lacus mattis, vitae tempor nunc malesuada. Maecenas faucibus magna vel libero maximus egestas. Vestibulum luctus semper velit, in lobortis risus tempus non. Curabitur bibendum ornare commodo. Quisque commodo neque sit amet tincidunt lacinia. Proin elementum ante velit, eu congue nulla semper quis. Pellentesque consequat vel nunc at scelerisque. Mauris sit amet venenatis diam, blandit viverra leo. Integer commodo laoreet orci.
|
||||
|
||||
Curabitur ipsum tortor, sodales ut augue sed, commodo porttitor libero. Pellentesque molestie vitae mi consectetur tempor. In sed lectus consequat, lobortis neque non, semper ipsum. Etiam eget ex et nibh sagittis pulvinar lacinia ac mauris. Aenean ligula eros, viverra ac nibh at, venenatis semper quam. Sed interdum ligula sit amet massa tincidunt tincidunt. Suspendisse potenti. Aliquam egestas facilisis est, sed faucibus erat scelerisque id. Duis dolor quam, viverra vitae orci euismod, laoreet pellentesque justo. Nunc malesuada non erat at ullamcorper. Mauris eget posuere odio. Vestibulum turpis nunc, pharetra eget ante in, feugiat mollis justo. Proin porttitor, diam nec vulputate pretium, tellus arcu rhoncus turpis, a blandit nisi nulla quis arcu. Nunc ac ullamcorper ligula, nec facilisis leo.
|
||||
|
||||
In vitae eros sollicitudin, iaculis ex eget, egestas orci. Etiam sed pretium lorem. Nam nisi enim, consectetur sit amet semper ac, semper pharetra diam. In pulvinar neque sapien, ac ullamcorper est lacinia a. Etiam tincidunt velit sed diam malesuada, eu ornare ex consectetur. Phasellus in imperdiet tellus. Sed bibendum, dui sit amet fringilla aliquet, enim odio sollicitudin lorem, vel semper turpis mauris vel mauris. Aenean congue magna ac massa cursus, in dictum orci commodo. Pellentesque mollis velit in sollicitudin tincidunt. Vestibulum et efficitur nulla.
|
||||
|
||||
Quisque posuere, velit sed porttitor dapibus, neque augue fringilla felis, eu luctus nisi nisl nec ipsum. Curabitur pellentesque ac lectus eget ultricies. Vestibulum est dolor, lacinia pharetra vulputate a, facilisis a magna. Nam vitae arcu nibh. Praesent finibus blandit ante, ac gravida ex mollis eget. Donec quam est, pulvinar vitae neque ut, bibendum aliquam erat. Nullam mollis arcu at sem tincidunt, in tristique lectus facilisis. Aenean ut lacus vel nisl finibus iaculis non a turpis. Integer eget ipsum ante. Donec nunc neque, vestibulum ac magna ac, posuere scelerisque dui. Pellentesque massa nibh, rhoncus id dolor quis, placerat posuere turpis. Donec aliquet augue nisi, eu finibus dui auctor et. Vestibulum eu varius lorem. Quisque lectus ante, malesuada pretium risus eget, interdum mattis enim.
|
||||
`;
|
@@ -5,6 +5,7 @@ import { html, css, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../src/components/ha-icon-button";
|
||||
import "../../src/managers/notification-manager";
|
||||
import "../../src/components/ha-expansion-panel";
|
||||
import { haStyle } from "../../src/resources/styles";
|
||||
import { PAGES, SIDEBAR } from "../build/import-pages";
|
||||
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
|
||||
@@ -44,6 +45,10 @@ class HaGallery extends LitElement {
|
||||
for (const page of group.pages!) {
|
||||
const key = `${group.category}/${page}`;
|
||||
const active = this._page === key;
|
||||
if (!(key in PAGES)) {
|
||||
console.error("Undefined page referenced in sidebar.js:", key);
|
||||
continue;
|
||||
}
|
||||
const title = PAGES[key].metadata.title || page;
|
||||
links.push(html`
|
||||
<a ?active=${active} href=${`#${group.category}/${page}`}>${title}</a>
|
||||
@@ -53,10 +58,9 @@ class HaGallery extends LitElement {
|
||||
sidebar.push(
|
||||
group.header
|
||||
? html`
|
||||
<details>
|
||||
<summary class="section">${group.header}</summary>
|
||||
<ha-expansion-panel .header=${group.header}>
|
||||
${links}
|
||||
</details>
|
||||
</ha-expansion-panel>
|
||||
`
|
||||
: links
|
||||
);
|
||||
@@ -92,27 +96,34 @@ class HaGallery extends LitElement {
|
||||
${dynamicElement(`demo-${this._page.replace("/", "-")}`)}
|
||||
</div>
|
||||
<div class="page-footer">
|
||||
${PAGES[this._page].description ||
|
||||
Object.keys(PAGES[this._page].metadata).length > 0
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.markdown`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit text
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
${PAGES[this._page].demo
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.ts`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit demo
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
<div class="header">Help us to improve our documentation</div>
|
||||
<div class="secondary">
|
||||
Suggest an edit to this page, or provide/view feedback for this
|
||||
page.
|
||||
</div>
|
||||
<div>
|
||||
${PAGES[this._page].description ||
|
||||
Object.keys(PAGES[this._page].metadata).length > 0
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.markdown`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit text
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
${PAGES[this._page].demo
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.ts`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit demo
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mwc-drawer>
|
||||
@@ -186,26 +197,16 @@ class HaGallery extends LitElement {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.sidebar details {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.sidebar summary {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.sidebar a {
|
||||
color: var(--primary-text-color);
|
||||
display: block;
|
||||
padding: 4px 12px;
|
||||
padding: 12px;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sidebar a[active]::before {
|
||||
border-radius: 4px;
|
||||
border-radius: 12px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 2px;
|
||||
@@ -236,14 +237,32 @@ class HaGallery extends LitElement {
|
||||
|
||||
.page-footer {
|
||||
text-align: center;
|
||||
margin: 16px 0;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.12);
|
||||
margin: 16px;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
|
||||
.page-footer div {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.page-footer .header {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-footer .secondary {
|
||||
line-height: 23px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-footer a {
|
||||
display: inline-block;
|
||||
margin: 0 8px;
|
||||
text-decoration: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -3,10 +3,20 @@ import { html, css, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import { describeAction } from "../../../../src/data/script_i18n";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
const actions = [
|
||||
const ENTITIES = [
|
||||
getEntity("scene", "kitchen_morning", "scening", {
|
||||
friendly_name: "Kitchen Morning",
|
||||
}),
|
||||
getEntity("media_player", "kitchen", "playing", {
|
||||
friendly_name: "Sonos Kitchen",
|
||||
}),
|
||||
];
|
||||
|
||||
const ACTIONS = [
|
||||
{ wait_template: "{{ true }}", alias: "Something with an alias" },
|
||||
{ delay: "0:05" },
|
||||
{ wait_template: "{{ true }}" },
|
||||
@@ -19,8 +29,20 @@ const actions = [
|
||||
device_id: "abcdefgh",
|
||||
domain: "plex",
|
||||
entity_id: "media_player.kitchen",
|
||||
type: "turn_on",
|
||||
},
|
||||
{ scene: "scene.kitchen_morning" },
|
||||
{
|
||||
service: "scene.turn_on",
|
||||
target: { entity_id: "scene.kitchen_morning" },
|
||||
metadata: {},
|
||||
},
|
||||
{
|
||||
service: "media_player.play_media",
|
||||
target: { entity_id: "media_player.kitchen" },
|
||||
data: { media_content_id: "", media_content_type: "" },
|
||||
metadata: { title: "Happy Song" },
|
||||
},
|
||||
{
|
||||
wait_for_trigger: [
|
||||
{
|
||||
@@ -52,7 +74,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
||||
}
|
||||
return html`
|
||||
<ha-card header="Actions">
|
||||
${actions.map(
|
||||
${ACTIONS.map(
|
||||
(conf) => html`
|
||||
<div class="action">
|
||||
<span>${describeAction(this.hass, conf as any)}</span>
|
||||
@@ -68,6 +90,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
||||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
|
@@ -14,7 +14,7 @@ import { HaDelayAction } from "../../../../src/panels/config/automation/action/t
|
||||
import { HaDeviceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-device_id";
|
||||
import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event";
|
||||
import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
|
||||
import { HaSceneAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-scene";
|
||||
import { HaSceneAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-activate_scene";
|
||||
import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service";
|
||||
import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger";
|
||||
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
|
||||
|
@@ -8,7 +8,7 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import type { Condition } from "../../../../src/data/automation";
|
||||
import type { ConditionWithShorthand } from "../../../../src/data/automation";
|
||||
import "../../../../src/panels/config/automation/condition/ha-automation-condition";
|
||||
import { HaDeviceCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-device";
|
||||
import { HaLogicalCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-logical";
|
||||
@@ -20,7 +20,7 @@ import { HaTimeCondition } from "../../../../src/panels/config/automation/condit
|
||||
import { HaTriggerCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-trigger";
|
||||
import { HaZoneCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-zone";
|
||||
|
||||
const SCHEMAS: { name: string; conditions: Condition[] }[] = [
|
||||
const SCHEMAS: { name: string; conditions: ConditionWithShorthand[] }[] = [
|
||||
{
|
||||
name: "State",
|
||||
conditions: [{ condition: "state", ...HaStateCondition.defaultConfig }],
|
||||
@@ -69,6 +69,14 @@ const SCHEMAS: { name: string; conditions: Condition[] }[] = [
|
||||
name: "Trigger",
|
||||
conditions: [{ condition: "trigger", ...HaTriggerCondition.defaultConfig }],
|
||||
},
|
||||
{
|
||||
name: "Shorthand",
|
||||
conditions: [
|
||||
{ and: HaLogicalCondition.defaultConfig.conditions },
|
||||
{ or: HaLogicalCondition.defaultConfig.conditions },
|
||||
{ not: HaLogicalCondition.defaultConfig.conditions },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-automation-editor-condition")
|
||||
|
34
gallery/src/pages/brand/logo.markdown
Normal file
34
gallery/src/pages/brand/logo.markdown
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: "Logo"
|
||||
---
|
||||
|
||||

|
||||
|
||||
# Using our logo
|
||||
|
||||
As a community, we are proud of our logo. Follow these guidelines to ensure it always looks its best. Our logo follows Google's material design spec and uses the blue interface color.
|
||||
|
||||
[Download Logo](https://github.com/home-assistant/assets/tree/master/logo)
|
||||
|
||||

|
||||
|
||||
|
||||
## Using the icon
|
||||
|
||||
Our icon is a shorter and most used version of our logo. The icon can exist without the wordmark, the wordmark should never exist without the icon.
|
||||
|
||||

|
||||
|
||||
## Using the right variant
|
||||
|
||||
The pretty blue logo with a background shadow, pictured top left, is our primary logo. It should only be used with black, white, and non-duotone photography.
|
||||
|
||||
When needed you can use our logo without a shadow, as seen as the second variant.
|
||||
|
||||
The outlined logo should only be used on packaging.
|
||||
|
||||
## Exclusion zone
|
||||
|
||||
The logo needs some personal space. It's exclusion zone is equal to a quarter the height of the icon.
|
||||
|
||||

|
41
gallery/src/pages/brand/our-story.markdown
Normal file
41
gallery/src/pages/brand/our-story.markdown
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: "Our story"
|
||||
---
|
||||
|
||||
## Open source home automation that puts local control and privacy first
|
||||
|
||||
Home Assistant is a free and open-source software for home automation that is designed to be the central control system for smart home devices with a focus on local control and privacy. It can be accessed via a web-based user interface, via apps for Android and iOS, or using voice commands via a supported virtual assistant like Google Assistant and Amazon Alexa.
|
||||
|
||||
IoT devices and services are supported by modular support for controlling proprietary ecosystems if they provide public access via an Open API for third-party integrations and protocols like Bluetooth, MQTT, Zigbee, and Z-Wave, After the Home Assistant software application is installed as a computer appliance it will act as a central control system for home automation. Information from all entities it sees can be used and controlled from within scripts trigger automations using scheduling and "blueprint" subroutines, e.g. for controlling lighting, climate, entertainment systems, and appliances.
|
||||
|
||||
# Open Home
|
||||
|
||||
The Open Home is our vision for the smart home. It defines the values that we put at the heart of every decision we make at Home Assistant. It’s woven into our architecture, licensing, community, and everything else.
|
||||
|
||||
The Open Home is about privacy, choice, and durability.
|
||||
|
||||
## Privacy
|
||||
|
||||
Your home should be your safe space. A place where you can be your true self without having to bother about what the world thinks of you. A place where you don’t need to act differently to avoid an algorithm categorizing your behavior. Privacy for the Open Home means that devices need to work locally. No one else needs to know if you turn on a light bulb or change the thermostat.
|
||||
|
||||
It is okay for a product to offer a cloud connection, but it should be extra and opt-in.
|
||||
|
||||
## Choice
|
||||
|
||||
Devices in your home gather data about themselves and their surroundings. Your data. Vendors shouldn’t be able to limit your access to your data or limit the interoperability of your devices with the rest of your smart home.
|
||||
|
||||
Choice for the Open Home means that devices need to make the gathered data available through local APIs. This avoids vendor lock-in and allows users to create their own smart home with devices from different manufacturers.
|
||||
|
||||
## Durability
|
||||
|
||||
If there is one thing that technology firms are very good at, it is launching new products. However, maintaining the products and making sure they keep working is an afterthought for most. The result is that vendors can decide to no longer support your device, crippling its features or even preventing it from working at all. As we install more and more devices in our home, durability is becoming more and more important. We shouldn’t have to buy everything new every couple of years because the manufacturer decided to move on.
|
||||
|
||||
Durability for the Open Home means that devices are designed and built to keep working. Not just this year, but for the next decade.
|
||||
|
||||
# Our history
|
||||
|
||||
The project was started as a Python application by Paulus Schoutsen in September 2013 and first published publicly on GitHub in November 2013. In July 2017, a managed operating system called Hass.io was initially introduced to make it easier use to use Home Assistant on single-board computers like the Raspberry Pi series. Its bundled "supervisor" management system allowed users to manage, backup, and update the local installation and introduced the option to extend the functionality of the software with add-ons.
|
||||
|
||||
An optional subscription service was introduced in December 2017 for $5/month to solve the complexities associated with secured remote access, as well as linking to Amazon Alexa and Google Assistant. Nabu Casa, Inc. was formed in September 2018 to take over the subscription service. The company's funding is based solely on revenue from the subscription service. It is used to finance the project's infrastructure and to pay for full-time employees contributing to the project.
|
||||
|
||||
In January 2020, branding was adjusted to make it easier to refer to different parts of the project. The main piece of software was renamed to Home Assistant Core, while the full suite of software with the embedded operating system and bundled "supervisor" management system was renamed to Home Assistant.
|
@@ -1,19 +1,28 @@
|
||||
---
|
||||
title: Alerts
|
||||
subtitle: An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task.
|
||||
---
|
||||
|
||||
# Alert `<ha-alert>`
|
||||
The alert offers four severity levels that set a distinctive icon and color.
|
||||
|
||||
<ha-alert alert-type="error">This is an error alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="error">
|
||||
This is an error alert — check it out!
|
||||
</ha-alert>
|
||||
|
||||
<ha-alert alert-type="warning">This is an warning alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="warning">
|
||||
This is an warning alert — check it out!
|
||||
</ha-alert>
|
||||
|
||||
<ha-alert alert-type="info">This is an info alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="info">
|
||||
This is an info alert — check it out!
|
||||
</ha-alert>
|
||||
|
||||
<ha-alert alert-type="success">This is an success alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="success">
|
||||
This is an success alert — check it out!
|
||||
</ha-alert>
|
||||
|
||||
**Note:** This component is by [MUI](https://mui.com/components/alert/) and is not documented in the [Material Design guidelines](https://material.io).
|
||||
**Note:** This component is by <a href="https://mui.com/components/alert/" rel="noopener noreferrer" target="_blank">MUI</a> and is not documented in the <a href="https://material.io" rel="noopener noreferrer" target="_blank">Material Design guidelines</a>.
|
||||
|
||||
1. [Guidelines](#guidelines)
|
||||
2. [Implementation](#implementation)
|
||||
@@ -21,8 +30,8 @@ The alert offers four severity levels that set a distinctive icon and color.
|
||||
### Resources
|
||||
| Type | Link | Status |
|
||||
|----------------|----------------------------------|-----------|
|
||||
| Design | [Home Assistant DesignKit](https://www.figma.com/community/file/967153512097289521/Home-Assistant-DesignKit) (Figma) | Available |
|
||||
| Implementation | [Web Component](https://github.com/home-assistant/frontend/blob/dev/src/components/ha-alert.ts) (GitHub) | Available |
|
||||
| Design | <a href="https://www.figma.com/community/file/967153512097289521/Home-Assistant-DesignKit" rel="noopener noreferrer" target="_blank">Home Assistant DesignKit</a> (Figma) | Available |
|
||||
| Implementation | <a href="https://github.com/home-assistant/frontend/blob/dev/src/components/ha-alert.ts" rel="noopener noreferrer" target="_blank">Web Component</a> (GitHub) | Available |
|
||||
|
||||
## Guidelines
|
||||
### Usage
|
||||
@@ -64,78 +73,93 @@ Actions must have a tab index of 0 so that they can be reached by keyboard-only
|
||||
### Example Usage
|
||||
**Alert type**
|
||||
|
||||
<ha-alert alert-type="error">This is an error alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="error">
|
||||
This is an error alert — check it out!
|
||||
</ha-alert>
|
||||
|
||||
<ha-alert alert-type="warning">This is an warning alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="warning">
|
||||
This is an warning alert — check it out!
|
||||
</ha-alert>
|
||||
|
||||
<ha-alert alert-type="info">This is an info alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="info">
|
||||
This is an info alert — check it out!
|
||||
</ha-alert>
|
||||
|
||||
<ha-alert alert-type="success">This is an success alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="success">
|
||||
This is an success alert — check it out!
|
||||
</ha-alert>
|
||||
|
||||
|
||||
```html
|
||||
<ha-alert alert-type="error">This is an error alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="warning">This is a warning alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="info">This is an info alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="success">This is a success alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="error">
|
||||
This is an error alert — check it out!
|
||||
</ha-alert>
|
||||
<ha-alert alert-type="warning">
|
||||
This is a warning alert — check it out!
|
||||
</ha-alert>
|
||||
<ha-alert alert-type="info">
|
||||
This is an info alert — check it out!
|
||||
</ha-alert>
|
||||
<ha-alert alert-type="success">
|
||||
This is a success alert — check it out!
|
||||
</ha-alert>
|
||||
```
|
||||
|
||||
**Title**
|
||||
|
||||
The `title ` option should not be used without a description.
|
||||
|
||||
<ha-alert alert-type="error" title="Error">
|
||||
This is an error alert — <strong>check it out!</strong>
|
||||
</ha-alert>
|
||||
|
||||
<ha-alert alert-type="warning">
|
||||
<title>Warning</title>
|
||||
This is an warning alert — <strong>check it out!</strong>
|
||||
</ha-alert>
|
||||
|
||||
<ha-alert alert-type="info">
|
||||
<title>Info</title>
|
||||
This is an info alert — <strong>check it out!</strong>
|
||||
</ha-alert>
|
||||
|
||||
<ha-alert alert-type="success">
|
||||
<title>Success </title>
|
||||
This is an success alert — <strong>check it out!</strong>
|
||||
<ha-alert alert-type="success" title="Success">
|
||||
This is an success alert — check it out!
|
||||
</ha-alert>
|
||||
|
||||
```html
|
||||
<ha-alert alert-type="error" title="Error">
|
||||
This is an error alert — <strong>check it out!</strong>
|
||||
</ha-alert>
|
||||
<ha-alert alert-type="warning">
|
||||
<title>Warning</title>
|
||||
This is an warning alert — <strong>check it out!</strong>
|
||||
</ha-alert>
|
||||
<ha-alert alert-type="info">
|
||||
<title>Info</title>
|
||||
This is an info alert — <strong>check it out!</strong>
|
||||
</ha-alert>
|
||||
<ha-alert alert-type="success">
|
||||
<title>Success </title>
|
||||
This is an success alert — <strong>check it out!</strong>
|
||||
<ha-alert alert-type="success" title="Success">
|
||||
This is an success alert — check it out!
|
||||
</ha-alert>
|
||||
```
|
||||
|
||||
**Dismissable**
|
||||
|
||||
*Documentation coming soon*
|
||||
<ha-alert alert-type="success" dismissable>
|
||||
This is an success alert — check it out!
|
||||
</ha-alert>
|
||||
|
||||
```html
|
||||
<ha-alert alert-type="success" dismissable>
|
||||
This is an success alert — check it out!
|
||||
</ha-alert>
|
||||
```
|
||||
|
||||
**Slotted action**
|
||||
|
||||
<ha-alert alert-type="success">
|
||||
This is an success alert — check it out!
|
||||
<mwc-button slot="action" label="Undo"></mwc-button>
|
||||
</ha-alert>
|
||||
|
||||
```html
|
||||
<ha-alert alert-type="success">
|
||||
This is an success alert — check it out!
|
||||
<mwc-button slot="action" label="Undo"></mwc-button>
|
||||
</ha-alert>
|
||||
```
|
||||
|
||||
**Slotted icon**
|
||||
|
||||
*Documentation coming soon*
|
||||
|
||||
**Slotted action**
|
||||
|
||||
*Documentation coming soon*
|
||||
|
||||
**Right to left**
|
||||
|
||||
*Documentation coming soon*
|
||||
<ha-alert alert-type="success" rtl>
|
||||
This is an info alert — check it out!
|
||||
</ha-alert>
|
||||
|
||||
```html
|
||||
<ha-alert alert-type="success" rtl>
|
||||
This is an info alert — check it out!
|
||||
</ha-alert>
|
||||
```
|
||||
|
||||
### API
|
||||
**Properties/Attributes**
|
||||
|
@@ -159,13 +159,19 @@ export class DemoHaAlert extends LitElement {
|
||||
|
||||
firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
applyThemesOnElement(this.shadowRoot!.querySelector(".dark"), {
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
});
|
||||
applyThemesOnElement(
|
||||
this.shadowRoot!.querySelector(".dark"),
|
||||
{
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
|
@@ -3,18 +3,7 @@ import { customElement } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-faded";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
|
||||
const LONG_TEXT = `
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc laoreet velit ut elit volutpat, eget ultrices odio lacinia. In imperdiet malesuada est, nec sagittis metus ultricies quis. Sed nisl ex, convallis porttitor ante quis, hendrerit tristique justo. Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque sed consequat risus. Suspendisse facilisis ligula a odio consectetur condimentum. Curabitur vehicula elit nec augue mollis, et volutpat massa dictum.
|
||||
|
||||
Nam pellentesque auctor rutrum. Suspendisse elit est, sodales vel diam nec, porttitor faucibus massa. Ut pretium ac orci eu pharetra. Praesent in nibh at magna viverra rutrum eu vitae tortor. Etiam eget sem ex. Fusce tristique odio nec lacus mattis, vitae tempor nunc malesuada. Maecenas faucibus magna vel libero maximus egestas. Vestibulum luctus semper velit, in lobortis risus tempus non. Curabitur bibendum ornare commodo. Quisque commodo neque sit amet tincidunt lacinia. Proin elementum ante velit, eu congue nulla semper quis. Pellentesque consequat vel nunc at scelerisque. Mauris sit amet venenatis diam, blandit viverra leo. Integer commodo laoreet orci.
|
||||
|
||||
Curabitur ipsum tortor, sodales ut augue sed, commodo porttitor libero. Pellentesque molestie vitae mi consectetur tempor. In sed lectus consequat, lobortis neque non, semper ipsum. Etiam eget ex et nibh sagittis pulvinar lacinia ac mauris. Aenean ligula eros, viverra ac nibh at, venenatis semper quam. Sed interdum ligula sit amet massa tincidunt tincidunt. Suspendisse potenti. Aliquam egestas facilisis est, sed faucibus erat scelerisque id. Duis dolor quam, viverra vitae orci euismod, laoreet pellentesque justo. Nunc malesuada non erat at ullamcorper. Mauris eget posuere odio. Vestibulum turpis nunc, pharetra eget ante in, feugiat mollis justo. Proin porttitor, diam nec vulputate pretium, tellus arcu rhoncus turpis, a blandit nisi nulla quis arcu. Nunc ac ullamcorper ligula, nec facilisis leo.
|
||||
|
||||
In vitae eros sollicitudin, iaculis ex eget, egestas orci. Etiam sed pretium lorem. Nam nisi enim, consectetur sit amet semper ac, semper pharetra diam. In pulvinar neque sapien, ac ullamcorper est lacinia a. Etiam tincidunt velit sed diam malesuada, eu ornare ex consectetur. Phasellus in imperdiet tellus. Sed bibendum, dui sit amet fringilla aliquet, enim odio sollicitudin lorem, vel semper turpis mauris vel mauris. Aenean congue magna ac massa cursus, in dictum orci commodo. Pellentesque mollis velit in sollicitudin tincidunt. Vestibulum et efficitur nulla.
|
||||
|
||||
Quisque posuere, velit sed porttitor dapibus, neque augue fringilla felis, eu luctus nisi nisl nec ipsum. Curabitur pellentesque ac lectus eget ultricies. Vestibulum est dolor, lacinia pharetra vulputate a, facilisis a magna. Nam vitae arcu nibh. Praesent finibus blandit ante, ac gravida ex mollis eget. Donec quam est, pulvinar vitae neque ut, bibendum aliquam erat. Nullam mollis arcu at sem tincidunt, in tristique lectus facilisis. Aenean ut lacus vel nisl finibus iaculis non a turpis. Integer eget ipsum ante. Donec nunc neque, vestibulum ac magna ac, posuere scelerisque dui. Pellentesque massa nibh, rhoncus id dolor quis, placerat posuere turpis. Donec aliquet augue nisi, eu finibus dui auctor et. Vestibulum eu varius lorem. Quisque lectus ante, malesuada pretium risus eget, interdum mattis enim.
|
||||
`;
|
||||
import { LONG_TEXT } from "../../data/text";
|
||||
|
||||
const SMALL_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
|
||||
|
||||
|
@@ -1,12 +1,110 @@
|
||||
/* eslint-disable lit/no-template-arrow */
|
||||
import "@material/mwc-button";
|
||||
import { LitElement, TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/demo-black-white-row";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||
friendly_name: "Alarm",
|
||||
}),
|
||||
getEntity("media_player", "livingroom", "playing", {
|
||||
friendly_name: "Livingroom",
|
||||
}),
|
||||
getEntity("media_player", "lounge", "idle", {
|
||||
friendly_name: "Lounge",
|
||||
supported_features: 444983,
|
||||
}),
|
||||
getEntity("light", "bedroom", "on", {
|
||||
friendly_name: "Bedroom",
|
||||
}),
|
||||
getEntity("switch", "coffee", "off", {
|
||||
friendly_name: "Coffee",
|
||||
}),
|
||||
];
|
||||
|
||||
const DEVICES = [
|
||||
{
|
||||
area_id: "bedroom",
|
||||
configuration_url: null,
|
||||
config_entries: ["config_entry_1"],
|
||||
connections: [],
|
||||
disabled_by: null,
|
||||
entry_type: null,
|
||||
id: "device_1",
|
||||
identifiers: [["demo", "volume1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
name_by_user: null,
|
||||
name: "Dishwasher",
|
||||
sw_version: null,
|
||||
hw_version: null,
|
||||
via_device_id: null,
|
||||
},
|
||||
{
|
||||
area_id: "backyard",
|
||||
configuration_url: null,
|
||||
config_entries: ["config_entry_2"],
|
||||
connections: [],
|
||||
disabled_by: null,
|
||||
entry_type: null,
|
||||
id: "device_2",
|
||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
name_by_user: null,
|
||||
name: "Lamp",
|
||||
sw_version: null,
|
||||
hw_version: null,
|
||||
via_device_id: null,
|
||||
},
|
||||
{
|
||||
area_id: null,
|
||||
configuration_url: null,
|
||||
config_entries: ["config_entry_3"],
|
||||
connections: [],
|
||||
disabled_by: null,
|
||||
entry_type: null,
|
||||
id: "device_3",
|
||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
name_by_user: "User name",
|
||||
name: "Technical name",
|
||||
sw_version: null,
|
||||
hw_version: null,
|
||||
via_device_id: null,
|
||||
},
|
||||
];
|
||||
|
||||
const AREAS = [
|
||||
{
|
||||
area_id: "backyard",
|
||||
name: "Backyard",
|
||||
picture: null,
|
||||
},
|
||||
{
|
||||
area_id: "bedroom",
|
||||
name: "Bedroom",
|
||||
picture: null,
|
||||
},
|
||||
{
|
||||
area_id: "livingroom",
|
||||
name: "Livingroom",
|
||||
picture: null,
|
||||
},
|
||||
];
|
||||
|
||||
const SCHEMAS: {
|
||||
title: string;
|
||||
translations?: Record<string, string>;
|
||||
@@ -14,6 +112,76 @@ const SCHEMAS: {
|
||||
schema: HaFormSchema[];
|
||||
data?: Record<string, any>;
|
||||
}[] = [
|
||||
{
|
||||
title: "Selectors",
|
||||
translations: {
|
||||
addon: "Addon",
|
||||
entity: "Entity",
|
||||
device: "Device",
|
||||
area: "Area",
|
||||
target: "Target",
|
||||
number: "Number",
|
||||
boolean: "Boolean",
|
||||
time: "Time",
|
||||
action: "Action",
|
||||
text: "Text",
|
||||
text_multiline: "Text Multiline",
|
||||
object: "Object",
|
||||
select: "Select",
|
||||
icon: "Icon",
|
||||
media: "Media",
|
||||
location: "Location",
|
||||
entities: "Entities",
|
||||
},
|
||||
schema: [
|
||||
{ name: "addon", selector: { addon: {} } },
|
||||
{ name: "entity", selector: { entity: {} } },
|
||||
{
|
||||
name: "Attribute",
|
||||
selector: { attribute: { entity_id: "" } },
|
||||
context: { filter_entity: "entity" },
|
||||
},
|
||||
{ name: "Device", selector: { device: {} } },
|
||||
{ name: "Duration", selector: { duration: {} } },
|
||||
{ name: "area", selector: { area: {} } },
|
||||
{ name: "target", selector: { target: {} } },
|
||||
{ name: "number", selector: { number: { min: 0, max: 10 } } },
|
||||
{ name: "boolean", selector: { boolean: {} } },
|
||||
{ name: "time", required: true, selector: { time: {} } },
|
||||
{ name: "datetime", required: true, selector: { datetime: {} } },
|
||||
{ name: "date", required: true, selector: { date: {} } },
|
||||
{ name: "action", selector: { action: {} } },
|
||||
{ name: "text", selector: { text: { multiline: false } } },
|
||||
{ name: "text_multiline", selector: { text: { multiline: true } } },
|
||||
{ name: "object", selector: { object: {} } },
|
||||
{
|
||||
name: "select",
|
||||
selector: {
|
||||
select: { options: ["Everyone Home", "Some Home", "All gone"] },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
selector: {
|
||||
icon: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "media",
|
||||
selector: {
|
||||
media: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "location",
|
||||
selector: { location: { radius: true, icon: "mdi:home" } },
|
||||
},
|
||||
{
|
||||
name: "entities",
|
||||
selector: { entity: { multiple: true } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Authentication",
|
||||
translations: {
|
||||
@@ -50,13 +218,11 @@ const SCHEMAS: {
|
||||
{
|
||||
type: "boolean",
|
||||
name: "bool",
|
||||
optional: true,
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
type: "integer",
|
||||
name: "int",
|
||||
optional: true,
|
||||
default: 10,
|
||||
},
|
||||
{
|
||||
@@ -67,7 +233,6 @@ const SCHEMAS: {
|
||||
{
|
||||
type: "string",
|
||||
name: "string",
|
||||
optional: true,
|
||||
default: "Default",
|
||||
},
|
||||
{
|
||||
@@ -77,7 +242,6 @@ const SCHEMAS: {
|
||||
["other", "other"],
|
||||
],
|
||||
name: "select",
|
||||
optional: true,
|
||||
default: "default",
|
||||
},
|
||||
{
|
||||
@@ -87,7 +251,6 @@ const SCHEMAS: {
|
||||
other: "Other",
|
||||
},
|
||||
name: "multi",
|
||||
optional: true,
|
||||
default: ["default"],
|
||||
},
|
||||
{
|
||||
@@ -108,7 +271,6 @@ const SCHEMAS: {
|
||||
{
|
||||
type: "integer",
|
||||
name: "int with default",
|
||||
optional: true,
|
||||
default: 10,
|
||||
},
|
||||
{
|
||||
@@ -122,7 +284,6 @@ const SCHEMAS: {
|
||||
{
|
||||
type: "integer",
|
||||
name: "int range optional",
|
||||
optional: true,
|
||||
valueMin: 0,
|
||||
valueMax: 10,
|
||||
},
|
||||
@@ -148,7 +309,6 @@ const SCHEMAS: {
|
||||
["other", "Other"],
|
||||
],
|
||||
name: "select optional",
|
||||
optional: true,
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
@@ -161,7 +321,6 @@ const SCHEMAS: {
|
||||
["option", "1000"],
|
||||
],
|
||||
name: "select many otions",
|
||||
optional: true,
|
||||
default: "default",
|
||||
},
|
||||
],
|
||||
@@ -190,7 +349,6 @@ const SCHEMAS: {
|
||||
option: "1000",
|
||||
},
|
||||
name: "multi many otions",
|
||||
optional: true,
|
||||
default: ["default"],
|
||||
},
|
||||
],
|
||||
@@ -239,23 +397,36 @@ const SCHEMAS: {
|
||||
valueMin: 1,
|
||||
valueMax: 65535,
|
||||
name: "port",
|
||||
optional: true,
|
||||
default: 80,
|
||||
},
|
||||
{ type: "string", name: "path", optional: true, default: "/" },
|
||||
{ type: "boolean", name: "ssl", optional: true, default: false },
|
||||
{ type: "string", name: "path", default: "/" },
|
||||
{ type: "boolean", name: "ssl", default: false },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-components-ha-form")
|
||||
class DemoHaForm extends LitElement {
|
||||
@state() private hass!: HomeAssistant;
|
||||
|
||||
private data = SCHEMAS.map(
|
||||
({ schema, data }) => data || computeInitialHaFormData(schema)
|
||||
);
|
||||
|
||||
private disabled = SCHEMAS.map(() => false);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockEntityRegistry(hass);
|
||||
mockDeviceRegistry(hass, DEVICES);
|
||||
mockAreaRegistry(hass, AREAS);
|
||||
mockHassioSupervisor(hass);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${SCHEMAS.map((info, idx) => {
|
||||
@@ -278,6 +449,7 @@ class DemoHaForm extends LitElement {
|
||||
(slot) => html`
|
||||
<ha-form
|
||||
slot=${slot}
|
||||
.hass=${this.hass}
|
||||
.data=${this.data[idx]}
|
||||
.schema=${info.schema}
|
||||
.error=${info.error}
|
||||
|
@@ -1,3 +1,5 @@
|
||||
---
|
||||
title: Target Selectors
|
||||
title: Selectors
|
||||
---
|
||||
|
||||
See the website for [list of available selectors](https://www.home-assistant.io/docs/blueprint/selectors/).
|
||||
|
@@ -1,27 +1,126 @@
|
||||
/* eslint-disable lit/no-template-arrow */
|
||||
import "@material/mwc-button";
|
||||
import { LitElement, TemplateResult, css, html } from "lit";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
import "../../../../src/components/ha-selector/ha-selector";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import { BlueprintInput } from "../../../../src/data/blueprint";
|
||||
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/demo-black-white-row";
|
||||
import { BlueprintInput } from "../../../../src/data/blueprint";
|
||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||
friendly_name: "Alarm",
|
||||
}),
|
||||
getEntity("media_player", "livingroom", "playing", {
|
||||
friendly_name: "Livingroom",
|
||||
}),
|
||||
getEntity("media_player", "lounge", "idle", {
|
||||
friendly_name: "Lounge",
|
||||
supported_features: 444983,
|
||||
}),
|
||||
getEntity("light", "bedroom", "on", {
|
||||
friendly_name: "Bedroom",
|
||||
}),
|
||||
getEntity("switch", "coffee", "off", {
|
||||
friendly_name: "Coffee",
|
||||
}),
|
||||
];
|
||||
|
||||
const DEVICES = [
|
||||
{
|
||||
area_id: "bedroom",
|
||||
configuration_url: null,
|
||||
config_entries: ["config_entry_1"],
|
||||
connections: [],
|
||||
disabled_by: null,
|
||||
entry_type: null,
|
||||
id: "device_1",
|
||||
identifiers: [["demo", "volume1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
name_by_user: null,
|
||||
name: "Dishwasher",
|
||||
sw_version: null,
|
||||
hw_version: null,
|
||||
via_device_id: null,
|
||||
},
|
||||
{
|
||||
area_id: "backyard",
|
||||
configuration_url: null,
|
||||
config_entries: ["config_entry_2"],
|
||||
connections: [],
|
||||
disabled_by: null,
|
||||
entry_type: null,
|
||||
id: "device_2",
|
||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
name_by_user: null,
|
||||
name: "Lamp",
|
||||
sw_version: null,
|
||||
hw_version: null,
|
||||
via_device_id: null,
|
||||
},
|
||||
{
|
||||
area_id: null,
|
||||
configuration_url: null,
|
||||
config_entries: ["config_entry_3"],
|
||||
connections: [],
|
||||
disabled_by: null,
|
||||
entry_type: null,
|
||||
id: "device_3",
|
||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
name_by_user: "User name",
|
||||
name: "Technical name",
|
||||
sw_version: null,
|
||||
hw_version: null,
|
||||
via_device_id: null,
|
||||
},
|
||||
];
|
||||
|
||||
const AREAS = [
|
||||
{
|
||||
area_id: "backyard",
|
||||
name: "Backyard",
|
||||
picture: null,
|
||||
},
|
||||
{
|
||||
area_id: "bedroom",
|
||||
name: "Bedroom",
|
||||
picture: null,
|
||||
},
|
||||
{
|
||||
area_id: "livingroom",
|
||||
name: "Livingroom",
|
||||
picture: null,
|
||||
},
|
||||
];
|
||||
|
||||
const SCHEMAS: {
|
||||
name: string;
|
||||
input: Record<string, BlueprintInput | null>;
|
||||
input: Record<string, (BlueprintInput & { required?: boolean }) | null>;
|
||||
}[] = [
|
||||
{
|
||||
name: "One of each",
|
||||
input: {
|
||||
entity: { name: "Entity", selector: { entity: {} } },
|
||||
attribute: {
|
||||
name: "Attribute",
|
||||
selector: { attribute: { entity_id: "" } },
|
||||
},
|
||||
device: { name: "Device", selector: { device: {} } },
|
||||
duration: { name: "Duration", selector: { duration: {} } },
|
||||
addon: { name: "Addon", selector: { addon: {} } },
|
||||
area: { name: "Area", selector: { area: {} } },
|
||||
target: { name: "Target", selector: { target: {} } },
|
||||
@@ -47,24 +146,125 @@ const SCHEMAS: {
|
||||
},
|
||||
boolean: { name: "Boolean", selector: { boolean: {} } },
|
||||
time: { name: "Time", selector: { time: {} } },
|
||||
date: { name: "Date", selector: { date: {} } },
|
||||
datetime: { name: "Date Time", selector: { datetime: {} } },
|
||||
action: { name: "Action", selector: { action: {} } },
|
||||
text: { name: "Text", selector: { text: { multiline: false } } },
|
||||
text: {
|
||||
name: "Text",
|
||||
selector: { text: {} },
|
||||
},
|
||||
password: {
|
||||
name: "Password",
|
||||
selector: { text: { type: "password" } },
|
||||
},
|
||||
text_multiline: {
|
||||
name: "Text multiline",
|
||||
selector: { text: { multiline: true } },
|
||||
selector: {
|
||||
text: { multiline: true },
|
||||
},
|
||||
},
|
||||
object: { name: "Object", selector: { object: {} } },
|
||||
select_radio: {
|
||||
name: "Select (Radio)",
|
||||
selector: {
|
||||
select: { options: ["Option 1", "Option 2"], mode: "list" },
|
||||
},
|
||||
},
|
||||
template: { name: "Template", selector: { template: {} } },
|
||||
select: {
|
||||
name: "Select",
|
||||
selector: { select: { options: ["Option 1", "Option 2"] } },
|
||||
selector: {
|
||||
select: {
|
||||
options: [
|
||||
"Option 1",
|
||||
"Option 2",
|
||||
"Option 3",
|
||||
"Option 4",
|
||||
"Option 5",
|
||||
"Option 6",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
select_custom: {
|
||||
name: "Select (Custom)",
|
||||
selector: {
|
||||
select: {
|
||||
custom_value: true,
|
||||
options: [
|
||||
"Option 1",
|
||||
"Option 2",
|
||||
"Option 3",
|
||||
"Option 4",
|
||||
"Option 5",
|
||||
"Option 6",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
icon: { name: "Icon", selector: { icon: {} } },
|
||||
media: { name: "Media", selector: { media: {} } },
|
||||
location: { name: "Location", selector: { location: {} } },
|
||||
location_radius: {
|
||||
name: "Location with radius",
|
||||
selector: { location: { radius: true, icon: "mdi:home" } },
|
||||
},
|
||||
color_temp: {
|
||||
name: "Color Temperature",
|
||||
selector: { color_temp: {} },
|
||||
},
|
||||
color_rgb: { name: "Color", selector: { color_rgb: {} } },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Multiples",
|
||||
input: {
|
||||
entity: { name: "Entity", selector: { entity: { multiple: true } } },
|
||||
device: { name: "Device", selector: { device: { multiple: true } } },
|
||||
area: { name: "Area", selector: { area: { multiple: true } } },
|
||||
select: {
|
||||
name: "Select Multiple",
|
||||
selector: {
|
||||
select: {
|
||||
multiple: true,
|
||||
custom_value: true,
|
||||
options: [
|
||||
"Option 1",
|
||||
"Option 2",
|
||||
"Option 3",
|
||||
"Option 4",
|
||||
"Option 5",
|
||||
"Option 6",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
select_checkbox: {
|
||||
name: "Select Multiple (Checkbox)",
|
||||
required: false,
|
||||
selector: {
|
||||
select: {
|
||||
mode: "list",
|
||||
multiple: true,
|
||||
options: ["Option 1", "Option 2", "Option 3", "Option 4"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-components-ha-selector")
|
||||
class DemoHaSelector extends LitElement {
|
||||
@state() private hass!: HomeAssistant;
|
||||
class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
@state() public hass!: HomeAssistant;
|
||||
|
||||
@state() private _disabled = false;
|
||||
|
||||
@state() private _required = false;
|
||||
|
||||
@state() private _helper = false;
|
||||
|
||||
@state() private _label = true;
|
||||
|
||||
private data = SCHEMAS.map(() => ({}));
|
||||
|
||||
@@ -73,14 +273,162 @@ class DemoHaSelector extends LitElement {
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockEntityRegistry(hass);
|
||||
mockDeviceRegistry(hass);
|
||||
mockAreaRegistry(hass);
|
||||
mockDeviceRegistry(hass, DEVICES);
|
||||
mockAreaRegistry(hass, AREAS);
|
||||
mockHassioSupervisor(hass);
|
||||
hass.mockWS("auth/sign_path", (params) => params);
|
||||
hass.mockWS("media_player/browse_media", this._browseMedia);
|
||||
}
|
||||
|
||||
public provideHass(el) {
|
||||
el.hass = this.hass;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.addEventListener("show-dialog", this._dialogManager);
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener("show-dialog", this._dialogManager);
|
||||
}
|
||||
|
||||
private _browseMedia = ({ media_content_id }) => {
|
||||
if (media_content_id === undefined) {
|
||||
return {
|
||||
title: "Media",
|
||||
media_class: "directory",
|
||||
media_content_type: "",
|
||||
media_content_id: "media-source://media_source/local/.",
|
||||
can_play: false,
|
||||
can_expand: true,
|
||||
children_media_class: "directory",
|
||||
thumbnail: null,
|
||||
children: [
|
||||
{
|
||||
title: "Misc",
|
||||
media_class: "directory",
|
||||
media_content_type: "",
|
||||
media_content_id: "media-source://media_source/local/misc",
|
||||
can_play: false,
|
||||
can_expand: true,
|
||||
children_media_class: null,
|
||||
thumbnail: null,
|
||||
},
|
||||
{
|
||||
title: "Movies",
|
||||
media_class: "directory",
|
||||
media_content_type: "",
|
||||
media_content_id: "media-source://media_source/local/movies",
|
||||
can_play: true,
|
||||
can_expand: true,
|
||||
children_media_class: "movie",
|
||||
thumbnail: null,
|
||||
},
|
||||
{
|
||||
title: "Music",
|
||||
media_class: "album",
|
||||
media_content_type: "",
|
||||
media_content_id: "media-source://media_source/local/music",
|
||||
can_play: false,
|
||||
can_expand: true,
|
||||
children_media_class: "music",
|
||||
thumbnail: "/images/album_cover_2.jpg",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: "Subfolder",
|
||||
media_class: "directory",
|
||||
media_content_type: "",
|
||||
media_content_id: "media-source://media_source/local/sub",
|
||||
can_play: false,
|
||||
can_expand: true,
|
||||
children_media_class: "directory",
|
||||
thumbnail: null,
|
||||
children: [
|
||||
{
|
||||
title: "audio.mp3",
|
||||
media_class: "music",
|
||||
media_content_type: "audio/mpeg",
|
||||
media_content_id: "media-source://media_source/local/audio.mp3",
|
||||
can_play: true,
|
||||
can_expand: false,
|
||||
children_media_class: null,
|
||||
thumbnail: "/images/album_cover.jpg",
|
||||
},
|
||||
{
|
||||
title: "image.jpg",
|
||||
media_class: "image",
|
||||
media_content_type: "image/jpeg",
|
||||
media_content_id: "media-source://media_source/local/image.jpg",
|
||||
can_play: true,
|
||||
can_expand: false,
|
||||
children_media_class: null,
|
||||
thumbnail: "https://brands.home-assistant.io/_/image/logo.png",
|
||||
},
|
||||
{
|
||||
title: "movie.mp4",
|
||||
media_class: "movie",
|
||||
media_content_type: "image/jpeg",
|
||||
media_content_id: "media-source://media_source/local/movie.mp4",
|
||||
can_play: true,
|
||||
can_expand: false,
|
||||
children_media_class: null,
|
||||
thumbnail: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
private _dialogManager = (e) => {
|
||||
const { dialogTag, dialogImport, dialogParams, addHistory } = e.detail;
|
||||
showDialog(
|
||||
this,
|
||||
this.shadowRoot!,
|
||||
dialogTag,
|
||||
dialogParams,
|
||||
dialogImport,
|
||||
addHistory
|
||||
);
|
||||
};
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="options">
|
||||
<ha-formfield label="Labels">
|
||||
<ha-switch
|
||||
.name=${"label"}
|
||||
.checked=${this._label}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Required">
|
||||
<ha-switch
|
||||
.name=${"required"}
|
||||
.checked=${this._required}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Disabled">
|
||||
<ha-switch
|
||||
.name=${"disabled"}
|
||||
.checked=${this._disabled}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Helper text">
|
||||
<ha-switch
|
||||
.name=${"helper"}
|
||||
.checked=${this._helper}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
${SCHEMAS.map((info, idx) => {
|
||||
const data = this.data[idx];
|
||||
const valueChanged = (ev) => {
|
||||
@@ -103,8 +451,12 @@ class DemoHaSelector extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.selector=${value!.selector}
|
||||
.key=${key}
|
||||
.label=${this._label ? value!.name : undefined}
|
||||
.value=${data[key] ?? value!.default}
|
||||
.disabled=${this._disabled}
|
||||
.required=${this._required}
|
||||
@value-changed=${valueChanged}
|
||||
.helper=${this._helper ? "Helper text" : undefined}
|
||||
></ha-selector>
|
||||
</ha-settings-row>
|
||||
`
|
||||
@@ -116,11 +468,21 @@ class DemoHaSelector extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleOptionChange(ev) {
|
||||
this[`_${ev.target.name}`] = ev.target.checked;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
paper-input,
|
||||
ha-selector {
|
||||
width: 60;
|
||||
}
|
||||
.options {
|
||||
max-width: 800px;
|
||||
margin: 16px auto;
|
||||
}
|
||||
.options ha-formfield {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
3
gallery/src/pages/components/ha-tip.markdown
Normal file
3
gallery/src/pages/components/ha-tip.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Tips
|
||||
---
|
73
gallery/src/pages/components/ha-tip.ts
Normal file
73
gallery/src/pages/components/ha-tip.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { html, css, LitElement, TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../../../src/components/ha-tip";
|
||||
import "../../../../src/components/ha-card";
|
||||
import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element";
|
||||
|
||||
const tips: (string | TemplateResult)[] = [
|
||||
"Test tip",
|
||||
"Bigger test tip, with some random text just to fill up as much space as possible without it looking like I'm really trying to to that",
|
||||
html`<i>Tip</i> <b>with</b> <sub>HTML</sub>`,
|
||||
];
|
||||
|
||||
@customElement("demo-components-ha-tip")
|
||||
export class DemoHaTip extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html` ${["light", "dark"].map(
|
||||
(mode) => html`
|
||||
<div class=${mode}>
|
||||
<ha-card header="ha-tip ${mode} demo">
|
||||
<div class="card-content">
|
||||
${tips.map((tip) => html`<ha-tip>${tip}</ha-tip>`)}
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
`
|
||||
)}`;
|
||||
}
|
||||
|
||||
firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
applyThemesOnElement(
|
||||
this.shadowRoot!.querySelector(".dark"),
|
||||
{
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.dark,
|
||||
.light {
|
||||
display: block;
|
||||
background-color: var(--primary-background-color);
|
||||
padding: 0 50px;
|
||||
}
|
||||
ha-tip {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
ha-card {
|
||||
margin: 24px auto;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-tip": DemoHaTip;
|
||||
}
|
||||
}
|
@@ -17,7 +17,7 @@ We want to make it as easy for designers to contribute as it is for developers.
|
||||
|
||||
- Meet us at <a href="https://discord.gg/BPBc8rZ9" rel="noopener noreferrer" target="_blank">devs_ux Discord</a>. Feel free to share your designs, user test or strategic ideas.
|
||||
- Start designing with our <a href="https://www.figma.com/community/file/967153512097289521/Home-Assistant-DesignKit" rel="noopener noreferrer" target="_blank">Figma DesignKit</a>.
|
||||
- Find the lates UX <a href="https://github.com/home-assistant/frontend/labels/ux" rel="noopener noreferrer" target="_blank">discussions</a> and <a href="https://github.com/home-assistant/frontend/discussions?discussions_q=label%3Aux" rel="noopener noreferrer" target="_blank">issues</a> on GitHub. Everyone can start a new issue or discussion!
|
||||
- Find the lates UX <a href="https://github.com/home-assistant/frontend/discussions?discussions_q=label%3Aux" rel="noopener noreferrer" target="_blank">discussions</a> and <a href="https://github.com/home-assistant/frontend/labels/ux" rel="noopener noreferrer" target="_blank">issues</a> on GitHub. Everyone can start a new issue or discussion!
|
||||
|
||||
|
||||
## Developers
|
||||
|
@@ -2,6 +2,8 @@
|
||||
title: Editing design.home-assistant.io
|
||||
---
|
||||
|
||||

|
||||
|
||||
# How to edit design.home-assistant.io
|
||||
|
||||
All pages are stored in [the pages folder][pages-folder] on GitHub. Pages are grouped in a folder per sidebar section. Each page can contain a `<page name>.markdown` description file, a `<page name>.ts` demo file or both. If both are defined the description is rendered first. The description can contain metadata to specify the title of the page.
|
||||
@@ -41,15 +43,12 @@ import { html, css, LitElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
|
||||
|
||||
@customElement("demo-user-experience-usability")
|
||||
export class DemoUserExperienceUsability extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
Hello world!
|
||||
</div>
|
||||
<div class="card-content">Hello world!</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ const CONFIGS = [
|
||||
heading: "markdown-it demo",
|
||||
config: `
|
||||
- type: markdown
|
||||
content: >
|
||||
content: >-
|
||||
# h1 Heading 8-)
|
||||
|
||||
## h2 Heading
|
||||
@@ -249,6 +249,17 @@ const CONFIGS = [
|
||||
::: warning
|
||||
*here be dragons*
|
||||
:::
|
||||
|
||||
### ha-alert
|
||||
|
||||
You can use our [\`ha-alert\`](https://design.home-assistant.io/#components/ha-alert) component in markdown content rendered in the Home Assistant Frontend.
|
||||
|
||||
<ha-alert alert-type="error">This is an error alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="warning">This is a warning alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="info">This is an info alert — check it out!</ha-alert>
|
||||
<ha-alert alert-type="success">This is a success alert — check it out!</ha-alert>
|
||||
<ha-alert title="Test alert">This is an alert with a title</ha-alert>
|
||||
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
@@ -29,6 +29,7 @@ const createConfigEntry = (
|
||||
source: "zeroconf",
|
||||
state: "loaded",
|
||||
supports_options: false,
|
||||
supports_remove_device: false,
|
||||
supports_unload: true,
|
||||
disabled_by: null,
|
||||
pref_disable_new_entities: false,
|
||||
@@ -187,6 +188,7 @@ const createEntityRegistryEntries = (
|
||||
device_id: "mock-device-id",
|
||||
area_id: null,
|
||||
disabled_by: null,
|
||||
hidden_by: null,
|
||||
entity_category: null,
|
||||
entity_id: "binary_sensor.updater",
|
||||
name: null,
|
||||
|
3
gallery/src/pages/more-info/update.markdown
Normal file
3
gallery/src/pages/more-info/update.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Update
|
||||
---
|
189
gallery/src/pages/more-info/update.ts
Normal file
189
gallery/src/pages/more-info/update.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
UPDATE_SUPPORT_BACKUP,
|
||||
UPDATE_SUPPORT_PROGRESS,
|
||||
UPDATE_SUPPORT_INSTALL,
|
||||
UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
} from "../../../../src/data/update";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import {
|
||||
MockHomeAssistant,
|
||||
provideHass,
|
||||
} from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-more-infos";
|
||||
import { LONG_TEXT } from "../../data/text";
|
||||
|
||||
const base_attributes = {
|
||||
title: "Awesome",
|
||||
installed_version: "1.2.2",
|
||||
latest_version: "1.2.3",
|
||||
release_url: "https://home-assistant.io",
|
||||
supported_features: UPDATE_SUPPORT_INSTALL,
|
||||
skipped_version: null,
|
||||
in_progress: false,
|
||||
release_summary:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In nec metus aliquet, porta mi ut, ultrices odio. Etiam egestas orci tellus, non semper metus blandit tincidunt. Praesent elementum turpis vel tempor pharetra. Sed quis cursus diam. Proin sem justo.",
|
||||
};
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("update", "update1", "on", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update",
|
||||
}),
|
||||
getEntity("update", "update2", "on", {
|
||||
...base_attributes,
|
||||
title: null,
|
||||
friendly_name: "Update without title",
|
||||
}),
|
||||
getEntity("update", "update3", "on", {
|
||||
...base_attributes,
|
||||
release_url: null,
|
||||
friendly_name: "Update without release_url",
|
||||
}),
|
||||
getEntity("update", "update4", "on", {
|
||||
...base_attributes,
|
||||
release_summary: null,
|
||||
friendly_name: "Update without release_summary",
|
||||
}),
|
||||
getEntity("update", "update5", "off", {
|
||||
...base_attributes,
|
||||
installed_version: "1.2.3",
|
||||
friendly_name: "No update",
|
||||
}),
|
||||
getEntity("update", "update6", "off", {
|
||||
...base_attributes,
|
||||
skipped_version: "1.2.3",
|
||||
friendly_name: "Skipped version",
|
||||
}),
|
||||
getEntity("update", "update7", "on", {
|
||||
...base_attributes,
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_BACKUP,
|
||||
friendly_name: "With backup support",
|
||||
}),
|
||||
getEntity("update", "update8", "on", {
|
||||
...base_attributes,
|
||||
in_progress: true,
|
||||
friendly_name: "With true in_progress",
|
||||
}),
|
||||
getEntity("update", "update9", "on", {
|
||||
...base_attributes,
|
||||
in_progress: 25,
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
||||
friendly_name: "With 25 in_progress",
|
||||
}),
|
||||
getEntity("update", "update10", "on", {
|
||||
...base_attributes,
|
||||
in_progress: 50,
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
||||
friendly_name: "With 50 in_progress",
|
||||
}),
|
||||
getEntity("update", "update11", "on", {
|
||||
...base_attributes,
|
||||
in_progress: 75,
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
||||
friendly_name: "With 75 in_progress",
|
||||
}),
|
||||
getEntity("update", "update12", "unavailable", {
|
||||
...base_attributes,
|
||||
in_progress: 50,
|
||||
friendly_name: "Unavailable",
|
||||
}),
|
||||
getEntity("update", "update13", "on", {
|
||||
...base_attributes,
|
||||
supported_features: 0,
|
||||
friendly_name: "No install support",
|
||||
}),
|
||||
getEntity("update", "update14", "off", {
|
||||
...base_attributes,
|
||||
installed_version: null,
|
||||
friendly_name: "Update without installed_version",
|
||||
}),
|
||||
getEntity("update", "update15", "off", {
|
||||
...base_attributes,
|
||||
latest_version: null,
|
||||
friendly_name: "Update without latest_version",
|
||||
}),
|
||||
getEntity("update", "update16", "off", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with release notes",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
}),
|
||||
getEntity("update", "update17", "off", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with release notes error",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
}),
|
||||
getEntity("update", "update18", "off", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with release notes loading",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
}),
|
||||
getEntity("update", "update19", "on", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with auto update",
|
||||
auto_update: true,
|
||||
}),
|
||||
getEntity("update", "update20", "on", {
|
||||
...base_attributes,
|
||||
in_progress: true,
|
||||
title: undefined,
|
||||
friendly_name: "Installing without title",
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-more-info-update")
|
||||
class DemoMoreInfoUpdate extends LitElement {
|
||||
@property() public hass!: MockHomeAssistant;
|
||||
|
||||
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<demo-more-infos
|
||||
.hass=${this.hass}
|
||||
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
||||
></demo-more-infos>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
hass.mockWS(
|
||||
"update/release_notes",
|
||||
(msg: { type: string; entity_id: string }) => {
|
||||
if (msg.entity_id === "update.update16") {
|
||||
return LONG_TEXT;
|
||||
}
|
||||
if (msg.entity_id === "update.update17") {
|
||||
return Promise.reject({
|
||||
code: "error",
|
||||
message: "Could not fetch release notes",
|
||||
});
|
||||
}
|
||||
if (msg.entity_id === "update.update18") {
|
||||
return undefined;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-more-info-update": DemoMoreInfoUpdate;
|
||||
}
|
||||
}
|
202
gallery/src/pages/user-test/configuration-menu.markdown
Normal file
202
gallery/src/pages/user-test/configuration-menu.markdown
Normal file
@@ -0,0 +1,202 @@
|
||||
---
|
||||
title: "User Test: Configuration menu"
|
||||
---
|
||||
|
||||
# User Test: Configuration menu (10-17 January, 2022)
|
||||
|
||||
At the end of last year, we created one Configuration menu by merging Supervisor. In the next iteration, we want to organize our menu by creating logical grouping and combining duplicated features. We are conducting this test to see if we are on the right track.
|
||||
|
||||
* Anyone could join
|
||||
* Respondents recruited on Twitter, Reddit and Home Assistant Forum
|
||||
* This test is open for 10 days
|
||||
* UsabilityHub for user test
|
||||
* Figma for prototype
|
||||
* 6 questions
|
||||
* 3 tasks
|
||||
* Due to some limitations by UsabilityHub, it only worked on desktop
|
||||
|
||||
# Results
|
||||
915 respondents took part in this test and they gave 407 comments. In general there isn’t a significant difference between:
|
||||
|
||||
* How long a respondent has been using Home Assistant
|
||||
* Installation method
|
||||
* How many visits to its Home Assistant in the past 3 months
|
||||
* Home Assistant expertise
|
||||
|
||||
## Overall menu change
|
||||
This prototype organized our menu by creating logical grouping and combining duplicated features. What do people think of this change?
|
||||
|
||||
### Stats
|
||||
* 2% (21) Like extremely
|
||||
* 30% (276) Like very much
|
||||
* 53% (481) Neutral
|
||||
* 12% (108) Dislike very much
|
||||
* 3% (26) Dislike extremely
|
||||
|
||||
*3 respondents passed*
|
||||
|
||||
### Comments summary
|
||||
**Like**
|
||||
|
||||
* Clean and decluttered
|
||||
* Style looks better
|
||||
* Faster to use
|
||||
* Merging Supervisor into different pages
|
||||
* Moving Developer tools to Settings
|
||||
|
||||
**Dislike**
|
||||
|
||||
* Moving Developer tools to Settings
|
||||
* More clicks for scripts and helpers
|
||||
* Too many changes at once causes a high learning curve
|
||||
* Removing the word `Integrations` makes it harder to find them
|
||||
* Difference between `Addons` and `Services` is a bit subtle
|
||||
* No clear distinction between `Developer` and `System`
|
||||
* Material Design got the Google image
|
||||
|
||||
**Suggestions**
|
||||
|
||||
* More top level menu items for example logs.
|
||||
* What are settings and what not? Maybe better to name it `Configuration`
|
||||
* Devices are a first-class citizen in the domain of Home Assistant, and so shouldn't be tucked away in "Settings"
|
||||
* Rename Developer tools (or make it only for Home Assistant developers)
|
||||
* Separate administration (for instance creating users / adding lights etc) from development activities (creating automations and scripts)
|
||||
* Search Bar in Settings
|
||||
* Feature to put menu items in sidebar
|
||||
* Unification of add-ons and integrations
|
||||
* Adding ‘New’ hints to show what changed
|
||||
* Give `About` a less prominent size
|
||||
* Accordion view option which puts every tab below
|
||||
* Dev mode and a Prod Mode
|
||||
* Always show config menu (on bigger screens)
|
||||
|
||||
### Conclusion
|
||||
We should keep our focus on organizing our menu by creating logical grouping and combining duplicated features. With these changes we make more people happy:
|
||||
|
||||
* Reconsider putting `Logs` as a top-level menu item
|
||||
* Add a search bar
|
||||
* Use the word `Integrations` with `Devices & Services`
|
||||
* Moving `Developer tools` to `Settings` is a good idea
|
||||
* Rename `Developer tools` to for example `Tools`
|
||||
* Add `New` explanation popups to what has changed
|
||||
* We could rename `Configuration` to `Settings`
|
||||
* Give `About` a less prominent size
|
||||
|
||||
## Helpers
|
||||
In Home Assistant you can create toggles, text fields, number sliders, timers and counters. Also known as `Helpers`. Where should they be placed?
|
||||
|
||||
### Stats
|
||||
* 78% (709) respondents are using helpers. They use it for:
|
||||
* 92% (645) automations and scenes
|
||||
* 62% (422) dashboards
|
||||
* 43% (296) virtual devices
|
||||
|
||||
### Comments summary
|
||||
Some respondents commented that they think `Helpers` shouldn’t be listed under `Automations & Services`. Although almost all respondents use it for that specific purpose.
|
||||
|
||||
### Conclusion
|
||||
Helpers is, in addition to `Automations & Services`, also partly seen as virtual devices and dashboard entities.
|
||||
|
||||
* We might consider promoting them in their own top-level menu item
|
||||
* Rename `Helpers` to something with `controls`
|
||||
|
||||
## Add person
|
||||
The first task in this user test was to add a person. Since this has not changed in the current menu structure, this should be an easy assignment. How do people experience the navigation to this feature?
|
||||
|
||||
### Stats
|
||||
95% reached the goal screen and 98% marked the task as completed. There were 18 common paths.
|
||||
|
||||
After the task we asked how easy it was to add a person.
|
||||
|
||||
* 41% (378) Extremely easy
|
||||
* 48% (440) Fairly easy
|
||||
* 7% (67) Neutral
|
||||
* 2% (19) Somewhat difficult
|
||||
* 1% (11) Very difficult
|
||||
|
||||
### Comments summary
|
||||
*No mentionable comments *
|
||||
|
||||
### Conclusion
|
||||
This test showed that the current navigation design works.
|
||||
|
||||
## YAML
|
||||
In Home Assistant you can make configuration changes in YAML files. To make these changes take effect you have to reload your YAML in the UI or do a restart. How are people doing this and can they find it in this new design?
|
||||
|
||||
### Stats
|
||||
83% reached the goal screen and 87% marked the task as completed. There were 59 common paths.
|
||||
|
||||
After the task we asked how easy it was to reload the YAML changes.
|
||||
|
||||
* 4% (40) Extremely easy
|
||||
* 22% (204) Fairly easy
|
||||
* 20% (179) Neutral
|
||||
* 37% (336) Somewhat difficult
|
||||
* 17% (156) Very difficult
|
||||
|
||||
And we asked if they have seen that we've moved some functionality from current `Server Controls` to `Developer Tools`.
|
||||
|
||||
* 57% (517) Yes
|
||||
* 43% (398) No
|
||||
|
||||
### Comments summary
|
||||
**Like**
|
||||
|
||||
* YAML in Developer tools
|
||||
|
||||
**Dislike**
|
||||
|
||||
* Hidden restart and reload
|
||||
* YAML in Developer Tools
|
||||
* Combining `Developer tools` with `Server management`
|
||||
* Reload Home Assistant button isn't clear what it does
|
||||
* Reload/restart Home Assistant in Developer Tools
|
||||
|
||||
**Suggestions**
|
||||
|
||||
* Reload all YAML button
|
||||
* Dev mode and a Prod Mode
|
||||
* Show restart/reload as buttons in System instead of overflow menu
|
||||
* Explain that you can reload YAML when you want to restart your system
|
||||
* YAML reloading under System
|
||||
|
||||
### Conclusion
|
||||
This test showed two different kinds of user groups: UI and YAML users.
|
||||
|
||||
* Moving `Developer tools` to `Settings` is a good idea
|
||||
* YAML users want reload YAML and Home Assistant restart in `System`
|
||||
* Move the restart and reload button to the `System` page from the overflow menu
|
||||
* Add suggestion to reload YAML when a user wants to restart
|
||||
* Add reload all YAML button
|
||||
|
||||
## Logs
|
||||
### Stats
|
||||
70% reached the goal screen and 77% marked the task as completed. There were 48 common paths.
|
||||
|
||||
After the task we asked to find out why your Elgato light isn't working.
|
||||
|
||||
* 6% (57) Extremely easy
|
||||
* 28% (254) Fairly easy
|
||||
* 21% (188) Neutral
|
||||
* 21% (196) Somewhat difficult
|
||||
* 24% (220) Very difficult
|
||||
|
||||
### Comments summary
|
||||
|
||||
**Suggestions**
|
||||
|
||||
* Log errors on the integration page
|
||||
* Problem solving center
|
||||
|
||||
### Conclusion
|
||||
Although this test shows that a large number of respondents manage to complete the task, they find it difficult to find out the light isn’t working.
|
||||
|
||||
* Add logs errors/warnings to the integration page
|
||||
* Reconsider putting `Logs` as a top-level menu item
|
||||
|
||||
## Learnings for next user test
|
||||
* Explain that topic is closed for comments so that you can do this test without any influence
|
||||
* Mobile test should work on mobile
|
||||
* Testing on an iPad got some bugs
|
||||
* People like doing these kind of test and we should do them more often
|
||||
|
17
gallery/src/pages/user-test/user-types.markdown
Normal file
17
gallery/src/pages/user-test/user-types.markdown
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: "User types"
|
||||
---
|
||||
|
||||
We have defined three user types for Home Assistant. They are a lean segmentation of users that helps us make decisions throughout the product. User types differ from traditional personas in that the segmentation criteria aren’t demographic and don’t personify a group into a single character with a fictitious background story.
|
||||
|
||||
# Outgrowers
|
||||
|
||||
Users that outgrow big tech smart home solutions. It just needs to work with easy setup via an app.
|
||||
|
||||
# Tinkerers
|
||||
|
||||
Technoid users in home networking and development that know how to code.
|
||||
|
||||
# Questioner
|
||||
|
||||
Users who want more advanced home automation, but need support to make it work.
|
@@ -42,7 +42,9 @@ class HassioAddonRepositoryEl extends LitElement {
|
||||
const repo = this.repo;
|
||||
let _addons = this.addons;
|
||||
if (!this.hass.userData?.showAdvanced) {
|
||||
_addons = _addons.filter((addon) => !addon.advanced);
|
||||
_addons = _addons.filter(
|
||||
(addon) => !addon.advanced && addon.stage === "stable"
|
||||
);
|
||||
}
|
||||
const addons = this._getAddons(_addons, this.filter);
|
||||
|
||||
|
@@ -14,7 +14,7 @@ import memoizeOne from "memoize-one";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import { navigate } from "../../../src/common/navigate";
|
||||
import "../../../src/common/search/search-input";
|
||||
import "../../../src/components/search-input";
|
||||
import { extractSearchParam } from "../../../src/common/url/search-params";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-icon-button";
|
||||
@@ -95,7 +95,7 @@ class HassioAddonStore extends LitElement {
|
||||
${this.supervisor.localize("store.repositories")}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item>
|
||||
${this.supervisor.localize("common.reload")}
|
||||
${this.supervisor.localize("store.check_updates")}
|
||||
</mwc-list-item>
|
||||
${this.hass.userData?.showAdvanced &&
|
||||
atLeastVersion(this.hass.config.version, 0, 117)
|
||||
@@ -110,8 +110,6 @@ class HassioAddonStore extends LitElement {
|
||||
<div class="search">
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
no-label-float
|
||||
no-underline
|
||||
.filter=${this._filter}
|
||||
@value-changed=${this._filterChanged}
|
||||
></search-input>
|
||||
@@ -221,13 +219,14 @@ class HassioAddonStore extends LitElement {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.search {
|
||||
padding: 0 16px;
|
||||
background: var(--sidebar-background-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
.search search-input {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
search-input {
|
||||
display: block;
|
||||
--mdc-text-field-fill-color: var(--sidebar-background-color);
|
||||
--mdc-text-field-idle-line-color: var(--divider-color);
|
||||
}
|
||||
.advanced {
|
||||
padding: 12px;
|
||||
|
@@ -1,7 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -11,10 +9,11 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "web-animations-js/web-animations-next-lite.min";
|
||||
import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-select";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
@@ -57,49 +56,44 @@ class HassioAddonAudio extends LitElement {
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
|
||||
<paper-dropdown-menu
|
||||
${this._inputDevices &&
|
||||
html`<ha-select
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.configuration.audio.input"
|
||||
)}
|
||||
@iron-select=${this._setInputDevice}
|
||||
@selected=${this._setInputDevice}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.value=${this._selectedInput!}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="device"
|
||||
.selected=${this._selectedInput!}
|
||||
>
|
||||
${this._inputDevices &&
|
||||
this._inputDevices.map(
|
||||
(item) => html`
|
||||
<paper-item device=${item.device || ""}>
|
||||
${item.name}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
<paper-dropdown-menu
|
||||
${this._inputDevices.map(
|
||||
(item) => html`
|
||||
<mwc-list-item .value=${item.device || ""}>
|
||||
${item.name}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>`}
|
||||
${this._outputDevices &&
|
||||
html`<ha-select
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.configuration.audio.output"
|
||||
)}
|
||||
@iron-select=${this._setOutputDevice}
|
||||
@selected=${this._setOutputDevice}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.value=${this._selectedOutput!}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="device"
|
||||
.selected=${this._selectedOutput!}
|
||||
>
|
||||
${this._outputDevices &&
|
||||
this._outputDevices.map(
|
||||
(item) => html`
|
||||
<paper-item device=${item.device || ""}
|
||||
>${item.name}</paper-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
${this._outputDevices.map(
|
||||
(item) => html`
|
||||
<mwc-list-item .value=${item.device || ""}
|
||||
>${item.name}</mwc-list-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</ha-select>`}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button @click=${this._saveSettings}>
|
||||
@@ -116,8 +110,7 @@ class HassioAddonAudio extends LitElement {
|
||||
hassioStyle,
|
||||
css`
|
||||
:host,
|
||||
ha-card,
|
||||
paper-dropdown-menu {
|
||||
ha-card {
|
||||
display: block;
|
||||
}
|
||||
paper-item {
|
||||
@@ -126,24 +119,30 @@ class HassioAddonAudio extends LitElement {
|
||||
.card-actions {
|
||||
text-align: right;
|
||||
}
|
||||
ha-select {
|
||||
width: 100%;
|
||||
}
|
||||
ha-select:last-child {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
protected update(changedProperties: PropertyValues): void {
|
||||
super.update(changedProperties);
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("addon")) {
|
||||
this._addonChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private _setInputDevice(ev): void {
|
||||
const device = ev.detail.item.getAttribute("device");
|
||||
const device = ev.target.value;
|
||||
this._selectedInput = device;
|
||||
}
|
||||
|
||||
private _setOutputDevice(ev): void {
|
||||
const device = ev.detail.item.getAttribute("device");
|
||||
const device = ev.target.value;
|
||||
this._selectedOutput = device;
|
||||
}
|
||||
|
||||
|
@@ -39,7 +39,14 @@ import type { HomeAssistant } from "../../../../src/types";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"];
|
||||
const SUPPORTED_UI_TYPES = [
|
||||
"string",
|
||||
"select",
|
||||
"boolean",
|
||||
"integer",
|
||||
"float",
|
||||
"schema",
|
||||
];
|
||||
|
||||
const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
|
||||
new Type("!secret", {
|
||||
@@ -48,6 +55,8 @@ const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
|
||||
}),
|
||||
]);
|
||||
|
||||
const MASKED_FIELDS = ["password", "secret", "token"];
|
||||
|
||||
@customElement("hassio-addon-config")
|
||||
class HassioAddonConfig extends LitElement {
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
@@ -75,19 +84,66 @@ class HassioAddonConfig extends LitElement {
|
||||
public computeLabel = (entry: HaFormSchema): string =>
|
||||
this.addon.translations[this.hass.language]?.configuration?.[entry.name]
|
||||
?.name ||
|
||||
this.addon.translations.en?.configuration?.[entry.name].name ||
|
||||
this.addon.translations.en?.configuration?.[entry.name]?.name ||
|
||||
entry.name;
|
||||
|
||||
private _schema = memoizeOne((schema: HaFormSchema[]): HaFormSchema[] =>
|
||||
// @ts-expect-error supervisor does not implement [string, string] for select.options[]
|
||||
schema.map((entry) =>
|
||||
entry.type === "select"
|
||||
? {
|
||||
...entry,
|
||||
options: entry.options.map((option) => [option, option]),
|
||||
}
|
||||
: entry
|
||||
)
|
||||
public computeHelper = (entry: HaFormSchema): string =>
|
||||
this.addon.translations[this.hass.language]?.configuration?.[entry.name]
|
||||
?.description ||
|
||||
this.addon.translations.en?.configuration?.[entry.name]?.description ||
|
||||
"";
|
||||
|
||||
private _convertSchema = memoizeOne(
|
||||
// Convert supervisor schema to selectors
|
||||
(schema: Record<string, any>): HaFormSchema[] =>
|
||||
schema.map((entry) =>
|
||||
entry.type === "select"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { select: { options: entry.options } },
|
||||
}
|
||||
: entry.type === "string"
|
||||
? entry.multiple
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: {
|
||||
select: { options: [], multiple: true, custom_value: true },
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: {
|
||||
text: {
|
||||
type:
|
||||
entry.format || MASKED_FIELDS.includes(entry.name)
|
||||
? "password"
|
||||
: "text",
|
||||
},
|
||||
},
|
||||
}
|
||||
: entry.type === "boolean"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { boolean: {} },
|
||||
}
|
||||
: entry.type === "schema"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { object: {} },
|
||||
}
|
||||
: entry.type === "float" || entry.type === "integer"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { number: { mode: "box" } },
|
||||
}
|
||||
: entry
|
||||
)
|
||||
);
|
||||
|
||||
private _filteredShchema = memoizeOne(
|
||||
@@ -114,7 +170,7 @@ class HassioAddonConfig extends LitElement {
|
||||
<div class="card-menu">
|
||||
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.menu")}
|
||||
.label=${this.supervisor.localize("common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
@@ -140,7 +196,8 @@ class HassioAddonConfig extends LitElement {
|
||||
.data=${this._options!}
|
||||
@value-changed=${this._configChanged}
|
||||
.computeLabel=${this.computeLabel}
|
||||
.schema=${this._schema(
|
||||
.computeHelper=${this.computeHelper}
|
||||
.schema=${this._convertSchema(
|
||||
this._showOptional
|
||||
? this.addon.schema!
|
||||
: this._filteredShchema(
|
||||
@@ -197,8 +254,9 @@ class HassioAddonConfig extends LitElement {
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._canShowSchema = !this.addon.schema!.find(
|
||||
// @ts-ignore
|
||||
(entry) => !SUPPORTED_UI_TYPES.includes(entry.type) || entry.multiple
|
||||
(entry) =>
|
||||
// @ts-ignore
|
||||
!SUPPORTED_UI_TYPES.includes(entry.type)
|
||||
);
|
||||
this._yamlMode = !this._canShowSchema;
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -8,10 +7,13 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
@@ -24,16 +26,6 @@ import { HomeAssistant } from "../../../../src/types";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
interface NetworkItem {
|
||||
description: string;
|
||||
container: string;
|
||||
host: number | null;
|
||||
}
|
||||
|
||||
interface NetworkItemInput extends PaperInputElement {
|
||||
container: string;
|
||||
}
|
||||
|
||||
@customElement("hassio-addon-network")
|
||||
class HassioAddonNetwork extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -42,9 +34,13 @@ class HassioAddonNetwork extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@state() private _showOptional = false;
|
||||
|
||||
@state() private _configHasChanged = false;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _config?: NetworkItem[];
|
||||
@state() private _config?: Record<string, any>;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
@@ -56,6 +52,10 @@ class HassioAddonNetwork extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const hasHiddenOptions = Object.keys(this._config).find(
|
||||
(entry) => this._config![entry] === null
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
.header=${this.supervisor.localize(
|
||||
@@ -63,52 +63,49 @@ class HassioAddonNetwork extends LitElement {
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.supervisor.localize(
|
||||
"addon.configuration.network.introduction"
|
||||
)}
|
||||
</p>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
${this.supervisor.localize(
|
||||
"addon.configuration.network.container"
|
||||
)}
|
||||
</th>
|
||||
<th>
|
||||
${this.supervisor.localize(
|
||||
"addon.configuration.network.host"
|
||||
)}
|
||||
</th>
|
||||
<th>${this.supervisor.localize("common.description")}</th>
|
||||
</tr>
|
||||
${this._config!.map(
|
||||
(item) => html`
|
||||
<tr>
|
||||
<td>${item.container}</td>
|
||||
<td>
|
||||
<paper-input
|
||||
@value-changed=${this._configChanged}
|
||||
placeholder=${this.supervisor.localize(
|
||||
"addon.configuration.network.disabled"
|
||||
)}
|
||||
.value=${item.host ? String(item.host) : ""}
|
||||
.container=${item.container}
|
||||
no-label-float
|
||||
></paper-input>
|
||||
</td>
|
||||
<td>${this._computeDescription(item)}</td>
|
||||
</tr>
|
||||
`
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<ha-form
|
||||
.data=${this._config}
|
||||
@value-changed=${this._configChanged}
|
||||
.computeLabel=${this._computeLabel}
|
||||
.computeHelper=${this._computeHelper}
|
||||
.schema=${this._createSchema(
|
||||
this._config,
|
||||
this._showOptional,
|
||||
this.hass.userData?.showAdvanced || false
|
||||
)}
|
||||
></ha-form>
|
||||
</div>
|
||||
${hasHiddenOptions
|
||||
? html`<ha-formfield
|
||||
class="show-optional"
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.configuration.network.show_disabled"
|
||||
)}
|
||||
>
|
||||
<ha-switch
|
||||
@change=${this._toggleOptional}
|
||||
.checked=${this._showOptional}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>`
|
||||
: ""}
|
||||
<div class="card-actions">
|
||||
<ha-progress-button class="warning" @click=${this._resetTapped}>
|
||||
${this.supervisor.localize("common.reset_defaults")}
|
||||
</ha-progress-button>
|
||||
<ha-progress-button @click=${this._saveTapped}>
|
||||
<ha-progress-button
|
||||
@click=${this._saveTapped}
|
||||
.disabled=${!this._configHasChanged}
|
||||
>
|
||||
${this.supervisor.localize("common.save")}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
@@ -123,50 +120,60 @@ class HassioAddonNetwork extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _computeDescription = (item: NetworkItem): string =>
|
||||
this.addon.translations[this.hass.language]?.network?.[item.container]
|
||||
?.description ||
|
||||
this.addon.translations.en?.network?.[item.container]?.description ||
|
||||
item.description;
|
||||
private _createSchema = memoizeOne(
|
||||
(
|
||||
config: Record<string, number>,
|
||||
showOptional: boolean,
|
||||
advanced: boolean
|
||||
): HaFormSchema[] =>
|
||||
(showOptional
|
||||
? Object.keys(config)
|
||||
: Object.keys(config).filter((entry) => config[entry] !== null)
|
||||
).map((entry) => ({
|
||||
name: entry,
|
||||
selector: {
|
||||
number: {
|
||||
mode: "box",
|
||||
min: 0,
|
||||
max: 65535,
|
||||
unit_of_measurement: advanced ? entry : undefined,
|
||||
},
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
private _computeLabel = (_: HaFormSchema): string => "";
|
||||
|
||||
private _computeHelper = (item: HaFormSchema): string =>
|
||||
this.addon.translations[this.hass.language]?.network?.[item.name] ||
|
||||
this.addon.translations.en?.network?.[item.name] ||
|
||||
this.addon.network_description?.[item.name] ||
|
||||
item.name;
|
||||
|
||||
private _setNetworkConfig(): void {
|
||||
const network = this.addon.network || {};
|
||||
const description = this.addon.network_description || {};
|
||||
const items: NetworkItem[] = Object.keys(network).map((key) => ({
|
||||
container: key,
|
||||
host: network[key],
|
||||
description: description[key],
|
||||
}));
|
||||
this._config = items.sort((a, b) => (a.container > b.container ? 1 : -1));
|
||||
this._config = this.addon.network || {};
|
||||
}
|
||||
|
||||
private async _configChanged(ev: Event): Promise<void> {
|
||||
const target = ev.target as NetworkItemInput;
|
||||
this._config!.forEach((item) => {
|
||||
if (
|
||||
item.container === target.container &&
|
||||
item.host !== parseInt(String(target.value), 10)
|
||||
) {
|
||||
item.host = target.value ? parseInt(String(target.value), 10) : null;
|
||||
}
|
||||
});
|
||||
private async _configChanged(ev: CustomEvent): Promise<void> {
|
||||
this._configHasChanged = true;
|
||||
this._config! = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
network: null,
|
||||
};
|
||||
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "option",
|
||||
};
|
||||
button.actionSuccess();
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||
@@ -177,19 +184,21 @@ class HassioAddonNetwork extends LitElement {
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
button.actionError();
|
||||
}
|
||||
}
|
||||
|
||||
button.progress = false;
|
||||
private _toggleOptional() {
|
||||
this._showOptional = !this._showOptional;
|
||||
}
|
||||
|
||||
private async _saveTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
this._error = undefined;
|
||||
const networkconfiguration = {};
|
||||
this._config!.forEach((item) => {
|
||||
networkconfiguration[item.container] = parseInt(String(item.host), 10);
|
||||
Object.entries(this._config!).forEach(([key, value]) => {
|
||||
networkconfiguration[key] = value ?? null;
|
||||
});
|
||||
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
@@ -198,11 +207,13 @@ class HassioAddonNetwork extends LitElement {
|
||||
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "option",
|
||||
};
|
||||
button.actionSuccess();
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||
@@ -213,8 +224,8 @@ class HassioAddonNetwork extends LitElement {
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
button.actionError();
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
@@ -232,6 +243,9 @@ class HassioAddonNetwork extends LitElement {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.show-optional {
|
||||
padding: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
mdiFlask,
|
||||
mdiHomeAssistant,
|
||||
mdiKey,
|
||||
mdiLinkLock,
|
||||
mdiNetwork,
|
||||
mdiNumeric1,
|
||||
mdiNumeric2,
|
||||
@@ -16,6 +17,8 @@ import {
|
||||
mdiNumeric4,
|
||||
mdiNumeric5,
|
||||
mdiNumeric6,
|
||||
mdiNumeric7,
|
||||
mdiNumeric8,
|
||||
mdiPound,
|
||||
mdiShield,
|
||||
} from "@mdi/js";
|
||||
@@ -31,6 +34,7 @@ import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-chip";
|
||||
import "../../../../src/components/ha-chip-set";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
@@ -84,6 +88,8 @@ const RATING_ICON = {
|
||||
4: mdiNumeric4,
|
||||
5: mdiNumeric5,
|
||||
6: mdiNumeric6,
|
||||
7: mdiNumeric7,
|
||||
8: mdiNumeric8,
|
||||
};
|
||||
|
||||
@customElement("hassio-addon-info")
|
||||
@@ -209,7 +215,7 @@ class HassioAddonInfo extends LitElement {
|
||||
>`}
|
||||
</div>
|
||||
|
||||
<div class="capabilities">
|
||||
<ha-chip-set class="capabilities">
|
||||
${this.addon.stage !== "stable"
|
||||
? html` <ha-chip
|
||||
hasIcon
|
||||
@@ -234,9 +240,9 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-chip
|
||||
hasIcon
|
||||
class=${classMap({
|
||||
green: [5, 6].includes(Number(this.addon.rating)),
|
||||
yellow: [3, 4].includes(Number(this.addon.rating)),
|
||||
red: [1, 2].includes(Number(this.addon.rating)),
|
||||
green: Number(this.addon.rating) >= 6,
|
||||
yellow: [3, 4, 5].includes(Number(this.addon.rating)),
|
||||
red: Number(this.addon.rating) >= 2,
|
||||
})}
|
||||
@click=${this._showMoreInfo}
|
||||
id="rating"
|
||||
@@ -364,7 +370,17 @@ class HassioAddonInfo extends LitElement {
|
||||
</ha-chip>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
${this.addon.signed
|
||||
? html`
|
||||
<ha-chip hasIcon @click=${this._showMoreInfo} id="signed">
|
||||
<ha-svg-icon slot="icon" .path=${mdiLinkLock}></ha-svg-icon>
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.capability.label.signed"
|
||||
)}
|
||||
</ha-chip>
|
||||
`
|
||||
: ""}
|
||||
</ha-chip-set>
|
||||
|
||||
<div class="description light-color">
|
||||
${this.addon.description}.<br />
|
||||
|
@@ -2,6 +2,7 @@ import "@material/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-ansi-to-html";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
fetchHassioAddonLogs,
|
||||
@@ -11,7 +12,6 @@ import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/hassio-ansi-to-html";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-addon-logs")
|
||||
@@ -40,9 +40,9 @@ class HassioAddonLogs extends LitElement {
|
||||
: ""}
|
||||
<div class="card-content">
|
||||
${this._content
|
||||
? html`<hassio-ansi-to-html
|
||||
? html`<ha-ansi-to-html
|
||||
.content=${this._content}
|
||||
></hassio-ansi-to-html>`
|
||||
></ha-ansi-to-html>`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import "@material/mwc-button";
|
||||
import { ActionDetail } from "@material/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
|
||||
import { mdiBackupRestore, mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -119,7 +119,7 @@ export class HassioBackups extends LitElement {
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean): DataTableColumnContainer => ({
|
||||
name: {
|
||||
title: this.supervisor?.localize("backup.name") || "",
|
||||
title: this.supervisor.localize("backup.name"),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
grows: true,
|
||||
@@ -127,8 +127,16 @@ export class HassioBackups extends LitElement {
|
||||
html`${entry || backup.slug}
|
||||
<div class="secondary">${backup.secondary}</div>`,
|
||||
},
|
||||
size: {
|
||||
title: this.supervisor.localize("backup.size"),
|
||||
width: "15%",
|
||||
hidden: narrow,
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
template: (entry: number) => Math.ceil(entry * 10) / 10 + " MB",
|
||||
},
|
||||
date: {
|
||||
title: this.supervisor?.localize("backup.created") || "",
|
||||
title: this.supervisor.localize("backup.created"),
|
||||
width: "15%",
|
||||
direction: "desc",
|
||||
hidden: narrow,
|
||||
@@ -158,7 +166,15 @@ export class HassioBackups extends LitElement {
|
||||
}
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
.tabs=${supervisorTabs(this.hass)}
|
||||
.tabs=${atLeastVersion(this.hass.config.version, 2022, 5)
|
||||
? [
|
||||
{
|
||||
translationKey: "panel.backups",
|
||||
path: `/hassio/backups`,
|
||||
iconPath: mdiBackupRestore,
|
||||
},
|
||||
]
|
||||
: supervisorTabs(this.hass)}
|
||||
.hass=${this.hass}
|
||||
.localizeFunc=${this.supervisor.localize}
|
||||
.searchLabel=${this.supervisor.localize("search")}
|
||||
@@ -174,7 +190,9 @@ export class HassioBackups extends LitElement {
|
||||
selectable
|
||||
hasFab
|
||||
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
|
||||
back-path="/config"
|
||||
back-path=${atLeastVersion(this.hass.config.version, 2022, 5)
|
||||
? "/config/system"
|
||||
: "/config"}
|
||||
supervisor
|
||||
>
|
||||
<ha-button-menu
|
||||
@@ -183,16 +201,16 @@ export class HassioBackups extends LitElement {
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.menu")}
|
||||
.label=${this.supervisor?.localize("common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
<mwc-list-item>
|
||||
${this.supervisor?.localize("common.reload")}
|
||||
${this.supervisor.localize("common.reload")}
|
||||
</mwc-list-item>
|
||||
${atLeastVersion(this.hass.config.version, 0, 116)
|
||||
? html`<mwc-list-item>
|
||||
${this.supervisor?.localize("backup.upload_backup")}
|
||||
${this.supervisor.localize("backup.upload_backup")}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { mdiFolderUpload } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input-container";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import { formatDate } from "../../../src/common/datetime/format_date";
|
||||
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
|
||||
@@ -32,13 +32,6 @@ interface AddonCheckboxItem extends CheckboxItem {
|
||||
|
||||
const _computeFolders = (folders): CheckboxItem[] => {
|
||||
const list: CheckboxItem[] = [];
|
||||
if (folders.includes("homeassistant")) {
|
||||
list.push({
|
||||
slug: "homeassistant",
|
||||
name: "Home Assistant configuration",
|
||||
checked: false,
|
||||
});
|
||||
}
|
||||
if (folders.includes("ssl")) {
|
||||
list.push({ slug: "ssl", name: "SSL", checked: false });
|
||||
}
|
||||
@@ -92,13 +85,15 @@ export class SupervisorBackupContent extends LitElement {
|
||||
|
||||
@property() public confirmBackupPassword = "";
|
||||
|
||||
@query("paper-input, ha-radio, ha-checkbox", true) private _focusTarget;
|
||||
|
||||
public willUpdate(changedProps) {
|
||||
super.willUpdate(changedProps);
|
||||
if (!this.hasUpdated) {
|
||||
this.folders = _computeFolders(
|
||||
this.backup
|
||||
? this.backup.folders
|
||||
: ["homeassistant", "ssl", "share", "media", "addons/local"]
|
||||
: ["ssl", "share", "media", "addons/local"]
|
||||
);
|
||||
this.addons = _computeAddons(
|
||||
this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
|
||||
@@ -109,6 +104,10 @@ export class SupervisorBackupContent extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
public override focus() {
|
||||
this._focusTarget?.focus();
|
||||
}
|
||||
|
||||
private _localize = (string: string) =>
|
||||
this.supervisor?.localize(`backup.${string}`) ||
|
||||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
|
||||
@@ -169,24 +168,23 @@ export class SupervisorBackupContent extends LitElement {
|
||||
: ""}
|
||||
${this.backupType === "partial"
|
||||
? html`<div class="partial-picker">
|
||||
${this.backup && this.backup.homeassistant
|
||||
? html`
|
||||
<ha-formfield
|
||||
.label=${html`<supervisor-formfield-label
|
||||
label="Home Assistant"
|
||||
.iconPath=${mdiHomeAssistant}
|
||||
.version=${this.backup.homeassistant}
|
||||
>
|
||||
</supervisor-formfield-label>`}
|
||||
>
|
||||
<ha-checkbox
|
||||
.checked=${this.homeAssistant}
|
||||
@click=${this.toggleHomeAssistant}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>
|
||||
`
|
||||
: ""}
|
||||
<ha-formfield
|
||||
.label=${html`<supervisor-formfield-label
|
||||
label="Home Assistant"
|
||||
.iconPath=${mdiHomeAssistant}
|
||||
.version=${this.backup
|
||||
? this.backup.homeassistant
|
||||
: this.hass.config.version}
|
||||
>
|
||||
</supervisor-formfield-label>`}
|
||||
>
|
||||
<ha-checkbox
|
||||
.checked=${this.homeAssistant}
|
||||
@change=${this.toggleHomeAssistant}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>
|
||||
|
||||
${foldersSection?.templates.length
|
||||
? html`
|
||||
<ha-formfield
|
||||
|
@@ -10,6 +10,7 @@ import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import "./hassio-addons";
|
||||
import "./hassio-update";
|
||||
import "../../../src/layouts/hass-subpage";
|
||||
|
||||
@customElement("hassio-dashboard")
|
||||
class HassioDashboard extends LitElement {
|
||||
@@ -22,6 +23,31 @@ class HassioDashboard extends LitElement {
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (atLeastVersion(this.hass.config.version, 2022, 5)) {
|
||||
return html`<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.header=${this.supervisor.localize("panel.addons")}
|
||||
>
|
||||
<hassio-addons
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this.supervisor}
|
||||
></hassio-addons>
|
||||
<a href="/hassio/store">
|
||||
<ha-fab
|
||||
.label=${this.supervisor.localize("panel.store")}
|
||||
extended
|
||||
class="non-tabs"
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiStorePlus}
|
||||
></ha-svg-icon> </ha-fab
|
||||
></a>
|
||||
</hass-subpage>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
@@ -74,6 +100,12 @@ class HassioDashboard extends LitElement {
|
||||
.content {
|
||||
margin: 0 auto;
|
||||
}
|
||||
ha-fab.non-tabs {
|
||||
position: fixed;
|
||||
right: calc(16px + env(safe-area-inset-right));
|
||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
z-index: 1;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -148,7 +148,6 @@ export class HassioUpdate extends LitElement {
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
--paper-item-body-two-line-min-height: 32px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -17,27 +17,27 @@ export class DialogHassioBackupUpload
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _params?: HassioBackupUploadDialogParams;
|
||||
@state() private _dialogParams?: HassioBackupUploadDialogParams;
|
||||
|
||||
public async showDialog(
|
||||
params: HassioBackupUploadDialogParams
|
||||
dialogParams: HassioBackupUploadDialogParams
|
||||
): Promise<void> {
|
||||
this._params = params;
|
||||
this._dialogParams = dialogParams;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
if (this._params && !this._params.onboarding) {
|
||||
if (this._params.reloadBackup) {
|
||||
this._params.reloadBackup();
|
||||
if (this._dialogParams && !this._dialogParams.onboarding) {
|
||||
if (this._dialogParams.reloadBackup) {
|
||||
this._dialogParams.reloadBackup();
|
||||
}
|
||||
}
|
||||
this._params = undefined;
|
||||
this._dialogParams = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
if (!this._dialogParams) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
@@ -47,17 +47,24 @@ export class DialogHassioBackupUpload
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
hideActions
|
||||
.heading=${true}
|
||||
.heading=${this.hass?.localize(
|
||||
"ui.panel.page-onboarding.restore.upload_backup"
|
||||
) || "Upload backup"}
|
||||
@closed=${this.closeDialog}
|
||||
>
|
||||
<div slot="heading">
|
||||
<ha-header-bar>
|
||||
<span slot="title"> Upload backup </span>
|
||||
<span slot="title"
|
||||
>${this.hass?.localize(
|
||||
"ui.panel.page-onboarding.restore.upload_backup"
|
||||
) || "Upload backup"}</span
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this.hass?.localize("common.close") || "close"}
|
||||
.label=${this.hass?.localize("ui.common.close") || "Close"}
|
||||
.path=${mdiClose}
|
||||
slot="actionItems"
|
||||
dialogAction="cancel"
|
||||
dialogInitialFocus
|
||||
></ha-icon-button>
|
||||
</ha-header-bar>
|
||||
</div>
|
||||
@@ -71,7 +78,7 @@ export class DialogHassioBackupUpload
|
||||
|
||||
private _backupUploaded(ev) {
|
||||
const backup = ev.detail.backup;
|
||||
this._params?.showBackup(backup.slug);
|
||||
this._dialogParams?.showBackup(backup.slug);
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
|
@@ -48,9 +48,9 @@ class HassioBackupDialog
|
||||
@query("supervisor-backup-content")
|
||||
private _backupContent!: SupervisorBackupContent;
|
||||
|
||||
public async showDialog(params: HassioBackupDialogParams) {
|
||||
this._backup = await fetchHassioBackupInfo(this.hass, params.slug);
|
||||
this._dialogParams = params;
|
||||
public async showDialog(dialogParams: HassioBackupDialogParams) {
|
||||
this._backup = await fetchHassioBackupInfo(this.hass, dialogParams.slug);
|
||||
this._dialogParams = dialogParams;
|
||||
this._restoringBackup = false;
|
||||
}
|
||||
|
||||
@@ -71,13 +71,13 @@ class HassioBackupDialog
|
||||
open
|
||||
scrimClickAction
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${true}
|
||||
.heading=${this._backup.name}
|
||||
>
|
||||
<div slot="heading">
|
||||
<ha-header-bar>
|
||||
<span slot="title">${this._backup.name}</span>
|
||||
<ha-icon-button
|
||||
.label=${this.hass?.localize("common.close") || "close"}
|
||||
.label=${this.hass?.localize("ui.common.close") || "Close"}
|
||||
.path=${mdiClose}
|
||||
slot="actionItems"
|
||||
dialogAction="cancel"
|
||||
@@ -92,6 +92,7 @@ class HassioBackupDialog
|
||||
.backup=${this._backup}
|
||||
.onboarding=${this._dialogParams.onboarding || false}
|
||||
.localize=${this._dialogParams.localize}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</supervisor-backup-content>`}
|
||||
${this._error
|
||||
@@ -114,12 +115,20 @@ class HassioBackupDialog
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this.hass!.localize("common.menu")}
|
||||
.label=${this.hass!.localize("ui.common.menu") || "Menu"}
|
||||
.path=${mdiDotsVertical}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
<mwc-list-item>Download Backup</mwc-list-item>
|
||||
<mwc-list-item class="error">Delete Backup</mwc-list-item>
|
||||
<mwc-list-item
|
||||
>${this._dialogParams.supervisor?.localize(
|
||||
"backup.download_backup"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
<mwc-list-item class="error"
|
||||
>${this._dialogParams.supervisor?.localize(
|
||||
"backup.delete_backup_title"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
</ha-button-menu>`
|
||||
: ""}
|
||||
</ha-dialog>
|
||||
|
@@ -30,8 +30,8 @@ class HassioCreateBackupDialog extends LitElement {
|
||||
@query("supervisor-backup-content")
|
||||
private _backupContent!: SupervisorBackupContent;
|
||||
|
||||
public showDialog(params: HassioCreateBackupDialogParams) {
|
||||
this._dialogParams = params;
|
||||
public showDialog(dialogParams: HassioCreateBackupDialogParams) {
|
||||
this._dialogParams = dialogParams;
|
||||
this._creatingBackup = false;
|
||||
}
|
||||
|
||||
@@ -57,10 +57,11 @@ class HassioCreateBackupDialog extends LitElement {
|
||||
)}
|
||||
>
|
||||
${this._creatingBackup
|
||||
? html` <ha-circular-progress active></ha-circular-progress>`
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
: html`<supervisor-backup-content
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this._dialogParams.supervisor}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</supervisor-backup-content>`}
|
||||
${this._error
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import "../../../../src/components/ha-select";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
ignoreSupervisorError,
|
||||
@@ -90,18 +89,20 @@ class HassioDatadiskDialog extends LitElement {
|
||||
)}
|
||||
<br /><br />
|
||||
|
||||
<paper-dropdown-menu
|
||||
<ha-select
|
||||
.label=${this.dialogParams.supervisor.localize(
|
||||
"dialog.datadisk_move.select_device"
|
||||
)}
|
||||
@value-changed=${this._select_device}
|
||||
@selected=${this._select_device}
|
||||
dialogInitialFocus
|
||||
>
|
||||
<paper-listbox slot="dropdown-content">
|
||||
${this.devices.map(
|
||||
(device) => html`<paper-item>${device}</paper-item>`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
${this.devices.map(
|
||||
(device) =>
|
||||
html`<mwc-list-item .value=${device}
|
||||
>${device}</mwc-list-item
|
||||
>`
|
||||
)}
|
||||
</ha-select>
|
||||
`
|
||||
: this.devices === undefined
|
||||
? this.dialogParams.supervisor.localize(
|
||||
@@ -111,7 +112,11 @@ class HassioDatadiskDialog extends LitElement {
|
||||
"dialog.datadisk_move.no_devices"
|
||||
)}
|
||||
|
||||
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
|
||||
<mwc-button
|
||||
slot="secondaryAction"
|
||||
@click=${this.closeDialog}
|
||||
dialogInitialFocus
|
||||
>
|
||||
${this.dialogParams.supervisor.localize(
|
||||
"dialog.datadisk_move.cancel"
|
||||
)}
|
||||
@@ -130,8 +135,8 @@ class HassioDatadiskDialog extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _select_device(event) {
|
||||
this.selectedDevice = event.detail.value;
|
||||
private _select_device(ev) {
|
||||
this.selectedDevice = ev.target.value;
|
||||
}
|
||||
|
||||
private async _moveDatadisk() {
|
||||
@@ -156,7 +161,7 @@ class HassioDatadiskDialog extends LitElement {
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
paper-dropdown-menu {
|
||||
ha-select {
|
||||
width: 100%;
|
||||
}
|
||||
ha-circular-progress {
|
||||
|
@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/common/search/search-input";
|
||||
import "../../../../src/components/search-input";
|
||||
import { stringCompare } from "../../../../src/common/string/compare";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-expansion-panel";
|
||||
@@ -39,8 +39,8 @@ class HassioHardwareDialog extends LitElement {
|
||||
|
||||
@state() private _filter?: string;
|
||||
|
||||
public showDialog(params: HassioHardwareDialogParams) {
|
||||
this._dialogParams = params;
|
||||
public showDialog(dialogParams: HassioHardwareDialogParams) {
|
||||
this._dialogParams = dialogParams;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
@@ -65,21 +65,21 @@ class HassioHardwareDialog extends LitElement {
|
||||
scrimClickAction
|
||||
hideActions
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${true}
|
||||
.heading=${this._dialogParams.supervisor.localize(
|
||||
"dialog.hardware.title"
|
||||
)}
|
||||
>
|
||||
<div class="header" slot="heading">
|
||||
<h2>
|
||||
${this._dialogParams.supervisor.localize("dialog.hardware.title")}
|
||||
</h2>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.close")}
|
||||
.label=${this._dialogParams.supervisor.localize("common.close")}
|
||||
.path=${mdiClose}
|
||||
dialogAction="close"
|
||||
></ha-icon-button>
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
autofocus
|
||||
no-label-float
|
||||
.filter=${this._filter}
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.label=${this._dialogParams.supervisor.localize(
|
||||
@@ -176,7 +176,7 @@ class HassioHardwareDialog extends LitElement {
|
||||
padding: 0.2em 0.4em;
|
||||
}
|
||||
search-input {
|
||||
margin: 0 16px;
|
||||
margin: 8px 16px 0;
|
||||
display: block;
|
||||
}
|
||||
.device-property {
|
||||
|
@@ -37,7 +37,10 @@ class HassioMarkdownDialog extends LitElement {
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(this.hass, this.title)}
|
||||
>
|
||||
<ha-markdown .content=${this.content || ""}></ha-markdown>
|
||||
<ha-markdown
|
||||
.content=${this.content || ""}
|
||||
dialogInitialFocus
|
||||
></ha-markdown>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
@@ -94,7 +94,7 @@ export class DialogHassioNetwork
|
||||
open
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${true}
|
||||
.heading=${this.supervisor.localize("dialog.network.title")}
|
||||
hideActions
|
||||
@closed=${this.closeDialog}
|
||||
>
|
||||
@@ -104,7 +104,7 @@ export class DialogHassioNetwork
|
||||
${this.supervisor.localize("dialog.network.title")}
|
||||
</span>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.close")}
|
||||
.label=${this.supervisor.localize("common.close")}
|
||||
.path=${mdiClose}
|
||||
slot="actionItems"
|
||||
dialogAction="cancel"
|
||||
@@ -119,6 +119,7 @@ export class DialogHassioNetwork
|
||||
html`<mwc-tab
|
||||
.id=${device.interface}
|
||||
.label=${device.interface}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</mwc-tab>`
|
||||
)}
|
||||
@@ -315,6 +316,7 @@ export class DialogHassioNetwork
|
||||
value="auto"
|
||||
name="${version}method"
|
||||
.checked=${this._interface![version]?.method === "auto"}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
|
@@ -19,22 +19,21 @@ import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { RegistriesDialogParams } from "./show-dialog-registries";
|
||||
|
||||
const SCHEMA = [
|
||||
const SCHEMA: HaFormSchema[] = [
|
||||
{
|
||||
type: "string",
|
||||
name: "registry",
|
||||
required: true,
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "username",
|
||||
required: true,
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "password",
|
||||
required: true,
|
||||
format: "password",
|
||||
selector: { text: { type: "password" } },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -81,6 +80,7 @@ class HassioRegistriesDialog extends LitElement {
|
||||
.schema=${SCHEMA}
|
||||
@value-changed=${this._valueChanged}
|
||||
.computeLabel=${this._computeLabel}
|
||||
dialogInitialFocus
|
||||
></ha-form>
|
||||
<div class="action">
|
||||
<mwc-button
|
||||
@@ -125,7 +125,7 @@ class HassioRegistriesDialog extends LitElement {
|
||||
</ha-alert>
|
||||
`}
|
||||
<div class="action">
|
||||
<mwc-button @click=${this._addRegistry}>
|
||||
<mwc-button @click=${this._addRegistry} dialogInitialFocus>
|
||||
${this.supervisor.localize(
|
||||
"dialog.registries.add_new_registry"
|
||||
)}
|
||||
|
@@ -106,6 +106,9 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
</paper-item-body>
|
||||
<div class="delete">
|
||||
<ha-icon-button
|
||||
.label=${this._dialogParams!.supervisor.localize(
|
||||
"dialog.repositories.remove"
|
||||
)}
|
||||
.disabled=${usedRepositories.includes(repo.slug)}
|
||||
.slug=${repo.slug}
|
||||
.path=${usedRepositories.includes(repo.slug)
|
||||
@@ -139,6 +142,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
"dialog.repositories.add"
|
||||
)}
|
||||
@keydown=${this._handleKeyAdd}
|
||||
dialogInitialFocus
|
||||
></paper-input>
|
||||
<mwc-button @click=${this._addRepository}>
|
||||
${this._processing
|
||||
|
@@ -1,9 +1,12 @@
|
||||
// Compat needs to be first import
|
||||
import "../../src/resources/compatibility";
|
||||
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
|
||||
import "../../src/resources/roboto";
|
||||
import "../../src/resources/safari-14-attachshadow-patch";
|
||||
import "./hassio-main";
|
||||
|
||||
setCancelSyntheticClickEvents(false);
|
||||
|
||||
const styleEl = document.createElement("style");
|
||||
styleEl.innerHTML = `
|
||||
body {
|
||||
|
@@ -3,8 +3,8 @@ import { customElement, property } from "lit/decorators";
|
||||
import { atLeastVersion } from "../../src/common/config/version";
|
||||
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../src/common/dom/fire_event";
|
||||
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
|
||||
import { mainWindow } from "../../src/common/dom/get_main_window";
|
||||
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
|
||||
import { navigate } from "../../src/common/navigate";
|
||||
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
|
||||
import { Supervisor } from "../../src/data/supervisor/supervisor";
|
||||
@@ -73,6 +73,14 @@ export class HassioMain extends SupervisorBaseElement {
|
||||
});
|
||||
});
|
||||
|
||||
// Forward keydown events to the main window for quickbar access
|
||||
document.body.addEventListener("keydown", (ev) => {
|
||||
// @ts-ignore
|
||||
fireEvent(mainWindow, "hass-quick-bar-trigger", ev, {
|
||||
bubbles: false,
|
||||
});
|
||||
});
|
||||
|
||||
makeDialogManager(this, this.shadowRoot!);
|
||||
}
|
||||
|
||||
@@ -121,7 +129,8 @@ export class HassioMain extends SupervisorBaseElement {
|
||||
this.parentElement,
|
||||
this.hass.themes,
|
||||
themeName,
|
||||
themeSettings
|
||||
themeSettings,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ import {
|
||||
} from "../../src/panels/my/ha-panel-my";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
|
||||
const REDIRECTS: Redirects = {
|
||||
export const REDIRECTS: Redirects = {
|
||||
supervisor: {
|
||||
redirect: "/hassio/dashboard",
|
||||
},
|
||||
|
@@ -8,24 +8,27 @@ import { atLeastVersion } from "../../src/common/config/version";
|
||||
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant } from "../../src/types";
|
||||
|
||||
export const supervisorTabs = (hass: HomeAssistant): PageNavigation[] => [
|
||||
{
|
||||
translationKey: atLeastVersion(hass.config.version, 2021, 12)
|
||||
? "panel.addons"
|
||||
: "panel.dashboard",
|
||||
path: `/hassio/dashboard`,
|
||||
iconPath: atLeastVersion(hass.config.version, 2021, 12)
|
||||
? mdiPuzzle
|
||||
: mdiViewDashboard,
|
||||
},
|
||||
{
|
||||
translationKey: "panel.backups",
|
||||
path: `/hassio/backups`,
|
||||
iconPath: mdiBackupRestore,
|
||||
},
|
||||
{
|
||||
translationKey: "panel.system",
|
||||
path: `/hassio/system`,
|
||||
iconPath: mdiCogs,
|
||||
},
|
||||
];
|
||||
export const supervisorTabs = (hass: HomeAssistant): PageNavigation[] =>
|
||||
atLeastVersion(hass.config.version, 2022, 5)
|
||||
? []
|
||||
: [
|
||||
{
|
||||
translationKey: atLeastVersion(hass.config.version, 2021, 12)
|
||||
? "panel.addons"
|
||||
: "panel.dashboard",
|
||||
path: `/hassio/dashboard`,
|
||||
iconPath: atLeastVersion(hass.config.version, 2021, 12)
|
||||
? mdiPuzzle
|
||||
: mdiViewDashboard,
|
||||
},
|
||||
{
|
||||
translationKey: "panel.backups",
|
||||
path: `/hassio/backups`,
|
||||
iconPath: mdiBackupRestore,
|
||||
},
|
||||
{
|
||||
translationKey: "panel.system",
|
||||
path: `/hassio/system`,
|
||||
iconPath: mdiCogs,
|
||||
},
|
||||
];
|
||||
|
@@ -205,16 +205,6 @@ class HassioCoreInfo extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
--mdc-menu-min-width: 200px;
|
||||
}
|
||||
@media (min-width: 563px) {
|
||||
paper-listbox {
|
||||
max-height: 150px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
min-height: 35px;
|
||||
}
|
||||
mwc-list-item ha-svg-icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
@@ -186,7 +186,7 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
<ha-button-menu corner="BOTTOM_START">
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.menu")}
|
||||
.label=${this.supervisor.localize("common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
@@ -440,16 +440,6 @@ class HassioHostInfo extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
--mdc-menu-min-width: 200px;
|
||||
}
|
||||
@media (min-width: 563px) {
|
||||
paper-listbox {
|
||||
max-height: 150px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
min-height: 35px;
|
||||
}
|
||||
mwc-list-item ha-svg-icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
@@ -23,6 +23,10 @@ import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import {
|
||||
UNHEALTHY_REASON_URL,
|
||||
UNSUPPORTED_REASON_URL,
|
||||
} from "../../../src/panels/config/system-health/ha-config-system-health";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { bytesToString } from "../../../src/util/bytes-to-string";
|
||||
@@ -30,11 +34,6 @@ import { documentationUrl } from "../../../src/util/documentation-url";
|
||||
import "../components/supervisor-metric";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
const UNSUPPORTED_REASON_URL = {};
|
||||
const UNHEALTHY_REASON_URL = {
|
||||
privileged: "/more-info/unsupported/privileged",
|
||||
};
|
||||
|
||||
@customElement("hassio-supervisor-info")
|
||||
class HassioSupervisorInfo extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
@@ -1,19 +1,17 @@
|
||||
import "../../../src/components/ha-ansi-to-html";
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-alert";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-select";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import "../components/hassio-ansi-to-html";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
interface LogProvider {
|
||||
@@ -73,31 +71,26 @@ class HassioSupervisorLog extends LitElement {
|
||||
: ""}
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<paper-dropdown-menu
|
||||
<ha-select
|
||||
.label=${this.supervisor.localize("system.log.log_provider")}
|
||||
@iron-select=${this._setLogProvider}
|
||||
@selected=${this._setLogProvider}
|
||||
.value=${this._selectedLogProvider}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="provider"
|
||||
.selected=${this._selectedLogProvider}
|
||||
>
|
||||
${logProviders.map(
|
||||
(provider) => html`
|
||||
<paper-item provider=${provider.key}>
|
||||
${provider.name}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
${logProviders.map(
|
||||
(provider) => html`
|
||||
<mwc-list-item .value=${provider.key}>
|
||||
${provider.name}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<div class="card-content" id="content">
|
||||
${this._content
|
||||
? html`<hassio-ansi-to-html .content=${this._content}>
|
||||
</hassio-ansi-to-html>`
|
||||
? html`<ha-ansi-to-html .content=${this._content}>
|
||||
</ha-ansi-to-html>`
|
||||
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
@@ -110,7 +103,7 @@ class HassioSupervisorLog extends LitElement {
|
||||
}
|
||||
|
||||
private async _setLogProvider(ev): Promise<void> {
|
||||
const provider = ev.detail.item.getAttribute("provider");
|
||||
const provider = ev.target.value;
|
||||
this._selectedLogProvider = provider;
|
||||
this._loadData();
|
||||
}
|
||||
@@ -153,9 +146,9 @@ class HassioSupervisorLog extends LitElement {
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
paper-dropdown-menu {
|
||||
padding: 0 2%;
|
||||
width: 96%;
|
||||
ha-select {
|
||||
width: 100%;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -10,7 +10,6 @@ import {
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/common/search/search-input";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-alert";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
@@ -29,16 +28,16 @@ import {
|
||||
HassioAddonDetails,
|
||||
updateHassioAddon,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import {
|
||||
createHassioPartialBackup,
|
||||
HassioPartialBackupCreateParams,
|
||||
} from "../../../src/data/hassio/backup";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
ignoreSupervisorError,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { updateOS } from "../../../src/data/hassio/host";
|
||||
import { updateSupervisor } from "../../../src/data/hassio/supervisor";
|
||||
import { fetchHassioHassOsInfo, updateOS } from "../../../src/data/hassio/host";
|
||||
import {
|
||||
fetchHassioHomeAssistantInfo,
|
||||
fetchHassioSupervisorInfo,
|
||||
updateSupervisor,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import { updateCore } from "../../../src/data/supervisor/core";
|
||||
import { StoreAddon } from "../../../src/data/supervisor/store";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
@@ -46,7 +45,6 @@ import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../../../src/layouts/hass-subpage";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { SUPERVISOR_UPDATE_NAMES } from "../../../src/panels/config/dashboard/ha-config-updates";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { addonArchIsSupported, extractChangelog } from "../util/addon";
|
||||
|
||||
@@ -56,6 +54,12 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const SUPERVISOR_UPDATE_NAMES = {
|
||||
core: "Home Assistant Core",
|
||||
os: "Home Assistant Operating System",
|
||||
supervisor: "Home Assistant Supervisor",
|
||||
};
|
||||
|
||||
type updateType = "os" | "supervisor" | "core" | "addon";
|
||||
|
||||
const changelogUrl = (
|
||||
@@ -103,7 +107,7 @@ class UpdateAvailableCard extends LitElement {
|
||||
|
||||
@state() private _addonInfo?: HassioAddonDetails;
|
||||
|
||||
@state() private _action: "backup" | "update" | null = null;
|
||||
@state() private _updating = false;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@@ -138,7 +142,7 @@ class UpdateAvailableCard extends LitElement {
|
||||
name: this._name,
|
||||
})}
|
||||
</p>`
|
||||
: this._action === null
|
||||
: !this._updating
|
||||
? html`
|
||||
${this._changelogContent
|
||||
? html`
|
||||
@@ -172,18 +176,13 @@ class UpdateAvailableCard extends LitElement {
|
||||
: html`<ha-circular-progress alt="Updating" size="large" active>
|
||||
</ha-circular-progress>
|
||||
<p class="progress-text">
|
||||
${this._action === "update"
|
||||
? this.supervisor.localize("update_available.updating", {
|
||||
name: this._name,
|
||||
version: this._version_latest,
|
||||
})
|
||||
: this.supervisor.localize(
|
||||
"update_available.creating_backup",
|
||||
{ name: this._name }
|
||||
)}
|
||||
${this.supervisor.localize("update_available.updating", {
|
||||
name: this._name,
|
||||
version: this._version_latest,
|
||||
})}
|
||||
</p>`}
|
||||
</div>
|
||||
${this._version !== this._version_latest && this._action === null
|
||||
${this._version !== this._version_latest && !this._updating
|
||||
? html`
|
||||
<div class="card-actions">
|
||||
${changelog
|
||||
@@ -197,13 +196,7 @@ class UpdateAvailableCard extends LitElement {
|
||||
</a>`
|
||||
: ""}
|
||||
<span></span>
|
||||
<ha-progress-button
|
||||
.disabled=${!this._version ||
|
||||
(this._shouldCreateBackup &&
|
||||
this.supervisor.info?.state !== "running")}
|
||||
@click=${this._update}
|
||||
raised
|
||||
>
|
||||
<ha-progress-button @click=${this._update} raised>
|
||||
${this.supervisor.localize("common.update")}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
@@ -221,11 +214,22 @@ class UpdateAvailableCard extends LitElement {
|
||||
: "addon";
|
||||
this._updateType = updateType as updateType;
|
||||
|
||||
if (updateType === "addon") {
|
||||
if (!this.addonSlug) {
|
||||
this.addonSlug = pathPart;
|
||||
}
|
||||
this._loadAddonData();
|
||||
switch (updateType) {
|
||||
case "addon":
|
||||
if (!this.addonSlug) {
|
||||
this.addonSlug = pathPart;
|
||||
}
|
||||
this._loadAddonData();
|
||||
break;
|
||||
case "core":
|
||||
this._loadCoreData();
|
||||
break;
|
||||
case "supervisor":
|
||||
this._loadSupervisorData();
|
||||
break;
|
||||
case "os":
|
||||
this._loadOsData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,39 +321,60 @@ class UpdateAvailableCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadSupervisorData() {
|
||||
try {
|
||||
const supervisor = await fetchHassioSupervisorInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { supervisor });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this._updateType,
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadCoreData() {
|
||||
try {
|
||||
const core = await fetchHassioHomeAssistantInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { core });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this._updateType,
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadOsData() {
|
||||
try {
|
||||
const os = await fetchHassioHassOsInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { os });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this._updateType,
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _update() {
|
||||
this._error = undefined;
|
||||
if (this._shouldCreateBackup) {
|
||||
let backupArgs: HassioPartialBackupCreateParams;
|
||||
if (this._updateType === "addon") {
|
||||
backupArgs = {
|
||||
name: `addon_${this.addonSlug}_${this._version}`,
|
||||
addons: [this.addonSlug!],
|
||||
homeassistant: false,
|
||||
};
|
||||
} else {
|
||||
backupArgs = {
|
||||
name: `${this._updateType}_${this._version}`,
|
||||
folders: ["homeassistant"],
|
||||
homeassistant: true,
|
||||
};
|
||||
}
|
||||
this._action = "backup";
|
||||
try {
|
||||
await createHassioPartialBackup(this.hass, backupArgs);
|
||||
} catch (err: any) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
this._action = null;
|
||||
return;
|
||||
}
|
||||
if (this._shouldCreateBackup && this.supervisor.info.state === "freeze") {
|
||||
this._error = this.supervisor.localize("backup.backup_already_running");
|
||||
return;
|
||||
}
|
||||
|
||||
this._action = "update";
|
||||
this._error = undefined;
|
||||
this._updating = true;
|
||||
|
||||
try {
|
||||
if (this._updateType === "addon") {
|
||||
await updateHassioAddon(this.hass, this.addonSlug!);
|
||||
await updateHassioAddon(
|
||||
this.hass,
|
||||
this.addonSlug!,
|
||||
this._shouldCreateBackup
|
||||
);
|
||||
} else if (this._updateType === "core") {
|
||||
await updateCore(this.hass);
|
||||
await updateCore(this.hass, this._shouldCreateBackup);
|
||||
} else if (this._updateType === "os") {
|
||||
await updateOS(this.hass);
|
||||
} else if (this._updateType === "supervisor") {
|
||||
@@ -358,11 +383,12 @@ class UpdateAvailableCard extends LitElement {
|
||||
} catch (err: any) {
|
||||
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
this._action = null;
|
||||
this._updating = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
fireEvent(this, "update-complete");
|
||||
this._updating = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
82
package.json
82
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"description": "A frontend for Home Assistant using the Polymer framework",
|
||||
"description": "A frontend for Home Assistant",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/home-assistant/home-assistant-polymer"
|
||||
"url": "https://github.com/home-assistant/frontend"
|
||||
},
|
||||
"name": "home-assistant-frontend",
|
||||
"version": "1.0.0",
|
||||
@@ -22,17 +22,18 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^5.0.2",
|
||||
"@codemirror/commands": "^0.19.5",
|
||||
"@codemirror/gutter": "^0.19.4",
|
||||
"@codemirror/highlight": "^0.19.6",
|
||||
"@codemirror/history": "^0.19.0",
|
||||
"@codemirror/autocomplete": "^0.19.12",
|
||||
"@codemirror/commands": "^0.19.8",
|
||||
"@codemirror/gutter": "^0.19.9",
|
||||
"@codemirror/highlight": "^0.19.7",
|
||||
"@codemirror/history": "^0.19.2",
|
||||
"@codemirror/legacy-modes": "^0.19.0",
|
||||
"@codemirror/rectangular-selection": "^0.19.1",
|
||||
"@codemirror/search": "^0.19.2",
|
||||
"@codemirror/state": "^0.19.4",
|
||||
"@codemirror/stream-parser": "^0.19.2",
|
||||
"@codemirror/text": "^0.19.5",
|
||||
"@codemirror/view": "^0.19.15",
|
||||
"@codemirror/search": "^0.19.6",
|
||||
"@codemirror/state": "^0.19.6",
|
||||
"@codemirror/stream-parser": "^0.19.5",
|
||||
"@codemirror/text": "^0.19.6",
|
||||
"@codemirror/view": "^0.19.40",
|
||||
"@formatjs/intl-datetimeformat": "^4.2.5",
|
||||
"@formatjs/intl-getcanonicallocales": "^1.8.0",
|
||||
"@formatjs/intl-locale": "^2.4.40",
|
||||
@@ -45,7 +46,8 @@
|
||||
"@fullcalendar/daygrid": "5.9.0",
|
||||
"@fullcalendar/interaction": "5.9.0",
|
||||
"@fullcalendar/list": "5.9.0",
|
||||
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch",
|
||||
"@lit-labs/motion": "^1.0.2",
|
||||
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch",
|
||||
"@material/chips": "14.0.0-canary.261f2db59.0",
|
||||
"@material/data-table": "14.0.0-canary.261f2db59.0",
|
||||
"@material/mwc-button": "0.25.3",
|
||||
@@ -57,7 +59,7 @@
|
||||
"@material/mwc-formfield": "0.25.3",
|
||||
"@material/mwc-icon-button": "patch:@material/mwc-icon-button@0.25.3#./.yarn/patches/@material/mwc-icon-button/remove-icon.patch",
|
||||
"@material/mwc-linear-progress": "0.25.3",
|
||||
"@material/mwc-list": "0.25.3",
|
||||
"@material/mwc-list": "^0.25.3",
|
||||
"@material/mwc-menu": "0.25.3",
|
||||
"@material/mwc-radio": "0.25.3",
|
||||
"@material/mwc-ripple": "0.25.3",
|
||||
@@ -66,17 +68,17 @@
|
||||
"@material/mwc-switch": "0.25.3",
|
||||
"@material/mwc-tab": "0.25.3",
|
||||
"@material/mwc-tab-bar": "0.25.3",
|
||||
"@material/mwc-textarea": "^0.25.3",
|
||||
"@material/mwc-textfield": "0.25.3",
|
||||
"@material/mwc-top-app-bar-fixed": "^0.25.3",
|
||||
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
|
||||
"@mdi/js": "6.5.95",
|
||||
"@mdi/svg": "6.5.95",
|
||||
"@mdi/js": "6.6.95",
|
||||
"@mdi/svg": "6.6.95",
|
||||
"@polymer/app-layout": "^3.1.0",
|
||||
"@polymer/iron-flex-layout": "^3.0.1",
|
||||
"@polymer/iron-icon": "^3.0.1",
|
||||
"@polymer/iron-input": "^3.0.1",
|
||||
"@polymer/iron-resizable-behavior": "^3.0.1",
|
||||
"@polymer/paper-dropdown-menu": "^3.2.0",
|
||||
"@polymer/paper-input": "^3.2.1",
|
||||
"@polymer/paper-item": "^3.0.1",
|
||||
"@polymer/paper-listbox": "^3.0.1",
|
||||
@@ -87,13 +89,15 @@
|
||||
"@polymer/paper-tooltip": "^3.0.1",
|
||||
"@polymer/polymer": "3.4.1",
|
||||
"@thomasloven/round-slider": "0.5.4",
|
||||
"@vaadin/vaadin-combo-box": "^21.0.2",
|
||||
"@vaadin/vaadin-date-picker": "^21.0.2",
|
||||
"@vaadin/combo-box": "^22.0.4",
|
||||
"@vaadin/vaadin-themable-mixin": "^22.0.4",
|
||||
"@vibrant/color": "^3.2.1-alpha.1",
|
||||
"@vibrant/core": "^3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
|
||||
"@vue/web-component-wrapper": "^1.2.0",
|
||||
"@webcomponents/scoped-custom-element-registry": "^0.0.5",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||
"app-datepicker": "^5.0.1",
|
||||
"chart.js": "^3.3.2",
|
||||
"comlink": "^4.3.1",
|
||||
"core-js": "^3.15.2",
|
||||
@@ -102,17 +106,18 @@
|
||||
"deep-clone-simple": "^1.1.1",
|
||||
"deep-freeze": "^0.0.1",
|
||||
"fuse.js": "^6.0.0",
|
||||
"fuzzysort": "^1.2.1",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^1.0.11",
|
||||
"home-assistant-js-websocket": "^6.0.1",
|
||||
"hls.js": "^1.1.5",
|
||||
"home-assistant-js-websocket": "^7.0.3",
|
||||
"idb-keyval": "^5.1.3",
|
||||
"intl-messageformat": "^9.9.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"leaflet": "^1.7.1",
|
||||
"leaflet-draw": "^1.0.4",
|
||||
"lit": "^2.0.2",
|
||||
"lit-vaadin-helpers": "^0.2.1",
|
||||
"marked": "^3.0.2",
|
||||
"lit": "^2.1.2",
|
||||
"lit-vaadin-helpers": "^0.3.0",
|
||||
"marked": "^4.0.12",
|
||||
"memoize-one": "^5.2.1",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"proxy-polyfill": "^0.3.2",
|
||||
@@ -131,13 +136,12 @@
|
||||
"vis-network": "^8.5.4",
|
||||
"vue": "^2.6.12",
|
||||
"vue2-daterange-picker": "^0.5.1",
|
||||
"web-animations-js": "^2.3.2",
|
||||
"workbox-cacheable-response": "^6.1.5",
|
||||
"workbox-core": "^6.1.5",
|
||||
"workbox-expiration": "^6.1.5",
|
||||
"workbox-precaching": "^6.1.5",
|
||||
"workbox-routing": "^6.1.5",
|
||||
"workbox-strategies": "^6.1.5",
|
||||
"workbox-cacheable-response": "^6.4.2",
|
||||
"workbox-core": "^6.4.2",
|
||||
"workbox-expiration": "^6.4.2",
|
||||
"workbox-precaching": "^6.4.2",
|
||||
"workbox-routing": "^6.4.2",
|
||||
"workbox-strategies": "^6.4.2",
|
||||
"xss": "^1.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -166,8 +170,9 @@
|
||||
"@types/js-yaml": "^4",
|
||||
"@types/leaflet": "^1",
|
||||
"@types/leaflet-draw": "^1",
|
||||
"@types/marked": "^2",
|
||||
"@types/marked": "^4",
|
||||
"@types/mocha": "^8",
|
||||
"@types/qrcode": "^1.4.2",
|
||||
"@types/sortablejs": "^1",
|
||||
"@types/webspeechapi": "^0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "^4.32.0",
|
||||
@@ -192,7 +197,7 @@
|
||||
"fs-extra": "^7.0.1",
|
||||
"glob": "^7.2.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-foreach": "^0.1.0",
|
||||
"gulp-flatmap": "^1.0.2",
|
||||
"gulp-json-transform": "^0.4.6",
|
||||
"gulp-merge-json": "^1.3.1",
|
||||
"gulp-rename": "^2.0.0",
|
||||
@@ -229,16 +234,16 @@
|
||||
"webpack-dev-server": "^4.3.0",
|
||||
"webpack-manifest-plugin": "^4.0.2",
|
||||
"webpackbar": "^5.0.0-3",
|
||||
"workbox-build": "^6.1.5"
|
||||
"workbox-build": "^6.4.2"
|
||||
},
|
||||
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
||||
"resolutions": {
|
||||
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||
"lit": "^2.0.2",
|
||||
"lit-html": "2.0.1",
|
||||
"lit-element": "3.0.1",
|
||||
"@lit/reactive-element": "1.0.1"
|
||||
"lit": "^2.1.2",
|
||||
"lit-html": "2.1.2",
|
||||
"lit-element": "3.1.2",
|
||||
"@lit/reactive-element": "1.2.1"
|
||||
},
|
||||
"main": "src/home-assistant.js",
|
||||
"husky": {
|
||||
@@ -249,5 +254,6 @@
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "always"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@3.2.0"
|
||||
}
|
||||
|
@@ -2,6 +2,6 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def where():
|
||||
def where() -> Path:
|
||||
"""Return path to the frontend."""
|
||||
return Path(__file__).parent
|
||||
|
0
public/py.typed
Normal file
0
public/py.typed
Normal file
3
pyproject.toml
Normal file
3
pyproject.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[build-system]
|
||||
requires = ["setuptools~=60.5", "wheel~=0.37.1"]
|
||||
build-backend = "setuptools.build_meta"
|
20
script/core
20
script/core
@@ -4,6 +4,8 @@
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
WD="${WORKSPACE_DIRECTORY:=/workspaces/frontend}"
|
||||
|
||||
if [ -z "${DEVCONTAINER}" ]; then
|
||||
echo "This task should only run inside a devcontainer, for local install HA Core in a venv."
|
||||
exit 1
|
||||
@@ -13,12 +15,12 @@ if [ -z $(which hass) ]; then
|
||||
echo "Installing Home Asstant core from dev."
|
||||
python3 -m pip install --upgrade \
|
||||
colorlog \
|
||||
git+git://github.com/home-assistant/home-assistant.git@dev
|
||||
git+https://github.com/home-assistant/home-assistant.git@dev
|
||||
fi
|
||||
|
||||
if [ ! -d "/workspaces/frontend/config" ]; then
|
||||
if [ ! -d "${WD}/config" ]; then
|
||||
echo "Creating default configuration."
|
||||
mkdir -p "/workspaces/frontend/config";
|
||||
mkdir -p "${WD}/config";
|
||||
hass --script ensure_config -c config
|
||||
echo "demo:
|
||||
|
||||
@@ -26,24 +28,24 @@ logger:
|
||||
default: info
|
||||
logs:
|
||||
homeassistant.components.frontend: debug
|
||||
" >> /workspaces/frontend/config/configuration.yaml
|
||||
" >> "${WD}/config/configuration.yaml"
|
||||
|
||||
if [ ! -z "${HASSIO}" ]; then
|
||||
echo "
|
||||
# frontend:
|
||||
# development_repo: /workspaces/frontend
|
||||
# development_repo: ${WD}
|
||||
|
||||
hassio:
|
||||
development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
||||
development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
||||
else
|
||||
echo "
|
||||
frontend:
|
||||
development_repo: /workspaces/frontend
|
||||
development_repo: ${WD}
|
||||
|
||||
# hassio:
|
||||
# development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
||||
# development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
hass -c /workspaces/frontend/config
|
||||
hass -c "${WD}/config"
|
||||
|
@@ -11,6 +11,6 @@ yarn install
|
||||
|
||||
script/build_frontend
|
||||
|
||||
rm -rf dist
|
||||
python3 setup.py -q sdist
|
||||
python3 -m twine upload dist/* --skip-existing
|
||||
rm -rf dist home_assistant_frontend.egg-info
|
||||
python3 -m build
|
||||
python3 -m twine upload dist/*.whl --skip-existing
|
||||
|
@@ -50,14 +50,14 @@ async function main(args) {
|
||||
return;
|
||||
}
|
||||
|
||||
const setup = fs.readFileSync("setup.py", "utf8");
|
||||
const setup = fs.readFileSync("setup.cfg", "utf8");
|
||||
const version = setup.match(/\d{8}\.\d+/)[0];
|
||||
const newVersion = method(version);
|
||||
|
||||
console.log("Current version:", version);
|
||||
console.log("New version:", newVersion);
|
||||
|
||||
fs.writeFileSync("setup.py", setup.replace(version, newVersion), "utf-8");
|
||||
fs.writeFileSync("setup.cfg", setup.replace(version, newVersion), "utf-8");
|
||||
|
||||
if (!commit) {
|
||||
return;
|
||||
|
26
setup.cfg
Normal file
26
setup.cfg
Normal file
@@ -0,0 +1,26 @@
|
||||
[metadata]
|
||||
name = home-assistant-frontend
|
||||
version = 20220427.0
|
||||
author = The Home Assistant Authors
|
||||
author_email = hello@home-assistant.io
|
||||
license = Apache-2.0
|
||||
platforms = any
|
||||
description = The Home Assistant frontend
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://github.com/home-assistant/frontend
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
zip_safe = False
|
||||
include_package_data = True
|
||||
python_requires = >= 3.4.0
|
||||
|
||||
[options.packages.find]
|
||||
include =
|
||||
hass_frontend*
|
||||
|
||||
[mypy]
|
||||
python_version = 3.4
|
||||
show_error_codes = True
|
||||
strict = True
|
14
setup.py
14
setup.py
@@ -1,14 +0,0 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20220118.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/frontend",
|
||||
author="The Home Assistant Authors",
|
||||
author_email="hello@home-assistant.io",
|
||||
license="Apache-2.0",
|
||||
packages=find_packages(include=["hass_frontend", "hass_frontend.*"]),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
)
|
@@ -101,13 +101,19 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
this._fetchAuthProviders();
|
||||
|
||||
if (matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
applyThemesOnElement(document.documentElement, {
|
||||
default_theme: "default",
|
||||
default_dark_theme: null,
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
});
|
||||
applyThemesOnElement(
|
||||
document.documentElement,
|
||||
{
|
||||
default_theme: "default",
|
||||
default_dark_theme: null,
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.redirectUri) {
|
||||
|
@@ -184,8 +184,10 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"person",
|
||||
"remote",
|
||||
"script",
|
||||
"scene",
|
||||
"sun",
|
||||
"timer",
|
||||
"update",
|
||||
"vacuum",
|
||||
"water_heater",
|
||||
"weather",
|
||||
@@ -199,6 +201,7 @@ export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
|
||||
"input_text",
|
||||
"number",
|
||||
"scene",
|
||||
"update",
|
||||
"select",
|
||||
];
|
||||
|
||||
@@ -234,7 +237,7 @@ export const DOMAINS_INPUT_ROW = [
|
||||
];
|
||||
|
||||
/** Domains that should have the history hidden in the more info dialog. */
|
||||
export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator", "scene"];
|
||||
export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator"];
|
||||
|
||||
/** States that we consider "off". */
|
||||
export const STATES_OFF = ["closed", "locked", "off"];
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { HaDurationData } from "../../components/ha-duration-input";
|
||||
import { ForDict } from "../../data/automation";
|
||||
import type { HaDurationData } from "../../components/ha-duration-input";
|
||||
import type { ForDict } from "../../data/automation";
|
||||
|
||||
export const createDurationData = (
|
||||
duration: string | number | ForDict | undefined
|
||||
): HaDurationData => {
|
||||
): HaDurationData | undefined => {
|
||||
if (duration === undefined) {
|
||||
return {};
|
||||
return undefined;
|
||||
}
|
||||
if (typeof duration !== "object") {
|
||||
if (typeof duration === "string" || isNaN(duration)) {
|
||||
@@ -19,6 +19,9 @@ export const createDurationData = (
|
||||
}
|
||||
return { seconds: duration };
|
||||
}
|
||||
if (!("days" in duration)) {
|
||||
return duration;
|
||||
}
|
||||
const { days, minutes, seconds, milliseconds } = duration;
|
||||
let hours = duration.hours || 0;
|
||||
hours = (hours || 0) + (days || 0) * 24;
|
||||
|
16
src/common/datetime/duration.ts
Normal file
16
src/common/datetime/duration.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import secondsToDuration from "./seconds_to_duration";
|
||||
|
||||
const DAY_IN_SECONDS = 86400;
|
||||
const HOUR_IN_SECONDS = 3600;
|
||||
const MINUTE_IN_SECONDS = 60;
|
||||
|
||||
export const UNIT_TO_SECOND_CONVERT = {
|
||||
s: 1,
|
||||
min: MINUTE_IN_SECONDS,
|
||||
h: HOUR_IN_SECONDS,
|
||||
d: DAY_IN_SECONDS,
|
||||
};
|
||||
|
||||
export const formatDuration = (duration: string, units: string): string =>
|
||||
secondsToDuration(parseFloat(duration) * UNIT_TO_SECOND_CONVERT[units]) ||
|
||||
"0";
|
@@ -13,14 +13,19 @@ export const formatDateTime = (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
|
||||
const formatDateTimeMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
new Intl.DateTimeFormat(
|
||||
locale.language === "en" && !useAmPm(locale)
|
||||
? "en-u-hc-h23"
|
||||
: locale.language,
|
||||
{
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// August 9, 2021, 8:23:15 AM
|
||||
@@ -31,15 +36,20 @@ export const formatDateTimeWithSeconds = (
|
||||
|
||||
const formatDateTimeWithSecondsMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
new Intl.DateTimeFormat(
|
||||
locale.language === "en" && !useAmPm(locale)
|
||||
? "en-u-hc-h23"
|
||||
: locale.language,
|
||||
{
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// 9/8/2021, 8:23 AM
|
||||
@@ -50,12 +60,17 @@ export const formatDateTimeNumeric = (
|
||||
|
||||
const formatDateTimeNumericMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
new Intl.DateTimeFormat(
|
||||
locale.language === "en" && !useAmPm(locale)
|
||||
? "en-u-hc-h23"
|
||||
: locale.language,
|
||||
{
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@@ -13,11 +13,16 @@ export const formatTime = (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
|
||||
const formatTimeMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
new Intl.DateTimeFormat(
|
||||
locale.language === "en" && !useAmPm(locale)
|
||||
? "en-u-hc-h23"
|
||||
: locale.language,
|
||||
{
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// 9:15:24 PM || 21:15:24
|
||||
@@ -28,12 +33,17 @@ export const formatTimeWithSeconds = (
|
||||
|
||||
const formatTimeWithSecondsMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
new Intl.DateTimeFormat(
|
||||
locale.language === "en" && !useAmPm(locale)
|
||||
? "en-u-hc-h23"
|
||||
: locale.language,
|
||||
{
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Tuesday 7:00 PM || Tuesday 19:00
|
||||
@@ -42,10 +52,15 @@ export const formatTimeWeekday = (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
|
||||
const formatTimeWeekdayMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
new Intl.DateTimeFormat(
|
||||
locale.language === "en" && !useAmPm(locale)
|
||||
? "en-u-hc-h23"
|
||||
: locale.language,
|
||||
{
|
||||
weekday: "long",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@@ -5,7 +5,10 @@ import type { ClassElement } from "../../types";
|
||||
type Callback = (oldValue: any, newValue: any) => void;
|
||||
|
||||
class Storage {
|
||||
constructor() {
|
||||
constructor(subscribe = true) {
|
||||
if (!subscribe) {
|
||||
return;
|
||||
}
|
||||
window.addEventListener("storage", (ev: StorageEvent) => {
|
||||
if (ev.key && this.hasKey(ev.key)) {
|
||||
this._storage[ev.key] = ev.newValue
|
||||
@@ -80,15 +83,18 @@ class Storage {
|
||||
}
|
||||
}
|
||||
|
||||
const storage = new Storage();
|
||||
const subscribeStorage = new Storage();
|
||||
|
||||
export const LocalStorage =
|
||||
(
|
||||
storageKey?: string,
|
||||
property?: boolean,
|
||||
subscribe = true,
|
||||
propertyOptions?: PropertyDeclaration
|
||||
): any =>
|
||||
(clsElement: ClassElement) => {
|
||||
const storage = subscribe ? subscribeStorage : new Storage(false);
|
||||
|
||||
const key = String(clsElement.key);
|
||||
storageKey = storageKey || String(clsElement.key);
|
||||
const initVal = clsElement.initializer
|
||||
@@ -97,7 +103,7 @@ export const LocalStorage =
|
||||
|
||||
storage.addFromStorage(storageKey);
|
||||
|
||||
const subscribe = (el: ReactiveElement): UnsubscribeFunc =>
|
||||
const subscribeChanges = (el: ReactiveElement): UnsubscribeFunc =>
|
||||
storage.subscribeChanges(storageKey!, (oldValue) => {
|
||||
el.requestUpdate(clsElement.key, oldValue);
|
||||
});
|
||||
@@ -131,17 +137,19 @@ export const LocalStorage =
|
||||
configurable: true,
|
||||
},
|
||||
finisher(cls: typeof ReactiveElement) {
|
||||
if (property) {
|
||||
if (property && subscribe) {
|
||||
const connectedCallback = cls.prototype.connectedCallback;
|
||||
const disconnectedCallback = cls.prototype.disconnectedCallback;
|
||||
cls.prototype.connectedCallback = function () {
|
||||
connectedCallback.call(this);
|
||||
this[`__unbsubLocalStorage${key}`] = subscribe(this);
|
||||
this[`__unbsubLocalStorage${key}`] = subscribeChanges(this);
|
||||
};
|
||||
cls.prototype.disconnectedCallback = function () {
|
||||
disconnectedCallback.call(this);
|
||||
this[`__unbsubLocalStorage${key}`]();
|
||||
};
|
||||
}
|
||||
if (property) {
|
||||
cls.createProperty(clsElement.key, {
|
||||
noAccessor: true,
|
||||
...propertyOptions,
|
||||
|
@@ -31,11 +31,12 @@ export const applyThemesOnElement = (
|
||||
element,
|
||||
themes: HomeAssistant["themes"],
|
||||
selectedTheme?: string,
|
||||
themeSettings?: Partial<HomeAssistant["selectedTheme"]>
|
||||
themeSettings?: Partial<HomeAssistant["selectedTheme"]>,
|
||||
main?: boolean
|
||||
) => {
|
||||
// If there is no explicitly desired theme provided, we automatically
|
||||
// If there is no explicitly desired theme provided, and the element is the main element we automatically
|
||||
// use the active one from `themes`.
|
||||
const themeToApply = selectedTheme || themes.theme;
|
||||
const themeToApply = selectedTheme || (main ? themes.theme : undefined);
|
||||
|
||||
// If there is no explicitly desired dark mode provided, we automatically
|
||||
// use the active one from `themes`.
|
||||
@@ -47,7 +48,7 @@ export const applyThemesOnElement = (
|
||||
let cacheKey = themeToApply;
|
||||
let themeRules: Partial<ThemeVars> = {};
|
||||
|
||||
if (darkMode) {
|
||||
if (themeToApply && darkMode) {
|
||||
cacheKey = `${cacheKey}__dark`;
|
||||
themeRules = { ...darkStyles };
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ export const isNavigationClick = (e: MouseEvent) => {
|
||||
|
||||
const anchor = e
|
||||
.composedPath()
|
||||
.filter((n) => (n as HTMLElement).tagName === "A")[0] as
|
||||
.find((n) => (n as HTMLElement).tagName === "A") as
|
||||
| HTMLAnchorElement
|
||||
| undefined;
|
||||
if (
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user