mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-12 18:59:26 +00:00
Compare commits
614 Commits
fix-entity
...
20221213.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d8b4611c24 | ||
![]() |
9c27bb37a0 | ||
![]() |
f2b7288e92 | ||
![]() |
f7aecb0d6d | ||
![]() |
1da8a974f8 | ||
![]() |
b82d6fd35f | ||
![]() |
62bc171b8c | ||
![]() |
2d7973af79 | ||
![]() |
d64bb98bb4 | ||
![]() |
5cabf1d041 | ||
![]() |
180357e0db | ||
![]() |
c1dba217da | ||
![]() |
8d1ecdb27e | ||
![]() |
f83544dd38 | ||
![]() |
2ae137bbc2 | ||
![]() |
9f8aa0b4bf | ||
![]() |
149fa5fca6 | ||
![]() |
0fb35fd0d0 | ||
![]() |
544272b4df | ||
![]() |
b939978de1 | ||
![]() |
d74fb8717c | ||
![]() |
f204a163d4 | ||
![]() |
bc9fb9a472 | ||
![]() |
1e654d9661 | ||
![]() |
03d33759b8 | ||
![]() |
ef7d696f9d | ||
![]() |
545141da92 | ||
![]() |
467957005d | ||
![]() |
348c3c9787 | ||
![]() |
fe0492c2e0 | ||
![]() |
0b377c060c | ||
![]() |
0f971e5868 | ||
![]() |
1dbe8c9b64 | ||
![]() |
6e4a6cb0db | ||
![]() |
3f1903bd87 | ||
![]() |
9643e0268f | ||
![]() |
23573d8c26 | ||
![]() |
3c8c1260b1 | ||
![]() |
b84de87688 | ||
![]() |
2d30994e56 | ||
![]() |
4ea19a6041 | ||
![]() |
af82c0d0c4 | ||
![]() |
2b5ef1207c | ||
![]() |
feddbdba96 | ||
![]() |
b581b95d9d | ||
![]() |
8c69c772d1 | ||
![]() |
ba09120ff3 | ||
![]() |
a8d242719f | ||
![]() |
0d0028fced | ||
![]() |
d1b1eecd92 | ||
![]() |
3b51050b52 | ||
![]() |
34e1d745be | ||
![]() |
efda2ab626 | ||
![]() |
ae354fa8da | ||
![]() |
ef130be352 | ||
![]() |
f1393e5f00 | ||
![]() |
dac784553e | ||
![]() |
d2ad67384f | ||
![]() |
69d16ab9b4 | ||
![]() |
a941ed4f90 | ||
![]() |
774259224b | ||
![]() |
8a5f781ca2 | ||
![]() |
dacef605c7 | ||
![]() |
2c279e2dec | ||
![]() |
06ee08db36 | ||
![]() |
34dfaa5a0f | ||
![]() |
b06db26540 | ||
![]() |
076a6c4459 | ||
![]() |
0001cad423 | ||
![]() |
d622d9d420 | ||
![]() |
228fd4b7fe | ||
![]() |
e6f2e8058b | ||
![]() |
83ba594cc4 | ||
![]() |
ff64dc2631 | ||
![]() |
745753c526 | ||
![]() |
f5385ba277 | ||
![]() |
1a69503eab | ||
![]() |
8ba46f7f57 | ||
![]() |
bbdb84482a | ||
![]() |
08ffe375b9 | ||
![]() |
8699f3e3a8 | ||
![]() |
ecdd07ff4d | ||
![]() |
f0f699a37e | ||
![]() |
75b1b1c9a0 | ||
![]() |
0ae8246d8a | ||
![]() |
b644407260 | ||
![]() |
b389127f78 | ||
![]() |
20dff9d25d | ||
![]() |
076ddb71b6 | ||
![]() |
f0127511b0 | ||
![]() |
07ad429f8c | ||
![]() |
ef3caf91f1 | ||
![]() |
4fcfcbeefb | ||
![]() |
f18997c7c3 | ||
![]() |
2ed8a4053b | ||
![]() |
1105c92569 | ||
![]() |
76a682fa28 | ||
![]() |
d059b97a2f | ||
![]() |
96080f3c78 | ||
![]() |
00274ebf66 | ||
![]() |
7e58bd59c3 | ||
![]() |
04ef783f5b | ||
![]() |
340449d064 | ||
![]() |
24f2ad8be9 | ||
![]() |
3eac53e209 | ||
![]() |
65cef9d996 | ||
![]() |
4c5f4508b2 | ||
![]() |
1aa6bd5577 | ||
![]() |
3dfd401036 | ||
![]() |
71a5d8c6f9 | ||
![]() |
a0bf582cc9 | ||
![]() |
77a53ffc6c | ||
![]() |
b6f1d78b7f | ||
![]() |
1fda303b23 | ||
![]() |
241645fe8d | ||
![]() |
faa57e4c02 | ||
![]() |
915563ce6c | ||
![]() |
44502d2c8d | ||
![]() |
b97a9ef311 | ||
![]() |
1a66b8a374 | ||
![]() |
4190ff5a2b | ||
![]() |
dfc461ce05 | ||
![]() |
dff7f653b1 | ||
![]() |
eccc6a8cdb | ||
![]() |
52235c6187 | ||
![]() |
594b402bd5 | ||
![]() |
684f6db4df | ||
![]() |
883e5e3a6c | ||
![]() |
29452841c2 | ||
![]() |
9b6e33cfec | ||
![]() |
e43f3b193e | ||
![]() |
40d0455936 | ||
![]() |
90a7c2d2ff | ||
![]() |
d4cda0c106 | ||
![]() |
92d022747b | ||
![]() |
ee6f97b802 | ||
![]() |
cb97918005 | ||
![]() |
f1f0baf787 | ||
![]() |
c0240eed67 | ||
![]() |
aeeacc6cad | ||
![]() |
fcfdad3d94 | ||
![]() |
566b93ec1f | ||
![]() |
5c452cb9e0 | ||
![]() |
06c1d9f6ef | ||
![]() |
bfd96944f9 | ||
![]() |
c119163422 | ||
![]() |
4846fa1a74 | ||
![]() |
aec0eb3c78 | ||
![]() |
92e7254c54 | ||
![]() |
bafe581562 | ||
![]() |
e6a153a802 | ||
![]() |
fce87ff0fe | ||
![]() |
e6b3475b5b | ||
![]() |
f969299567 | ||
![]() |
7a87dc4d8a | ||
![]() |
d6fa1427f1 | ||
![]() |
aa1e9cedca | ||
![]() |
147b1f34ac | ||
![]() |
0cfba81eae | ||
![]() |
a9d44fcb61 | ||
![]() |
0aa2c9044a | ||
![]() |
9bae4a646d | ||
![]() |
15a0847db8 | ||
![]() |
8b817b35b0 | ||
![]() |
f95a3c75f6 | ||
![]() |
2223ffd7ee | ||
![]() |
590ad5b8a0 | ||
![]() |
2bb9961fc2 | ||
![]() |
3e2fb09251 | ||
![]() |
fc80daa3e0 | ||
![]() |
2aa7b95a5a | ||
![]() |
c2436fb157 | ||
![]() |
a958c6296b | ||
![]() |
fe4191aea9 | ||
![]() |
49d9cf41fe | ||
![]() |
0bfb2b4a56 | ||
![]() |
1a68a2f4d7 | ||
![]() |
1197e5a35b | ||
![]() |
c6386284d1 | ||
![]() |
185d2f1d52 | ||
![]() |
c5ec1797f6 | ||
![]() |
048b345c75 | ||
![]() |
70868da305 | ||
![]() |
cef3dbfdf0 | ||
![]() |
bda5b97c91 | ||
![]() |
ae15dd678b | ||
![]() |
681c745e84 | ||
![]() |
23b59978ab | ||
![]() |
90b9eaeb19 | ||
![]() |
65426bd2d0 | ||
![]() |
1cb44ce857 | ||
![]() |
1c03dc9b77 | ||
![]() |
238e844068 | ||
![]() |
ec6a4b4e7a | ||
![]() |
ac65882fdd | ||
![]() |
c92e6423e8 | ||
![]() |
db0d24c807 | ||
![]() |
9e56ddcc69 | ||
![]() |
dd4c3c28ee | ||
![]() |
245202c125 | ||
![]() |
8b8a85b4b8 | ||
![]() |
0aae285236 | ||
![]() |
31ac274a51 | ||
![]() |
6f07e7ca59 | ||
![]() |
fa506202ac | ||
![]() |
c810c67a53 | ||
![]() |
663c58512d | ||
![]() |
3cd64675df | ||
![]() |
79c8b7dc27 | ||
![]() |
98a32041d4 | ||
![]() |
ffbcb0a343 | ||
![]() |
ab4dd47e51 | ||
![]() |
c7cb8cf762 | ||
![]() |
a5ab4eaf0e | ||
![]() |
d52dbde909 | ||
![]() |
1cde9e882e | ||
![]() |
8cb0d38d78 | ||
![]() |
9cb168c439 | ||
![]() |
2add29c4eb | ||
![]() |
e2104c1591 | ||
![]() |
17ac81a708 | ||
![]() |
42386c7dee | ||
![]() |
2e988bf5c3 | ||
![]() |
3356d559c9 | ||
![]() |
43755deb39 | ||
![]() |
9778c0731c | ||
![]() |
ebc0edac10 | ||
![]() |
effc9467c2 | ||
![]() |
68e94d7222 | ||
![]() |
c4992c477b | ||
![]() |
449c1f2469 | ||
![]() |
d52e521ef8 | ||
![]() |
03d03f9903 | ||
![]() |
1122698351 | ||
![]() |
9d730919d5 | ||
![]() |
6326bb010f | ||
![]() |
2ab5da6d84 | ||
![]() |
a56b2e3270 | ||
![]() |
523d936010 | ||
![]() |
b3e2beac5a | ||
![]() |
4c8e863c0e | ||
![]() |
69074df1ab | ||
![]() |
16848d03ae | ||
![]() |
dd9683674d | ||
![]() |
822917d060 | ||
![]() |
7cc6809f53 | ||
![]() |
57291183ca | ||
![]() |
504e8dd946 | ||
![]() |
5c4517517d | ||
![]() |
1b917a5b04 | ||
![]() |
527c4f71c2 | ||
![]() |
3ac6e6f307 | ||
![]() |
9e955dbaaa | ||
![]() |
e0a56956e0 | ||
![]() |
66ed1b18be | ||
![]() |
d445bf2505 | ||
![]() |
16bd1f5883 | ||
![]() |
c12e6662dd | ||
![]() |
0b18875d70 | ||
![]() |
57fb8f9f01 | ||
![]() |
f1139e09f9 | ||
![]() |
51febc2218 | ||
![]() |
c8d16af1b5 | ||
![]() |
66a75c4714 | ||
![]() |
cb8e602340 | ||
![]() |
de008f65a3 | ||
![]() |
ab1b778439 | ||
![]() |
62ac9155fc | ||
![]() |
68302d0896 | ||
![]() |
a76f456ebc | ||
![]() |
9d3eaba46b | ||
![]() |
5bb9538861 | ||
![]() |
fe1beb0d59 | ||
![]() |
153161d2cb | ||
![]() |
370864e0ed | ||
![]() |
9b6fca2c0e | ||
![]() |
55467666f7 | ||
![]() |
928f20ada5 | ||
![]() |
b53e86ad03 | ||
![]() |
112ec10b30 | ||
![]() |
1b4989a7dc | ||
![]() |
1f9763d6c8 | ||
![]() |
b495667e8d | ||
![]() |
a46e72ffbd | ||
![]() |
0a2eb05062 | ||
![]() |
c9b5fe9a85 | ||
![]() |
58d5a07a43 | ||
![]() |
c44de09a7c | ||
![]() |
a0b645d1b9 | ||
![]() |
0b6c6b2b98 | ||
![]() |
bad3edc340 | ||
![]() |
d3015c362d | ||
![]() |
fbb8ff4362 | ||
![]() |
6393d59035 | ||
![]() |
62de708b2b | ||
![]() |
6c4c65730c | ||
![]() |
23f8373b16 | ||
![]() |
dec8883f2a | ||
![]() |
a475b06d49 | ||
![]() |
0972cb4583 | ||
![]() |
dad7c43fd2 | ||
![]() |
7e6a9f1653 | ||
![]() |
f627e98902 | ||
![]() |
0d623794ed | ||
![]() |
0a3fa3e218 | ||
![]() |
19887fbd54 | ||
![]() |
fe9967550b | ||
![]() |
9b19b6f203 | ||
![]() |
797718f478 | ||
![]() |
9ea0e3a75f | ||
![]() |
0b76b60f6e | ||
![]() |
d8be662bd6 | ||
![]() |
c478a15846 | ||
![]() |
811208363b | ||
![]() |
969772663b | ||
![]() |
c3b9438b3b | ||
![]() |
9b51df02d6 | ||
![]() |
8a4b0b081a | ||
![]() |
1ecc88291d | ||
![]() |
fb80da013e | ||
![]() |
a4fcb743fa | ||
![]() |
8444fe0a07 | ||
![]() |
1442f6d546 | ||
![]() |
c468fba36f | ||
![]() |
2afbfb01bd | ||
![]() |
907466d060 | ||
![]() |
4deee46864 | ||
![]() |
391cc95883 | ||
![]() |
0c800344d2 | ||
![]() |
08279f35cf | ||
![]() |
05f2ef8a37 | ||
![]() |
3a41b4e65b | ||
![]() |
e08c12c4dd | ||
![]() |
bb0884c4bb | ||
![]() |
45646eaf0b | ||
![]() |
d735b2b722 | ||
![]() |
310b110b8b | ||
![]() |
06557709ae | ||
![]() |
1b5571557c | ||
![]() |
712ecbd13c | ||
![]() |
ea286b6fac | ||
![]() |
a2953138f4 | ||
![]() |
b327d6e0bc | ||
![]() |
88983806ae | ||
![]() |
1ac701d1c8 | ||
![]() |
aaeca323d4 | ||
![]() |
83a4fd6c1b | ||
![]() |
ef9643ddaf | ||
![]() |
af93ec1b92 | ||
![]() |
4dce9404a4 | ||
![]() |
ed54c70e75 | ||
![]() |
114507b5cb | ||
![]() |
8eddaa1914 | ||
![]() |
37394f7bc5 | ||
![]() |
d5cdd53fab | ||
![]() |
0ac2393ecb | ||
![]() |
c38892a162 | ||
![]() |
c77e5dee84 | ||
![]() |
3752336a9a | ||
![]() |
70d4fe1285 | ||
![]() |
d3738adf11 | ||
![]() |
7ededd2766 | ||
![]() |
ed8b07b7e2 | ||
![]() |
c12189b27f | ||
![]() |
9c923e45c5 | ||
![]() |
66bfdb6d12 | ||
![]() |
aa673774a8 | ||
![]() |
8c7974e466 | ||
![]() |
a70e2342a2 | ||
![]() |
b5c9aae1aa | ||
![]() |
93893d0237 | ||
![]() |
ad3dcb355f | ||
![]() |
952b433b2c | ||
![]() |
200fff506c | ||
![]() |
e84b9b7c6f | ||
![]() |
e2a89b1157 | ||
![]() |
cb0310593c | ||
![]() |
c14d9ab957 | ||
![]() |
dd695545d3 | ||
![]() |
176d8567f4 | ||
![]() |
4f4a95c04e | ||
![]() |
6393944a1b | ||
![]() |
f768c5ef7f | ||
![]() |
650d579d05 | ||
![]() |
9811f2681c | ||
![]() |
6a3ac9116e | ||
![]() |
2a6ef9b955 | ||
![]() |
b9395e1c97 | ||
![]() |
cd8c1f42ca | ||
![]() |
0ec887ad50 | ||
![]() |
f8a7737eb9 | ||
![]() |
3e01597a38 | ||
![]() |
6d230ebd65 | ||
![]() |
9035e8e9dc | ||
![]() |
7ff138534f | ||
![]() |
0b76183acd | ||
![]() |
9f658c10c3 | ||
![]() |
72ed6fdd6b | ||
![]() |
3bdc5ad420 | ||
![]() |
8d2f7d99af | ||
![]() |
b88317f1d3 | ||
![]() |
8ccd0426dd | ||
![]() |
c17e8ba65a | ||
![]() |
d9f1540115 | ||
![]() |
1b480248d1 | ||
![]() |
e88bb1114b | ||
![]() |
80e868e281 | ||
![]() |
d8a49c6eec | ||
![]() |
594ee85bbe | ||
![]() |
182b8f809c | ||
![]() |
8e4bebb694 | ||
![]() |
dddb922593 | ||
![]() |
da38cbccf1 | ||
![]() |
71c43058ea | ||
![]() |
f9d119d33d | ||
![]() |
d3b97ae91c | ||
![]() |
5146fa1d9e | ||
![]() |
fc86a66c33 | ||
![]() |
3959a7475c | ||
![]() |
61d09072a7 | ||
![]() |
4a07d3d39b | ||
![]() |
be30cdb51f | ||
![]() |
894258d7b8 | ||
![]() |
296d5f8ffe | ||
![]() |
d1c2020ee4 | ||
![]() |
3c1b2aa4f3 | ||
![]() |
17a11809de | ||
![]() |
3083d5b04c | ||
![]() |
0848c096b9 | ||
![]() |
8d5c36a96a | ||
![]() |
cc76a6c5ed | ||
![]() |
01fd2787be | ||
![]() |
c79955e76a | ||
![]() |
51874329d1 | ||
![]() |
6252955bb5 | ||
![]() |
5422fda990 | ||
![]() |
db8bc9d34a | ||
![]() |
9f19bdde65 | ||
![]() |
7336c1280f | ||
![]() |
8e245c8a83 | ||
![]() |
5a150ac80d | ||
![]() |
eac13980ff | ||
![]() |
977fdd9fbb | ||
![]() |
cedde3d6a2 | ||
![]() |
56c78ae108 | ||
![]() |
82a641a200 | ||
![]() |
04181e9c28 | ||
![]() |
4b8960c236 | ||
![]() |
fc104e7280 | ||
![]() |
8c125f4dee | ||
![]() |
b93f457d53 | ||
![]() |
e8ce6ad919 | ||
![]() |
0ce695577c | ||
![]() |
50b67751d9 | ||
![]() |
05515f21c3 | ||
![]() |
063c377797 | ||
![]() |
aee11da671 | ||
![]() |
5fcb219fcd | ||
![]() |
a97dfbb51f | ||
![]() |
c5f4e8ffdd | ||
![]() |
7ffd30643a | ||
![]() |
dcfcd54f10 | ||
![]() |
3b103619ec | ||
![]() |
614c1574ca | ||
![]() |
e1e3f9d925 | ||
![]() |
0ba4a07b92 | ||
![]() |
5a5f31b32c | ||
![]() |
ff92768973 | ||
![]() |
bb0529ecd2 | ||
![]() |
bc62e9372b | ||
![]() |
087a897cbe | ||
![]() |
589efa8cc5 | ||
![]() |
c93179c307 | ||
![]() |
9e416e829c | ||
![]() |
e13c632afa | ||
![]() |
544c8fe3bb | ||
![]() |
81b21f874b | ||
![]() |
ea319d55ef | ||
![]() |
8c03bbdccc | ||
![]() |
321914d53a | ||
![]() |
fe46f759c9 | ||
![]() |
490d46396e | ||
![]() |
7ec28c4314 | ||
![]() |
e9f4307d15 | ||
![]() |
3c62bc9b18 | ||
![]() |
28bae5071c | ||
![]() |
e9281ad9f1 | ||
![]() |
78187c5b0b | ||
![]() |
f164ad0b89 | ||
![]() |
48b10005e3 | ||
![]() |
b3d64fc52a | ||
![]() |
23e5a47b3b | ||
![]() |
55d84973c6 | ||
![]() |
de90a62de7 | ||
![]() |
3a17f2d73e | ||
![]() |
8f6a09f44c | ||
![]() |
d78191efa6 | ||
![]() |
749d869e03 | ||
![]() |
5d6f446c30 | ||
![]() |
3cf14bb2e1 | ||
![]() |
f7253a73a5 | ||
![]() |
cfabaa8716 | ||
![]() |
032f497687 | ||
![]() |
432483b3d2 | ||
![]() |
e95d5b1afb | ||
![]() |
330f3e5ce4 | ||
![]() |
fee6ae3045 | ||
![]() |
5e431a07ad | ||
![]() |
5d4c090b26 | ||
![]() |
b84240edbc | ||
![]() |
f4dc74b2e8 | ||
![]() |
c95d19299b | ||
![]() |
7696df56ac | ||
![]() |
771733d326 | ||
![]() |
4f3c708109 | ||
![]() |
d2078a7e50 | ||
![]() |
d70cb24722 | ||
![]() |
6902537666 | ||
![]() |
9ea1f61971 | ||
![]() |
782c95cf04 | ||
![]() |
d5d6216cfe | ||
![]() |
1086c85964 | ||
![]() |
47c0901df2 | ||
![]() |
d323ab6726 | ||
![]() |
07b5856190 | ||
![]() |
462dee0351 | ||
![]() |
f181a085de | ||
![]() |
bf5589b88d | ||
![]() |
c5428d8581 | ||
![]() |
fd431f36f7 | ||
![]() |
7acf3a049e | ||
![]() |
cfb0e8b39e | ||
![]() |
a3abbf3812 | ||
![]() |
980156d23a | ||
![]() |
37c2a3636e | ||
![]() |
b682d13486 | ||
![]() |
7f82b90c25 | ||
![]() |
3e9d6ea2c5 | ||
![]() |
57c5c1c191 | ||
![]() |
b553a3fd92 | ||
![]() |
d1964e92ea | ||
![]() |
a889969bb8 | ||
![]() |
f80b2c578b | ||
![]() |
579f73e08f | ||
![]() |
d13c6d3e7b | ||
![]() |
e78c875e8e | ||
![]() |
5e8c54b00f | ||
![]() |
71bc74893f | ||
![]() |
df72e5099e | ||
![]() |
e6862daa38 | ||
![]() |
66db8c999f | ||
![]() |
00bc315fc1 | ||
![]() |
f461825a59 | ||
![]() |
8c98326e31 | ||
![]() |
7bb619b0c3 | ||
![]() |
07bc9081b8 | ||
![]() |
91fa5972d1 | ||
![]() |
abbfde19a2 | ||
![]() |
ccd617d68e | ||
![]() |
8a7f35cee6 | ||
![]() |
e510e5b371 | ||
![]() |
aa6ee0f6d2 | ||
![]() |
f3449c4d9b | ||
![]() |
3c289deb21 | ||
![]() |
847d163cc8 | ||
![]() |
28d11703fc | ||
![]() |
1f003ae3be | ||
![]() |
a57d7813e7 | ||
![]() |
5842b10a10 | ||
![]() |
8ee9655bd5 | ||
![]() |
3ef567dcd5 | ||
![]() |
37f6b4f6be | ||
![]() |
a817faae54 | ||
![]() |
ab745f6e8e | ||
![]() |
02d608b704 | ||
![]() |
310df387e7 | ||
![]() |
fe8e79a67f | ||
![]() |
8ffe676827 | ||
![]() |
f032d0dbcf | ||
![]() |
81cc745c0a | ||
![]() |
43f9c9ebc9 | ||
![]() |
a9d1feb196 | ||
![]() |
8ec2c38f72 | ||
![]() |
72aea57105 | ||
![]() |
031ecf5be8 | ||
![]() |
93e7927686 | ||
![]() |
320d8e6190 | ||
![]() |
efa4f65686 | ||
![]() |
ec257710ff | ||
![]() |
ffad6f340f | ||
![]() |
0b637fc9bd | ||
![]() |
9f9b0b6457 | ||
![]() |
a4227680de | ||
![]() |
5cfd263617 | ||
![]() |
430e671901 | ||
![]() |
8fcd396445 | ||
![]() |
e273b6b659 | ||
![]() |
2751adf440 | ||
![]() |
d661450121 | ||
![]() |
4511ded205 | ||
![]() |
2bf0c5d72d | ||
![]() |
604e5d5e09 | ||
![]() |
5466705d97 | ||
![]() |
774aee406c | ||
![]() |
775837b60f | ||
![]() |
030b2b921a | ||
![]() |
229bc26327 | ||
![]() |
99e85173eb | ||
![]() |
be0c22d7ae | ||
![]() |
fee1092a08 | ||
![]() |
d041bd9fd3 | ||
![]() |
df4b83349e | ||
![]() |
61b42249ec | ||
![]() |
40616b6af2 |
@@ -5,33 +5,39 @@
|
||||
"context": ".."
|
||||
},
|
||||
"appPort": "8124:8123",
|
||||
"context": "..",
|
||||
"postCreateCommand": "script/bootstrap",
|
||||
"extensions": [
|
||||
"github.vscode-pull-request-github",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||
"esbenp.prettier-vscode",
|
||||
"bierner.lit-html",
|
||||
"runem.lit-plugin",
|
||||
"ms-python.vscode-pylance"
|
||||
],
|
||||
"containerEnv": {
|
||||
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
||||
},
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"files.eol": "\n",
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"files.trimTrailingWhitespace": true
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"runem.lit-plugin",
|
||||
"github.vscode-pull-request-github",
|
||||
"eamodio.gitlens"
|
||||
],
|
||||
"settings": {
|
||||
"files.eol": "\n",
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true,
|
||||
"editor.renderWhitespace": "boundary",
|
||||
"editor.rulers": [80],
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"terminal.integrated.shell.linux": "/usr/bin/zsh",
|
||||
"gitlens.showWelcomeOnInstall": false,
|
||||
"gitlens.showWhatsNewAfterUpgrades": false,
|
||||
"workbench.startupEditor": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
90
.github/workflows/cast_deployment.yaml
vendored
Normal file
90
.github/workflows/cast_deployment.yaml
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
name: Cast deployment
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
NODE_VERSION: 16
|
||||
NODE_OPTIONS: --max_old_space_size=6144
|
||||
|
||||
jobs:
|
||||
deploy_dev:
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy Development
|
||||
if: github.event_name != 'push'
|
||||
environment:
|
||||
name: Cast Development
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Build Cast
|
||||
run: ./node_modules/.bin/gulp build-cast
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=cast/dist --alias dev
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
|
||||
|
||||
deploy_master:
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy Production
|
||||
if: github.event_name == 'push'
|
||||
environment:
|
||||
name: Cast Production
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Build Cast
|
||||
run: ./node_modules/.bin/gulp build-cast
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=cast/dist --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
|
1
.github/workflows/ci.yaml
vendored
1
.github/workflows/ci.yaml
vendored
@@ -13,6 +13,7 @@ on:
|
||||
env:
|
||||
NODE_VERSION: 16
|
||||
NODE_OPTIONS: --max_old_space_size=6144
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
|
@@ -1,6 +1,9 @@
|
||||
name: Demo
|
||||
name: Demo deployment
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
@@ -12,24 +15,34 @@ env:
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: Demo
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Build Demo
|
||||
run: ./node_modules/.bin/gulp build-demo
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=demo/dist --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }}
|
||||
with:
|
||||
args: deploy --dir=demo/dist --prod
|
45
.github/workflows/design_deployment.yaml
vendored
Normal file
45
.github/workflows/design_deployment.yaml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: Design deployment
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
env:
|
||||
NODE_VERSION: 16
|
||||
NODE_OPTIONS: --max_old_space_size=6144
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: Design
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Build Gallery
|
||||
run: ./node_modules/.bin/gulp build-gallery
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=gallery/dist --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }}
|
54
.github/workflows/design_preview.yaml
vendored
Normal file
54
.github/workflows/design_preview.yaml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: Design preview
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- labeled
|
||||
branches:
|
||||
- dev
|
||||
|
||||
env:
|
||||
NODE_VERSION: 16
|
||||
NODE_OPTIONS: --max_old_space_size=6144
|
||||
|
||||
jobs:
|
||||
preview:
|
||||
runs-on: ubuntu-latest
|
||||
# Skip running on forks since it won't have access to secrets
|
||||
# Skip running PRs without 'needs design preview' label
|
||||
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Build Gallery
|
||||
run: ./node_modules/.bin/gulp build-gallery
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Deploy preview to Netlify
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}"
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }}
|
||||
|
||||
- name: Generate summary
|
||||
run: |
|
||||
echo "${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}" >> "$GITHUB_STEP_SUMMARY"
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v3.0.0
|
||||
- uses: dessant/lock-threads@v4.0.0
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: "30"
|
||||
|
19
.github/workflows/netflify.yml
vendored
19
.github/workflows/netflify.yml
vendored
@@ -1,19 +0,0 @@
|
||||
name: Netlify
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
trigger_builds:
|
||||
name: Trigger netlify build preview
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: Trigger Cast build
|
||||
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_CAST_DEV_BUILD_HOOK }}
|
||||
|
||||
- name: Trigger Demo build
|
||||
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_DEMO_DEV_BUILD_HOOK }}
|
||||
|
||||
- name: Trigger Design build
|
||||
run: curl -X POST -d "NIGHTLY" https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_GALLERY_DEV_BUILD_HOOK }}
|
3
.github/workflows/nightly.yaml
vendored
3
.github/workflows/nightly.yaml
vendored
@@ -49,9 +49,8 @@ jobs:
|
||||
run: |
|
||||
pip install build
|
||||
yarn install
|
||||
|
||||
export SKIP_FETCH_NIGHTLY_TRANSLATIONS=1
|
||||
script/build_frontend
|
||||
|
||||
rm -rf dist home_assistant_frontend.egg-info
|
||||
python3 -m build
|
||||
|
||||
|
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -52,11 +52,11 @@ jobs:
|
||||
python3 -m pip install twine build
|
||||
export TWINE_USERNAME="__token__"
|
||||
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
|
||||
|
||||
export SKIP_FETCH_NIGHTLY_TRANSLATIONS=1
|
||||
script/release
|
||||
|
||||
- name: Upload release assets
|
||||
uses: softprops/action-gh-release@v0.1.14
|
||||
uses: softprops/action-gh-release@v0.1.15
|
||||
with:
|
||||
files: |
|
||||
dist/*.whl
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2022.06.7
|
||||
uses: home-assistant/wheels@2022.10.1
|
||||
with:
|
||||
abi: cp310
|
||||
tag: musllinux_1_2
|
||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 90 days stale policy
|
||||
uses: actions/stale@v5.1.1
|
||||
uses: actions/stale@v6.0.1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 90
|
||||
|
17
.gitignore
vendored
17
.gitignore
vendored
@@ -2,19 +2,20 @@
|
||||
.reify-cache
|
||||
|
||||
# build
|
||||
build
|
||||
hass_frontend/*
|
||||
dist
|
||||
build/
|
||||
dist/
|
||||
/hass_frontend/
|
||||
/translations/
|
||||
|
||||
# yarn
|
||||
.yarn/*
|
||||
.yarn/**
|
||||
!.yarn/patches
|
||||
!.yarn/releases
|
||||
!.yarn/plugins
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
node_modules/*
|
||||
/node_modules/
|
||||
yarn-error.log
|
||||
npm-debug.log
|
||||
|
||||
@@ -26,11 +27,11 @@ npm-debug.log
|
||||
# venv stuff
|
||||
pyvenv.cfg
|
||||
pip-selfcheck.json
|
||||
venv/*
|
||||
/venv/
|
||||
.venv
|
||||
|
||||
# vscode
|
||||
.vscode/*
|
||||
.vscode/**
|
||||
!.vscode/extensions.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/tasks.json
|
||||
@@ -45,4 +46,4 @@ src/cast/dev_const.ts
|
||||
.tool-versions
|
||||
|
||||
# Home Assistant config
|
||||
/config
|
||||
/config/
|
||||
|
5
.vscode/extensions.json
vendored
5
.vscode/extensions.json
vendored
@@ -2,7 +2,8 @@
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"bierner.lit-html",
|
||||
"runem.lit-plugin"
|
||||
"runem.lit-plugin",
|
||||
"github.vscode-pull-request-github",
|
||||
"eamodio.gitlens"
|
||||
]
|
||||
}
|
||||
|
8
.vscode/tasks.json
vendored
8
.vscode/tasks.json
vendored
@@ -191,7 +191,13 @@
|
||||
"runOptions": {
|
||||
"instanceLimit": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Setup and fetch nightly translations",
|
||||
"type": "gulp",
|
||||
"task": "setup-and-fetch-nightly-translations",
|
||||
"problemMatcher": []
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
|
785
.yarn/releases/yarn-3.2.0.cjs
vendored
785
.yarn/releases/yarn-3.2.0.cjs
vendored
File diff suppressed because one or more lines are too long
783
.yarn/releases/yarn-3.2.3.cjs
vendored
Executable file
783
.yarn/releases/yarn-3.2.3.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.2.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-3.2.3.cjs
|
||||
|
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"no-restricted-syntax": 0,
|
||||
"no-console": 0
|
||||
}
|
||||
}
|
@@ -1,7 +1,12 @@
|
||||
{
|
||||
"extends": "../.eslintrc.json",
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"global-require": 0
|
||||
"no-console": "off",
|
||||
"import/no-extraneous-dependencies": "off",
|
||||
"import/extensions": "off",
|
||||
"import/no-dynamic-require": "off",
|
||||
"global-require": "off",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"prefer-arrow-callback": "off"
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const path = require("path");
|
||||
|
||||
// Currently only supports CommonJS modules, as require is synchronous. `import` would need babel running asynchronous.
|
||||
@@ -29,7 +28,6 @@ module.exports = function inlineConstants(babel, options, cwd) {
|
||||
const absolute = module.startsWith(".")
|
||||
? require.resolve(module, { paths: [cwd] })
|
||||
: module;
|
||||
// eslint-disable-next-line import/no-dynamic-require
|
||||
return [absolute, require(absolute)];
|
||||
})
|
||||
);
|
||||
|
@@ -1,9 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const path = require("path");
|
||||
const env = require("./env.js");
|
||||
const paths = require("./paths.js");
|
||||
|
||||
// Files from NPM Packages that should not be imported
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
module.exports.ignorePackages = ({ latestBuild }) => [
|
||||
// Part of yaml.js and only used for !!js functions that we don't use
|
||||
require.resolve("esprima"),
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const paths = require("./paths.js");
|
||||
|
@@ -1,17 +1,12 @@
|
||||
const del = require("del");
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const fs = require("fs/promises");
|
||||
const mapStream = require("map-stream");
|
||||
|
||||
const inDirFrontend = "translations/frontend";
|
||||
const inDirBackend = "translations/backend";
|
||||
const downloadDir = "translations/downloads";
|
||||
const srcMeta = "src/translations/translationMetadata.json";
|
||||
|
||||
const encoding = "utf8";
|
||||
|
||||
const tasks = [];
|
||||
|
||||
function hasHtml(data) {
|
||||
return /<[a-z][\s\S]*>/i.test(data);
|
||||
}
|
||||
@@ -46,50 +41,29 @@ function checkHtml() {
|
||||
});
|
||||
}
|
||||
|
||||
let taskName = "clean-downloaded-translations";
|
||||
gulp.task(taskName, function () {
|
||||
return del([`${downloadDir}/**`]);
|
||||
// Backend translations do not currently pass HTML check so are excluded here for now
|
||||
gulp.task("check-translations-html", function () {
|
||||
return gulp.src([`${inDirFrontend}/*.json`]).pipe(checkHtml());
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
taskName = "check-translations-html";
|
||||
gulp.task(taskName, function () {
|
||||
return gulp.src(`${downloadDir}/*.json`).pipe(checkHtml());
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
taskName = "check-all-files-exist";
|
||||
gulp.task(taskName, function () {
|
||||
const file = fs.readFileSync(srcMeta, { encoding });
|
||||
gulp.task("check-all-files-exist", async function () {
|
||||
const file = await fs.readFile(srcMeta, { encoding });
|
||||
const meta = JSON.parse(file);
|
||||
const writings = [];
|
||||
Object.keys(meta).forEach((lang) => {
|
||||
if (!fs.existsSync(`${inDirFrontend}/${lang}.json`)) {
|
||||
fs.writeFileSync(`${inDirFrontend}/${lang}.json`, JSON.stringify({}));
|
||||
}
|
||||
if (!fs.existsSync(`${inDirBackend}/${lang}.json`)) {
|
||||
fs.writeFileSync(`${inDirBackend}/${lang}.json`, JSON.stringify({}));
|
||||
}
|
||||
writings.push(
|
||||
fs.writeFile(`${inDirFrontend}/${lang}.json`, JSON.stringify({}), {
|
||||
flag: "wx",
|
||||
}),
|
||||
fs.writeFile(`${inDirBackend}/${lang}.json`, JSON.stringify({}), {
|
||||
flag: "wx",
|
||||
})
|
||||
);
|
||||
});
|
||||
return Promise.resolve();
|
||||
await Promise.allSettled(writings);
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
taskName = "move-downloaded-translations";
|
||||
gulp.task(taskName, function () {
|
||||
return gulp.src(`${downloadDir}/*.json`).pipe(gulp.dest(inDirFrontend));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
taskName = "check-downloaded-translations";
|
||||
gulp.task(
|
||||
taskName,
|
||||
gulp.series(
|
||||
"check-translations-html",
|
||||
"move-downloaded-translations",
|
||||
"check-all-files-exist",
|
||||
"clean-downloaded-translations"
|
||||
)
|
||||
"check-downloaded-translations",
|
||||
gulp.series("check-translations-html", "check-all-files-exist")
|
||||
);
|
||||
tasks.push(taskName);
|
||||
|
||||
module.exports = tasks;
|
||||
|
@@ -1,6 +1,4 @@
|
||||
// Tasks to generate entry HTML
|
||||
/* eslint-disable import/no-dynamic-require */
|
||||
/* eslint-disable global-require */
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
@@ -91,7 +89,9 @@ gulp.task("gen-pages-prod", (done) => {
|
||||
});
|
||||
|
||||
gulp.task("gen-index-app-dev", (done) => {
|
||||
let latestAppJS, latestCoreJS, latestCustomPanelJS;
|
||||
let latestAppJS;
|
||||
let latestCoreJS;
|
||||
let latestCustomPanelJS;
|
||||
|
||||
if (env.useWDS()) {
|
||||
latestAppJS = "http://localhost:8000/src/entrypoints/app.ts";
|
||||
|
170
build-scripts/gulp/fetch-nightly_translations.js
Normal file
170
build-scripts/gulp/fetch-nightly_translations.js
Normal file
@@ -0,0 +1,170 @@
|
||||
// Task to download the latest Lokalise translations from the nightly workflow artifacts
|
||||
|
||||
const fs = require("fs/promises");
|
||||
const path = require("path");
|
||||
const process = require("process");
|
||||
const del = require("del");
|
||||
const gulp = require("gulp");
|
||||
const jszip = require("jszip");
|
||||
const tar = require("tar");
|
||||
const { Octokit } = require("@octokit/rest");
|
||||
const { createOAuthDeviceAuth } = require("@octokit/auth-oauth-device");
|
||||
|
||||
const MAX_AGE = 24; // hours
|
||||
const OWNER = "home-assistant";
|
||||
const REPO = "frontend";
|
||||
const WORKFLOW_NAME = "nightly.yaml";
|
||||
const ARTIFACT_NAME = "translations";
|
||||
const CLIENT_ID = "Iv1.3914e28cb27834d1";
|
||||
const EXTRACT_DIR = "translations";
|
||||
const TOKEN_FILE = path.join(EXTRACT_DIR, "token.json");
|
||||
const ARTIFACT_FILE = path.join(EXTRACT_DIR, "artifact.json");
|
||||
|
||||
let allowTokenSetup = false;
|
||||
gulp.task("allow-setup-fetch-nightly-translations", (done) => {
|
||||
allowTokenSetup = true;
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("fetch-nightly-translations", async function () {
|
||||
// Skip all when environment flag is set (assumes translations are already in place)
|
||||
if (process.env?.SKIP_FETCH_NIGHTLY_TRANSLATIONS) {
|
||||
console.log("Skipping fetch due to environment signal");
|
||||
return;
|
||||
}
|
||||
|
||||
// Read current translations artifact info if it exists,
|
||||
// and stop if they are not old enough
|
||||
let currentArtifact;
|
||||
try {
|
||||
currentArtifact = JSON.parse(await fs.readFile(ARTIFACT_FILE, "utf-8"));
|
||||
const currentAge =
|
||||
(Date.now() - Date.parse(currentArtifact.created_at)) / 3600000;
|
||||
if (currentAge < MAX_AGE) {
|
||||
console.log(
|
||||
"Keeping current translations (only %s hours old)",
|
||||
currentAge.toFixed(1)
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
currentArtifact = null;
|
||||
}
|
||||
|
||||
// To store file writing promises
|
||||
const createExtractDir = fs.mkdir(EXTRACT_DIR, { recursive: true });
|
||||
const writings = [];
|
||||
|
||||
// Authenticate to GitHub using GitHub action token if it exists,
|
||||
// otherwise look for a saved user token or generate a new one if none
|
||||
let tokenAuth;
|
||||
if (process.env.GITHUB_TOKEN) {
|
||||
tokenAuth = { token: process.env.GITHUB_TOKEN };
|
||||
} else {
|
||||
try {
|
||||
tokenAuth = JSON.parse(await fs.readFile(TOKEN_FILE, "utf-8"));
|
||||
} catch {
|
||||
if (!allowTokenSetup) {
|
||||
console.log("No token found so build wil continue with English only");
|
||||
return;
|
||||
}
|
||||
const auth = createOAuthDeviceAuth({
|
||||
clientType: "github-app",
|
||||
clientId: CLIENT_ID,
|
||||
onVerification: (verification) => {
|
||||
console.log(
|
||||
"Task needs to authenticate to GitHub to fetch the translations from nightly workflow\n" +
|
||||
"Please go to %s to authorize this task\n" +
|
||||
"\nEnter user code: %s\n\n" +
|
||||
"This code will expire in %s minutes\n" +
|
||||
"Task will automatically continue after authorization and token will be saved for future use",
|
||||
verification.verification_uri,
|
||||
verification.user_code,
|
||||
(verification.expires_in / 60).toFixed(0)
|
||||
);
|
||||
},
|
||||
});
|
||||
tokenAuth = await auth({ type: "oauth" });
|
||||
writings.push(
|
||||
createExtractDir.then(
|
||||
fs.writeFile(TOKEN_FILE, JSON.stringify(tokenAuth, null, 2))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Authenticate with token and request workflow runs from GitHub
|
||||
console.log("Fetching new translations...");
|
||||
const octokit = new Octokit({
|
||||
userAgent: "Fetch Nightly Translations",
|
||||
auth: tokenAuth.token,
|
||||
});
|
||||
|
||||
const workflowRunsResponse = await octokit.rest.actions.listWorkflowRuns({
|
||||
owner: OWNER,
|
||||
repo: REPO,
|
||||
workflow_id: WORKFLOW_NAME,
|
||||
status: "success",
|
||||
event: "schedule",
|
||||
per_page: 1,
|
||||
exclude_pull_requests: true,
|
||||
});
|
||||
if (workflowRunsResponse.data.total_count === 0) {
|
||||
throw Error("No successful nightly workflow runs found");
|
||||
}
|
||||
const latestNightlyRun = workflowRunsResponse.data.workflow_runs[0];
|
||||
|
||||
// Stop if current is already the latest, otherwise Find the translations artifact
|
||||
if (currentArtifact?.workflow_run.id === latestNightlyRun.id) {
|
||||
console.log("Stopping because current translations are still the latest");
|
||||
return;
|
||||
}
|
||||
const latestArtifact = (
|
||||
await octokit.actions.listWorkflowRunArtifacts({
|
||||
owner: OWNER,
|
||||
repo: REPO,
|
||||
run_id: latestNightlyRun.id,
|
||||
})
|
||||
).data.artifacts.find((artifact) => artifact.name === ARTIFACT_NAME);
|
||||
if (!latestArtifact) {
|
||||
throw Error("Latest nightly workflow run has no translations artifact");
|
||||
}
|
||||
writings.push(
|
||||
createExtractDir.then(
|
||||
fs.writeFile(ARTIFACT_FILE, JSON.stringify(latestArtifact, null, 2))
|
||||
)
|
||||
);
|
||||
|
||||
// Remove the current translations
|
||||
const deleteCurrent = Promise.all(writings).then(
|
||||
del([`${EXTRACT_DIR}/*`, `!${ARTIFACT_FILE}`, `!${TOKEN_FILE}`])
|
||||
);
|
||||
|
||||
// Get the download URL and follow the redirect to download (stored as ArrayBuffer)
|
||||
const downloadResponse = await octokit.actions.downloadArtifact({
|
||||
owner: OWNER,
|
||||
repo: REPO,
|
||||
artifact_id: latestArtifact.id,
|
||||
archive_format: "zip",
|
||||
});
|
||||
if (downloadResponse.status !== 200) {
|
||||
throw Error("Failure downloading translations artifact");
|
||||
}
|
||||
|
||||
// Artifact is a tarball, but GitHub adds it to a zip file
|
||||
console.log("Unpacking downloaded translations...");
|
||||
const zip = await jszip.loadAsync(downloadResponse.data);
|
||||
await deleteCurrent;
|
||||
const extractStream = zip.file(/.*/)[0].nodeStream().pipe(tar.extract());
|
||||
await new Promise((resolve, reject) => {
|
||||
extractStream.on("close", resolve).on("error", reject);
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
"setup-and-fetch-nightly-translations",
|
||||
gulp.series(
|
||||
"allow-setup-fetch-nightly-translations",
|
||||
"fetch-nightly-translations"
|
||||
)
|
||||
);
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable */
|
||||
// Run demo develop mode
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
@@ -41,7 +40,7 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
|
||||
}
|
||||
processed.add(pageId);
|
||||
|
||||
const [category, name] = pageId.split("/", 2);
|
||||
const [category] = pageId.split("/", 2);
|
||||
|
||||
const demoFile = path.resolve(pageDir, `${pageId}.ts`);
|
||||
const descriptionFile = path.resolve(pageDir, `${pageId}.markdown`);
|
||||
|
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const del = require("del");
|
||||
const path = require("path");
|
||||
const gulp = require("gulp");
|
||||
|
@@ -5,9 +5,9 @@ const rollup = require("rollup");
|
||||
const handler = require("serve-handler");
|
||||
const http = require("http");
|
||||
const log = require("fancy-log");
|
||||
const open = require("open");
|
||||
const rollupConfig = require("../rollup");
|
||||
const paths = require("../paths");
|
||||
const open = require("open");
|
||||
|
||||
const bothBuilds = (createConfigFunc, params) =>
|
||||
gulp.series(
|
||||
@@ -30,11 +30,11 @@ const bothBuilds = (createConfigFunc, params) =>
|
||||
);
|
||||
|
||||
function createServer(serveOptions) {
|
||||
const server = http.createServer((request, response) => {
|
||||
return handler(request, response, {
|
||||
const server = http.createServer((request, response) =>
|
||||
handler(request, response, {
|
||||
public: serveOptions.root,
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
server.listen(
|
||||
serveOptions.port,
|
||||
|
@@ -1,7 +1,5 @@
|
||||
// Generate service worker.
|
||||
// Based on manifest, create a file with the content as service_worker.js
|
||||
/* eslint-disable import/no-dynamic-require */
|
||||
/* eslint-disable global-require */
|
||||
const gulp = require("gulp");
|
||||
const path = require("path");
|
||||
const fs = require("fs-extra");
|
||||
|
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const crypto = require("crypto");
|
||||
const del = require("del");
|
||||
const path = require("path");
|
||||
@@ -15,6 +13,8 @@ const { mapFiles } = require("../util");
|
||||
const env = require("../env");
|
||||
const paths = require("../paths");
|
||||
|
||||
require("./fetch-nightly_translations");
|
||||
|
||||
const inFrontendDir = "translations/frontend";
|
||||
const inBackendDir = "translations/backend";
|
||||
const workDir = "build/translations";
|
||||
@@ -23,10 +23,13 @@ const coreDir = workDir + "/core";
|
||||
const outDir = workDir + "/output";
|
||||
let mergeBackend = false;
|
||||
|
||||
gulp.task("translations-enable-merge-backend", (done) => {
|
||||
mergeBackend = true;
|
||||
done();
|
||||
});
|
||||
gulp.task(
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel((done) => {
|
||||
mergeBackend = true;
|
||||
done();
|
||||
}, "allow-setup-fetch-nightly-translations")
|
||||
);
|
||||
|
||||
// Panel translations which should be split from the core translations.
|
||||
const TRANSLATION_FRAGMENTS = Object.keys(
|
||||
@@ -170,17 +173,24 @@ gulp.task("build-master-translation", () => {
|
||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
||||
.pipe(
|
||||
merge({
|
||||
fileName: "translationMaster.json",
|
||||
fileName: "en.json",
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(workDir));
|
||||
.pipe(gulp.dest(fullDir));
|
||||
});
|
||||
|
||||
gulp.task("build-merged-translations", () =>
|
||||
gulp
|
||||
.src([inFrontendDir + "/*.json", workDir + "/test.json"], {
|
||||
allowEmpty: true,
|
||||
})
|
||||
.src(
|
||||
[
|
||||
inFrontendDir + "/*.json",
|
||||
"!" + inFrontendDir + "/en.json",
|
||||
workDir + "/test.json",
|
||||
],
|
||||
{
|
||||
allowEmpty: true,
|
||||
}
|
||||
)
|
||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
||||
.pipe(
|
||||
flatmap((stream, file) => {
|
||||
@@ -193,7 +203,7 @@ gulp.task("build-merged-translations", () =>
|
||||
// than a base translation + region.
|
||||
const tr = path.basename(file.history[0], ".json");
|
||||
const subtags = tr.split("-");
|
||||
const src = [workDir + "/translationMaster.json"];
|
||||
const src = [fullDir + "/en.json"];
|
||||
for (let i = 1; i <= subtags.length; i++) {
|
||||
const lang = subtags.slice(0, i).join("-");
|
||||
if (lang === "test") {
|
||||
@@ -378,7 +388,6 @@ gulp.task("build-translation-write-metadata", () =>
|
||||
if (value.nativeName) {
|
||||
newData[key] = value;
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`Skipping language ${key}. Native name was not translated.`
|
||||
);
|
||||
@@ -411,8 +420,10 @@ gulp.task(
|
||||
gulp.task(
|
||||
"build-translations",
|
||||
gulp.series(
|
||||
"clean-translations",
|
||||
"ensure-translations-build-dir",
|
||||
gulp.parallel(
|
||||
"fetch-nightly-translations",
|
||||
gulp.series("clean-translations", "ensure-translations-build-dir")
|
||||
),
|
||||
"create-translations",
|
||||
"build-translation-fingerprints",
|
||||
"build-translation-write-metadata"
|
||||
@@ -422,8 +433,10 @@ gulp.task(
|
||||
gulp.task(
|
||||
"build-supervisor-translations",
|
||||
gulp.series(
|
||||
"clean-translations",
|
||||
"ensure-translations-build-dir",
|
||||
gulp.parallel(
|
||||
"fetch-nightly-translations",
|
||||
gulp.series("clean-translations", "ensure-translations-build-dir")
|
||||
),
|
||||
"build-master-translation",
|
||||
"build-merged-translations",
|
||||
"build-translation-fragment-supervisor",
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
// Tasks to run webpack.
|
||||
const fs = require("fs");
|
||||
const gulp = require("gulp");
|
||||
@@ -69,7 +68,6 @@ const doneHandler = (done) => (err, stats) => {
|
||||
}
|
||||
|
||||
if (stats.hasErrors() || stats.hasWarnings()) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(stats.toString("minimal"));
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
|
@@ -81,13 +81,13 @@ module.exports = function (opts = {}) {
|
||||
opts.workerRegexp.flags
|
||||
);
|
||||
if (!workerRegexp.test(code)) {
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const ms = new MagicString(code);
|
||||
// Reset the regexp
|
||||
workerRegexp.lastIndex = 0;
|
||||
while (true) {
|
||||
for (;;) {
|
||||
const match = workerRegexp.exec(code);
|
||||
if (!match) {
|
||||
break;
|
||||
@@ -98,6 +98,7 @@ module.exports = function (opts = {}) {
|
||||
// Parse the optional options object
|
||||
if (match[3] && match[3].length > 0) {
|
||||
// FIXME: ooooof!
|
||||
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
||||
optionsObject = new Function(`return ${match[3].slice(1)};`)();
|
||||
}
|
||||
delete optionsObject.type;
|
||||
@@ -110,12 +111,14 @@ module.exports = function (opts = {}) {
|
||||
}
|
||||
|
||||
// Find worker file and store it as a chunk with ID prefixed for our loader
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const resolvedWorkerFile = (await this.resolve(workerFile, id)).id;
|
||||
let chunkRefId;
|
||||
if (resolvedWorkerFile in refIds) {
|
||||
chunkRefId = refIds[resolvedWorkerFile];
|
||||
} else {
|
||||
this.addWatchFile(resolvedWorkerFile);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const source = await getBundledWorker(
|
||||
resolvedWorkerFile,
|
||||
rollupOptions
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const path = require("path");
|
||||
|
||||
const commonjs = require("@rollup/plugin-commonjs");
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const webpack = require("webpack");
|
||||
const path = require("path");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
@@ -103,7 +102,6 @@ const createWebpackConfig = ({
|
||||
? path.resolve(context, resource)
|
||||
: require.resolve(resource);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
"Error in Home Assistant ignore plugin",
|
||||
resource,
|
||||
|
@@ -213,7 +213,7 @@
|
||||
</p>
|
||||
<ul>
|
||||
<li>Google Chrome (all platforms except iOS)</li>
|
||||
<li>Microsoft Edge (all platforms)</li>
|
||||
<li>Microsoft Edge (all platforms except iOS)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
@@ -88,7 +88,7 @@ class HcCast extends LitElement {
|
||||
>
|
||||
${(this.lovelaceConfig
|
||||
? this.lovelaceConfig.views
|
||||
: [generateDefaultViewConfig([], [], [], {}, () => "")]
|
||||
: [generateDefaultViewConfig({}, {}, {}, {}, () => "")]
|
||||
).map(
|
||||
(view, idx) => html`
|
||||
<paper-icon-item
|
||||
@@ -181,7 +181,7 @@ class HcCast extends LitElement {
|
||||
private async _handlePickView(ev: Event) {
|
||||
const path = (ev.currentTarget as any).getAttribute("data-path");
|
||||
await ensureConnectedCastSession(this.castManager!, this.auth!);
|
||||
castSendShowLovelaceView(this.castManager, path);
|
||||
castSendShowLovelaceView(this.castManager, path, this.auth.data.hassUrl);
|
||||
}
|
||||
|
||||
private async _handleLogout() {
|
||||
|
@@ -44,7 +44,7 @@ class HcLayout extends LitElement {
|
||||
<div class="footer">
|
||||
<a href="./faq.html">Frequently Asked Questions</a> – Found a bug?
|
||||
<a
|
||||
href="https://github.com/home-assistant/home-assistant-polymer/issues"
|
||||
href="https://github.com/home-assistant/frontend/issues"
|
||||
target="_blank"
|
||||
>Let us know!</a
|
||||
>
|
||||
|
@@ -33,7 +33,6 @@ import { castContext } from "../cast_context";
|
||||
import "./hc-launch-screen";
|
||||
|
||||
let resourcesLoaded = false;
|
||||
|
||||
@customElement("hc-main")
|
||||
export class HcMain extends HassElement {
|
||||
@state() private _showDemo = false;
|
||||
@@ -46,6 +45,8 @@ export class HcMain extends HassElement {
|
||||
|
||||
@state() private _urlPath?: string | null;
|
||||
|
||||
private _hassUUID?: string;
|
||||
|
||||
private _unsubLovelace?: UnsubscribeFunc;
|
||||
|
||||
public processIncomingMessage(msg: HassMessage) {
|
||||
@@ -125,6 +126,7 @@ export class HcMain extends HassElement {
|
||||
|
||||
if (this.hass) {
|
||||
status.hassUrl = this.hass.auth.data.hassUrl;
|
||||
status.hassUUID = this._hassUUID;
|
||||
status.lovelacePath = this._lovelacePath;
|
||||
status.urlPath = this._urlPath;
|
||||
}
|
||||
@@ -163,6 +165,18 @@ export class HcMain extends HassElement {
|
||||
};
|
||||
|
||||
private async _handleGetStatusMessage(msg: GetStatusMessage) {
|
||||
if (
|
||||
(this.hass && msg.hassUUID && msg.hassUUID !== this._hassUUID) ||
|
||||
(this.hass && msg.hassUrl && msg.hassUrl !== this.hass.auth.data.hassUrl)
|
||||
) {
|
||||
this._error = "Not connected to the same Home Assistant instance.";
|
||||
this._sendError(
|
||||
ReceiverErrorCode.WRONG_INSTANCE,
|
||||
this._error,
|
||||
msg.senderId!
|
||||
);
|
||||
}
|
||||
|
||||
this._sendStatus(msg.senderId!);
|
||||
}
|
||||
|
||||
@@ -179,6 +193,7 @@ export class HcMain extends HassElement {
|
||||
expires_in: 0,
|
||||
}),
|
||||
});
|
||||
this._hassUUID = msg.hassUUID;
|
||||
} catch (err: any) {
|
||||
const errorMessage = this._getErrorMessage(err);
|
||||
this._error = errorMessage;
|
||||
@@ -209,9 +224,29 @@ export class HcMain extends HassElement {
|
||||
if (!this.hass) {
|
||||
this._sendStatus(msg.senderId!);
|
||||
this._error = "Cannot show Lovelace because we're not connected.";
|
||||
this._sendError(ReceiverErrorCode.NOT_CONNECTED, this._error);
|
||||
this._sendError(
|
||||
ReceiverErrorCode.NOT_CONNECTED,
|
||||
this._error,
|
||||
msg.senderId!
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(msg.hassUUID && msg.hassUUID !== this._hassUUID) ||
|
||||
(msg.hassUrl && msg.hassUrl !== this.hass.auth.data.hassUrl)
|
||||
) {
|
||||
this._sendStatus(msg.senderId!);
|
||||
this._error =
|
||||
"Cannot show Lovelace because we're not connected to the same Home Assistant instance.";
|
||||
this._sendError(
|
||||
ReceiverErrorCode.WRONG_INSTANCE,
|
||||
this._error,
|
||||
msg.senderId!
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this._error = undefined;
|
||||
if (msg.urlPath === "lovelace") {
|
||||
msg.urlPath = null;
|
||||
|
@@ -508,7 +508,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
|
||||
origin_addresses: ["XYZ"],
|
||||
status: "OK",
|
||||
mode: "driving",
|
||||
units: "imperial",
|
||||
units: "us_customary",
|
||||
duration_in_traffic: "41 mins",
|
||||
duration: "44 mins",
|
||||
distance: "34.3 mi",
|
||||
@@ -527,7 +527,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
|
||||
origin_addresses: ["XYZ"],
|
||||
status: "OK",
|
||||
mode: "driving",
|
||||
units: "imperial",
|
||||
units: "us_customary",
|
||||
duration_in_traffic: "37 mins",
|
||||
duration: "37 mins",
|
||||
distance: "30.2 mi",
|
||||
|
@@ -1196,7 +1196,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
||||
left: "15%",
|
||||
},
|
||||
type: "state-icon",
|
||||
entity: "binary_sensor.water_leak_sensor_158d0002338651",
|
||||
entity: "binary_sensor.water_leak_sensor_158d00026e26dc",
|
||||
},
|
||||
{
|
||||
prefix: "Kitchen: ",
|
||||
@@ -1206,7 +1206,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
||||
top: "89%",
|
||||
left: "32%",
|
||||
},
|
||||
entity: "binary_sensor.water_leak_sensor_158d0002338651",
|
||||
entity: "binary_sensor.water_leak_sensor_158d00026e26dc",
|
||||
},
|
||||
{
|
||||
style: {
|
||||
@@ -1215,7 +1215,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
||||
left: "60%",
|
||||
},
|
||||
type: "state-icon",
|
||||
entity: "binary_sensor.water_leak_sensor_158d00026e26dc",
|
||||
entity: "binary_sensor.water_leak_sensor_158d0002338651",
|
||||
},
|
||||
{
|
||||
prefix: "Bathroom: ",
|
||||
@@ -1225,7 +1225,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
||||
top: "89%",
|
||||
left: "77%",
|
||||
},
|
||||
entity: "binary_sensor.water_leak_sensor_158d00026e26dc",
|
||||
entity: "binary_sensor.water_leak_sensor_158d0002338651",
|
||||
},
|
||||
],
|
||||
type: "picture-elements",
|
||||
|
@@ -138,7 +138,7 @@ if (!window.cardTools) {
|
||||
return cardTools.createThing("row", config);
|
||||
|
||||
const domain = config.entity.split(".", 1)[0];
|
||||
Object.assign(config, { type: DEFAULT_ROWS[domain] || "text" });
|
||||
Object.assign(config, { type: DEFAULT_ROWS[domain] || "simple" });
|
||||
return cardTools.createThing("entity-row", config);
|
||||
};
|
||||
|
||||
|
@@ -20,6 +20,7 @@ import { mockHistory } from "./stubs/history";
|
||||
import { mockLovelace } from "./stubs/lovelace";
|
||||
import { mockMediaPlayer } from "./stubs/media_player";
|
||||
import { mockPersistentNotification } from "./stubs/persistent_notification";
|
||||
import { mockRecorder } from "./stubs/recorder";
|
||||
import { mockShoppingList } from "./stubs/shopping_list";
|
||||
import { mockSystemLog } from "./stubs/system_log";
|
||||
import { mockTemplate } from "./stubs/template";
|
||||
@@ -45,6 +46,7 @@ class HaDemo extends HomeAssistantAppEl {
|
||||
mockAuth(hass);
|
||||
mockTranslations(hass);
|
||||
mockHistory(hass);
|
||||
mockRecorder(hass);
|
||||
mockShoppingList(hass);
|
||||
mockSystemLog(hass);
|
||||
mockTemplate(hass);
|
||||
@@ -61,12 +63,14 @@ class HaDemo extends HomeAssistantAppEl {
|
||||
area_id: null,
|
||||
disabled_by: null,
|
||||
entity_id: "sensor.co2_intensity",
|
||||
id: "sensor.co2_intensity",
|
||||
name: null,
|
||||
icon: null,
|
||||
platform: "co2signal",
|
||||
hidden_by: null,
|
||||
entity_category: null,
|
||||
has_entity_name: false,
|
||||
unique_id: "co2_intensity",
|
||||
},
|
||||
{
|
||||
config_entry_id: "co2signal",
|
||||
@@ -74,12 +78,14 @@ class HaDemo extends HomeAssistantAppEl {
|
||||
area_id: null,
|
||||
disabled_by: null,
|
||||
entity_id: "sensor.grid_fossil_fuel_percentage",
|
||||
id: "sensor.co2_intensity",
|
||||
name: null,
|
||||
icon: null,
|
||||
platform: "co2signal",
|
||||
hidden_by: null,
|
||||
entity_category: null,
|
||||
has_entity_name: false,
|
||||
unique_id: "grid_fossil_fuel_percentage",
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -116,3 +122,9 @@ class HaDemo extends HomeAssistantAppEl {
|
||||
}
|
||||
|
||||
customElements.define("ha-demo", HaDemo);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-demo": HaDemo;
|
||||
}
|
||||
}
|
||||
|
@@ -1,90 +1,101 @@
|
||||
import { format, startOfToday, startOfTomorrow } from "date-fns/esm";
|
||||
import { EnergySolarForecasts } from "../../../src/data/energy";
|
||||
import {
|
||||
EnergyInfo,
|
||||
EnergyPreferences,
|
||||
EnergySolarForecasts,
|
||||
FossilEnergyConsumption,
|
||||
} from "../../../src/data/energy";
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockEnergy = (hass: MockHomeAssistant) => {
|
||||
hass.mockWS("energy/get_prefs", () => ({
|
||||
energy_sources: [
|
||||
{
|
||||
type: "grid",
|
||||
flow_from: [
|
||||
{
|
||||
stat_energy_from: "sensor.energy_consumption_tarif_1",
|
||||
stat_cost: "sensor.energy_consumption_tarif_1_cost",
|
||||
entity_energy_from: "sensor.energy_consumption_tarif_1",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
{
|
||||
stat_energy_from: "sensor.energy_consumption_tarif_2",
|
||||
stat_cost: "sensor.energy_consumption_tarif_2_cost",
|
||||
entity_energy_from: "sensor.energy_consumption_tarif_2",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
],
|
||||
flow_to: [
|
||||
{
|
||||
stat_energy_to: "sensor.energy_production_tarif_1",
|
||||
stat_compensation: "sensor.energy_production_tarif_1_compensation",
|
||||
entity_energy_to: "sensor.energy_production_tarif_1",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
{
|
||||
stat_energy_to: "sensor.energy_production_tarif_2",
|
||||
stat_compensation: "sensor.energy_production_tarif_2_compensation",
|
||||
entity_energy_to: "sensor.energy_production_tarif_2",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
],
|
||||
cost_adjustment_day: 0,
|
||||
},
|
||||
{
|
||||
type: "solar",
|
||||
stat_energy_from: "sensor.solar_production",
|
||||
config_entry_solar_forecast: ["solar_forecast"],
|
||||
},
|
||||
/* {
|
||||
type: "battery",
|
||||
stat_energy_from: "sensor.battery_output",
|
||||
stat_energy_to: "sensor.battery_input",
|
||||
}, */
|
||||
{
|
||||
type: "gas",
|
||||
stat_energy_from: "sensor.energy_gas",
|
||||
stat_cost: "sensor.energy_gas_cost",
|
||||
entity_energy_from: "sensor.energy_gas",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
],
|
||||
device_consumption: [
|
||||
{
|
||||
stat_consumption: "sensor.energy_car",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_ac",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_washing_machine",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_dryer",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_heat_pump",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_boiler",
|
||||
},
|
||||
],
|
||||
}));
|
||||
hass.mockWS("energy/info", () => ({ cost_sensors: [] }));
|
||||
hass.mockWS("energy/fossil_energy_consumption", ({ period }) => ({
|
||||
start: period === "month" ? 250 : period === "day" ? 10 : 2,
|
||||
}));
|
||||
hass.mockWS(
|
||||
"energy/get_prefs",
|
||||
(): EnergyPreferences => ({
|
||||
energy_sources: [
|
||||
{
|
||||
type: "grid",
|
||||
flow_from: [
|
||||
{
|
||||
stat_energy_from: "sensor.energy_consumption_tarif_1",
|
||||
stat_cost: "sensor.energy_consumption_tarif_1_cost",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
{
|
||||
stat_energy_from: "sensor.energy_consumption_tarif_2",
|
||||
stat_cost: "sensor.energy_consumption_tarif_2_cost",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
],
|
||||
flow_to: [
|
||||
{
|
||||
stat_energy_to: "sensor.energy_production_tarif_1",
|
||||
stat_compensation:
|
||||
"sensor.energy_production_tarif_1_compensation",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
{
|
||||
stat_energy_to: "sensor.energy_production_tarif_2",
|
||||
stat_compensation:
|
||||
"sensor.energy_production_tarif_2_compensation",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
],
|
||||
cost_adjustment_day: 0,
|
||||
},
|
||||
{
|
||||
type: "solar",
|
||||
stat_energy_from: "sensor.solar_production",
|
||||
config_entry_solar_forecast: ["solar_forecast"],
|
||||
},
|
||||
/* {
|
||||
type: "battery",
|
||||
stat_energy_from: "sensor.battery_output",
|
||||
stat_energy_to: "sensor.battery_input",
|
||||
}, */
|
||||
{
|
||||
type: "gas",
|
||||
stat_energy_from: "sensor.energy_gas",
|
||||
stat_cost: "sensor.energy_gas_cost",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
],
|
||||
device_consumption: [
|
||||
{
|
||||
stat_consumption: "sensor.energy_car",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_ac",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_washing_machine",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_dryer",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_heat_pump",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_boiler",
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
hass.mockWS(
|
||||
"energy/info",
|
||||
(): EnergyInfo => ({ cost_sensors: {}, solar_forecast_domains: [] })
|
||||
);
|
||||
hass.mockWS(
|
||||
"energy/fossil_energy_consumption",
|
||||
({ period }): FossilEnergyConsumption => ({
|
||||
start: period === "month" ? 250 : period === "day" ? 10 : 2,
|
||||
})
|
||||
);
|
||||
const todayString = format(startOfToday(), "yyyy-MM-dd");
|
||||
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
|
||||
hass.mockWS(
|
||||
|
@@ -1,12 +1,4 @@
|
||||
import {
|
||||
addDays,
|
||||
addHours,
|
||||
addMonths,
|
||||
differenceInHours,
|
||||
endOfDay,
|
||||
} from "date-fns/esm";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { StatisticValue } from "../../../src/data/history";
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
interface HistoryQueryParams {
|
||||
@@ -72,331 +64,6 @@ const generateHistory = (state, deltas) => {
|
||||
|
||||
const incrementalUnits = ["clients", "queries", "ads"];
|
||||
|
||||
const generateMeanStatistics = (
|
||||
id: string,
|
||||
start: Date,
|
||||
end: Date,
|
||||
period: "5minute" | "hour" | "day" | "month" = "hour",
|
||||
initValue: number,
|
||||
maxDiff: number
|
||||
) => {
|
||||
const statistics: StatisticValue[] = [];
|
||||
let currentDate = new Date(start);
|
||||
currentDate.setMinutes(0, 0, 0);
|
||||
let lastVal = initValue;
|
||||
const now = new Date();
|
||||
while (end > currentDate && currentDate < now) {
|
||||
const delta = Math.random() * maxDiff;
|
||||
const mean = lastVal + delta;
|
||||
statistics.push({
|
||||
statistic_id: id,
|
||||
start: currentDate.toISOString(),
|
||||
end: currentDate.toISOString(),
|
||||
mean,
|
||||
min: mean - Math.random() * maxDiff,
|
||||
max: mean + Math.random() * maxDiff,
|
||||
last_reset: "1970-01-01T00:00:00+00:00",
|
||||
state: mean,
|
||||
sum: null,
|
||||
});
|
||||
lastVal = mean;
|
||||
currentDate =
|
||||
period === "day"
|
||||
? addDays(currentDate, 1)
|
||||
: period === "month"
|
||||
? addMonths(currentDate, 1)
|
||||
: addHours(currentDate, 1);
|
||||
}
|
||||
return statistics;
|
||||
};
|
||||
|
||||
const generateSumStatistics = (
|
||||
id: string,
|
||||
start: Date,
|
||||
end: Date,
|
||||
period: "5minute" | "hour" | "day" | "month" = "hour",
|
||||
initValue: number,
|
||||
maxDiff: number
|
||||
) => {
|
||||
const statistics: StatisticValue[] = [];
|
||||
let currentDate = new Date(start);
|
||||
currentDate.setMinutes(0, 0, 0);
|
||||
let sum = initValue;
|
||||
const now = new Date();
|
||||
while (end > currentDate && currentDate < now) {
|
||||
const add = Math.random() * maxDiff;
|
||||
sum += add;
|
||||
statistics.push({
|
||||
statistic_id: id,
|
||||
start: currentDate.toISOString(),
|
||||
end: currentDate.toISOString(),
|
||||
mean: null,
|
||||
min: null,
|
||||
max: null,
|
||||
last_reset: "1970-01-01T00:00:00+00:00",
|
||||
state: initValue + sum,
|
||||
sum,
|
||||
});
|
||||
currentDate =
|
||||
period === "day"
|
||||
? addDays(currentDate, 1)
|
||||
: period === "month"
|
||||
? addMonths(currentDate, 1)
|
||||
: addHours(currentDate, 1);
|
||||
}
|
||||
return statistics;
|
||||
};
|
||||
|
||||
const generateCurvedStatistics = (
|
||||
id: string,
|
||||
start: Date,
|
||||
end: Date,
|
||||
_period: "5minute" | "hour" | "day" | "month" = "hour",
|
||||
initValue: number,
|
||||
maxDiff: number,
|
||||
metered: boolean
|
||||
) => {
|
||||
const statistics: StatisticValue[] = [];
|
||||
let currentDate = new Date(start);
|
||||
currentDate.setMinutes(0, 0, 0);
|
||||
let sum = initValue;
|
||||
const hours = differenceInHours(end, start) - 1;
|
||||
let i = 0;
|
||||
let half = false;
|
||||
const now = new Date();
|
||||
while (end > currentDate && currentDate < now) {
|
||||
const add = Math.random() * maxDiff;
|
||||
sum += i * add;
|
||||
statistics.push({
|
||||
statistic_id: id,
|
||||
start: currentDate.toISOString(),
|
||||
end: currentDate.toISOString(),
|
||||
mean: null,
|
||||
min: null,
|
||||
max: null,
|
||||
last_reset: "1970-01-01T00:00:00+00:00",
|
||||
state: initValue + sum,
|
||||
sum: metered ? sum : null,
|
||||
});
|
||||
currentDate = addHours(currentDate, 1);
|
||||
if (!half && i > hours / 2) {
|
||||
half = true;
|
||||
}
|
||||
i += half ? -1 : 1;
|
||||
}
|
||||
return statistics;
|
||||
};
|
||||
|
||||
const statisticsFunctions: Record<
|
||||
string,
|
||||
(
|
||||
id: string,
|
||||
start: Date,
|
||||
end: Date,
|
||||
period: "5minute" | "hour" | "day" | "month"
|
||||
) => StatisticValue[]
|
||||
> = {
|
||||
"sensor.energy_consumption_tarif_1": (
|
||||
id: string,
|
||||
start: Date,
|
||||
end: Date,
|
||||
period = "hour"
|
||||
) => {
|
||||
if (period !== "hour") {
|
||||
return generateSumStatistics(
|
||||
id,
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
0,
|
||||
period === "day" ? 17 : 504
|
||||
);
|
||||
}
|
||||
const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000);
|
||||
const morningLow = generateSumStatistics(
|
||||
id,
|
||||
start,
|
||||
morningEnd,
|
||||
period,
|
||||
0,
|
||||
0.7
|
||||
);
|
||||
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
||||
const morningFinalVal = morningLow.length
|
||||
? morningLow[morningLow.length - 1].sum!
|
||||
: 0;
|
||||
const empty = generateSumStatistics(
|
||||
id,
|
||||
morningEnd,
|
||||
eveningStart,
|
||||
period,
|
||||
morningFinalVal,
|
||||
0
|
||||
);
|
||||
const eveningLow = generateSumStatistics(
|
||||
id,
|
||||
eveningStart,
|
||||
end,
|
||||
period,
|
||||
morningFinalVal,
|
||||
0.7
|
||||
);
|
||||
return [...morningLow, ...empty, ...eveningLow];
|
||||
},
|
||||
"sensor.energy_consumption_tarif_2": (
|
||||
id: string,
|
||||
start: Date,
|
||||
end: Date,
|
||||
period = "hour"
|
||||
) => {
|
||||
if (period !== "hour") {
|
||||
return generateSumStatistics(
|
||||
id,
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
0,
|
||||
period === "day" ? 17 : 504
|
||||
);
|
||||
}
|
||||
const morningEnd = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
||||
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
||||
const highTarif = generateSumStatistics(
|
||||
id,
|
||||
morningEnd,
|
||||
eveningStart,
|
||||
period,
|
||||
0,
|
||||
0.3
|
||||
);
|
||||
const highTarifFinalVal = highTarif.length
|
||||
? highTarif[highTarif.length - 1].sum!
|
||||
: 0;
|
||||
const morning = generateSumStatistics(id, start, morningEnd, period, 0, 0);
|
||||
const evening = generateSumStatistics(
|
||||
id,
|
||||
eveningStart,
|
||||
end,
|
||||
period,
|
||||
highTarifFinalVal,
|
||||
0
|
||||
);
|
||||
return [...morning, ...highTarif, ...evening];
|
||||
},
|
||||
"sensor.energy_production_tarif_1": (id, start, end, period = "hour") =>
|
||||
generateSumStatistics(id, start, end, period, 0, 0),
|
||||
"sensor.energy_production_tarif_1_compensation": (
|
||||
id,
|
||||
start,
|
||||
end,
|
||||
period = "hour"
|
||||
) => generateSumStatistics(id, start, end, period, 0, 0),
|
||||
"sensor.energy_production_tarif_2": (id, start, end, period = "hour") => {
|
||||
if (period !== "hour") {
|
||||
return generateSumStatistics(
|
||||
id,
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
0,
|
||||
period === "day" ? 17 : 504
|
||||
);
|
||||
}
|
||||
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
||||
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
|
||||
const dayEnd = new Date(endOfDay(productionEnd));
|
||||
const production = generateCurvedStatistics(
|
||||
id,
|
||||
productionStart,
|
||||
productionEnd,
|
||||
period,
|
||||
0,
|
||||
0.15,
|
||||
true
|
||||
);
|
||||
const productionFinalVal = production.length
|
||||
? production[production.length - 1].sum!
|
||||
: 0;
|
||||
const morning = generateSumStatistics(
|
||||
id,
|
||||
start,
|
||||
productionStart,
|
||||
period,
|
||||
0,
|
||||
0
|
||||
);
|
||||
const evening = generateSumStatistics(
|
||||
id,
|
||||
productionEnd,
|
||||
dayEnd,
|
||||
period,
|
||||
productionFinalVal,
|
||||
0
|
||||
);
|
||||
const rest = generateSumStatistics(
|
||||
id,
|
||||
dayEnd,
|
||||
end,
|
||||
period,
|
||||
productionFinalVal,
|
||||
1
|
||||
);
|
||||
return [...morning, ...production, ...evening, ...rest];
|
||||
},
|
||||
"sensor.solar_production": (id, start, end, period = "hour") => {
|
||||
if (period !== "hour") {
|
||||
return generateSumStatistics(
|
||||
id,
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
0,
|
||||
period === "day" ? 17 : 504
|
||||
);
|
||||
}
|
||||
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
|
||||
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
|
||||
const dayEnd = new Date(endOfDay(productionEnd));
|
||||
const production = generateCurvedStatistics(
|
||||
id,
|
||||
productionStart,
|
||||
productionEnd,
|
||||
period,
|
||||
0,
|
||||
0.3,
|
||||
true
|
||||
);
|
||||
const productionFinalVal = production.length
|
||||
? production[production.length - 1].sum!
|
||||
: 0;
|
||||
const morning = generateSumStatistics(
|
||||
id,
|
||||
start,
|
||||
productionStart,
|
||||
period,
|
||||
0,
|
||||
0
|
||||
);
|
||||
const evening = generateSumStatistics(
|
||||
id,
|
||||
productionEnd,
|
||||
dayEnd,
|
||||
period,
|
||||
productionFinalVal,
|
||||
0
|
||||
);
|
||||
const rest = generateSumStatistics(
|
||||
id,
|
||||
dayEnd,
|
||||
end,
|
||||
period,
|
||||
productionFinalVal,
|
||||
2
|
||||
);
|
||||
return [...morning, ...production, ...evening, ...rest];
|
||||
},
|
||||
};
|
||||
|
||||
export const mockHistory = (mockHass: MockHomeAssistant) => {
|
||||
mockHass.mockAPI(
|
||||
new RegExp("history/period/.+"),
|
||||
@@ -466,43 +133,4 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
|
||||
return results;
|
||||
}
|
||||
);
|
||||
mockHass.mockWS("recorder/get_statistics_metadata", () => []);
|
||||
mockHass.mockWS("history/list_statistic_ids", () => []);
|
||||
mockHass.mockWS(
|
||||
"history/statistics_during_period",
|
||||
({ statistic_ids, start_time, end_time, period }, hass) => {
|
||||
const start = new Date(start_time);
|
||||
const end = end_time ? new Date(end_time) : new Date();
|
||||
|
||||
const statistics: Record<string, StatisticValue[]> = {};
|
||||
|
||||
statistic_ids.forEach((id: string) => {
|
||||
if (id in statisticsFunctions) {
|
||||
statistics[id] = statisticsFunctions[id](id, start, end, period);
|
||||
} else {
|
||||
const entityState = hass.states[id];
|
||||
const state = entityState ? Number(entityState.state) : 1;
|
||||
statistics[id] =
|
||||
entityState && "last_reset" in entityState.attributes
|
||||
? generateSumStatistics(
|
||||
id,
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
state,
|
||||
state * (state > 80 ? 0.01 : 0.05)
|
||||
)
|
||||
: generateMeanStatistics(
|
||||
id,
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
state,
|
||||
state * (state > 80 ? 0.05 : 0.1)
|
||||
);
|
||||
}
|
||||
});
|
||||
return statistics;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
342
demo/src/stubs/recorder.ts
Normal file
342
demo/src/stubs/recorder.ts
Normal file
@@ -0,0 +1,342 @@
|
||||
import {
|
||||
addDays,
|
||||
addHours,
|
||||
addMonths,
|
||||
differenceInHours,
|
||||
endOfDay,
|
||||
} from "date-fns";
|
||||
import {
|
||||
Statistics,
|
||||
StatisticsMetaData,
|
||||
StatisticValue,
|
||||
} from "../../../src/data/recorder";
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
const generateMeanStatistics = (
|
||||
start: Date,
|
||||
end: Date,
|
||||
period: "5minute" | "hour" | "day" | "month" = "hour",
|
||||
initValue: number,
|
||||
maxDiff: number
|
||||
): StatisticValue[] => {
|
||||
const statistics: StatisticValue[] = [];
|
||||
let currentDate = new Date(start);
|
||||
currentDate.setMinutes(0, 0, 0);
|
||||
let lastVal = initValue;
|
||||
const now = new Date();
|
||||
while (end > currentDate && currentDate < now) {
|
||||
const delta = Math.random() * maxDiff;
|
||||
const mean = lastVal + delta;
|
||||
statistics.push({
|
||||
start: currentDate.getTime(),
|
||||
end: currentDate.getTime(),
|
||||
mean,
|
||||
min: mean - Math.random() * maxDiff,
|
||||
max: mean + Math.random() * maxDiff,
|
||||
last_reset: 0,
|
||||
state: mean,
|
||||
sum: null,
|
||||
});
|
||||
lastVal = mean;
|
||||
currentDate =
|
||||
period === "day"
|
||||
? addDays(currentDate, 1)
|
||||
: period === "month"
|
||||
? addMonths(currentDate, 1)
|
||||
: addHours(currentDate, 1);
|
||||
}
|
||||
return statistics;
|
||||
};
|
||||
|
||||
const generateSumStatistics = (
|
||||
start: Date,
|
||||
end: Date,
|
||||
period: "5minute" | "hour" | "day" | "month" = "hour",
|
||||
initValue: number,
|
||||
maxDiff: number
|
||||
): StatisticValue[] => {
|
||||
const statistics: StatisticValue[] = [];
|
||||
let currentDate = new Date(start);
|
||||
currentDate.setMinutes(0, 0, 0);
|
||||
let sum = initValue;
|
||||
const now = new Date();
|
||||
while (end > currentDate && currentDate < now) {
|
||||
const add = Math.random() * maxDiff;
|
||||
sum += add;
|
||||
statistics.push({
|
||||
start: currentDate.getTime(),
|
||||
end: currentDate.getTime(),
|
||||
mean: null,
|
||||
min: null,
|
||||
max: null,
|
||||
last_reset: 0,
|
||||
state: initValue + sum,
|
||||
sum,
|
||||
});
|
||||
currentDate =
|
||||
period === "day"
|
||||
? addDays(currentDate, 1)
|
||||
: period === "month"
|
||||
? addMonths(currentDate, 1)
|
||||
: addHours(currentDate, 1);
|
||||
}
|
||||
return statistics;
|
||||
};
|
||||
|
||||
const generateCurvedStatistics = (
|
||||
start: Date,
|
||||
end: Date,
|
||||
_period: "5minute" | "hour" | "day" | "month" = "hour",
|
||||
initValue: number,
|
||||
maxDiff: number,
|
||||
metered: boolean
|
||||
): StatisticValue[] => {
|
||||
const statistics: StatisticValue[] = [];
|
||||
let currentDate = new Date(start);
|
||||
currentDate.setMinutes(0, 0, 0);
|
||||
let sum = initValue;
|
||||
const hours = differenceInHours(end, start) - 1;
|
||||
let i = 0;
|
||||
let half = false;
|
||||
const now = new Date();
|
||||
while (end > currentDate && currentDate < now) {
|
||||
const add = Math.random() * maxDiff;
|
||||
sum += i * add;
|
||||
statistics.push({
|
||||
start: currentDate.getTime(),
|
||||
end: currentDate.getTime(),
|
||||
mean: null,
|
||||
min: null,
|
||||
max: null,
|
||||
last_reset: 0,
|
||||
state: initValue + sum,
|
||||
sum: metered ? sum : null,
|
||||
});
|
||||
currentDate = addHours(currentDate, 1);
|
||||
if (!half && i > hours / 2) {
|
||||
half = true;
|
||||
}
|
||||
i += half ? -1 : 1;
|
||||
}
|
||||
return statistics;
|
||||
};
|
||||
|
||||
const statisticsFunctions: Record<
|
||||
string,
|
||||
(
|
||||
id: string,
|
||||
start: Date,
|
||||
end: Date,
|
||||
period: "5minute" | "hour" | "day" | "month"
|
||||
) => StatisticValue[]
|
||||
> = {
|
||||
"sensor.energy_consumption_tarif_1": (
|
||||
_id: string,
|
||||
start: Date,
|
||||
end: Date,
|
||||
period = "hour"
|
||||
) => {
|
||||
if (period !== "hour") {
|
||||
return generateSumStatistics(
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
0,
|
||||
period === "day" ? 17 : 504
|
||||
);
|
||||
}
|
||||
const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000);
|
||||
const morningLow = generateSumStatistics(start, morningEnd, period, 0, 0.7);
|
||||
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
||||
const morningFinalVal = morningLow.length
|
||||
? morningLow[morningLow.length - 1].sum!
|
||||
: 0;
|
||||
const empty = generateSumStatistics(
|
||||
morningEnd,
|
||||
eveningStart,
|
||||
period,
|
||||
morningFinalVal,
|
||||
0
|
||||
);
|
||||
const eveningLow = generateSumStatistics(
|
||||
eveningStart,
|
||||
end,
|
||||
period,
|
||||
morningFinalVal,
|
||||
0.7
|
||||
);
|
||||
return [...morningLow, ...empty, ...eveningLow];
|
||||
},
|
||||
"sensor.energy_consumption_tarif_2": (
|
||||
_id: string,
|
||||
start: Date,
|
||||
end: Date,
|
||||
period = "hour"
|
||||
) => {
|
||||
if (period !== "hour") {
|
||||
return generateSumStatistics(
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
0,
|
||||
period === "day" ? 17 : 504
|
||||
);
|
||||
}
|
||||
const morningEnd = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
||||
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
||||
const highTarif = generateSumStatistics(
|
||||
morningEnd,
|
||||
eveningStart,
|
||||
period,
|
||||
0,
|
||||
0.3
|
||||
);
|
||||
const highTarifFinalVal = highTarif.length
|
||||
? highTarif[highTarif.length - 1].sum!
|
||||
: 0;
|
||||
const morning = generateSumStatistics(start, morningEnd, period, 0, 0);
|
||||
const evening = generateSumStatistics(
|
||||
eveningStart,
|
||||
end,
|
||||
period,
|
||||
highTarifFinalVal,
|
||||
0
|
||||
);
|
||||
return [...morning, ...highTarif, ...evening];
|
||||
},
|
||||
"sensor.energy_production_tarif_1": (_id, start, end, period = "hour") =>
|
||||
generateSumStatistics(start, end, period, 0, 0),
|
||||
"sensor.energy_production_tarif_1_compensation": (
|
||||
_id,
|
||||
start,
|
||||
end,
|
||||
period = "hour"
|
||||
) => generateSumStatistics(start, end, period, 0, 0),
|
||||
"sensor.energy_production_tarif_2": (_id, start, end, period = "hour") => {
|
||||
if (period !== "hour") {
|
||||
return generateSumStatistics(
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
0,
|
||||
period === "day" ? 17 : 504
|
||||
);
|
||||
}
|
||||
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
||||
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
|
||||
const dayEnd = new Date(endOfDay(productionEnd));
|
||||
const production = generateCurvedStatistics(
|
||||
productionStart,
|
||||
productionEnd,
|
||||
period,
|
||||
0,
|
||||
0.15,
|
||||
true
|
||||
);
|
||||
const productionFinalVal = production.length
|
||||
? production[production.length - 1].sum!
|
||||
: 0;
|
||||
const morning = generateSumStatistics(start, productionStart, period, 0, 0);
|
||||
const evening = generateSumStatistics(
|
||||
productionEnd,
|
||||
dayEnd,
|
||||
period,
|
||||
productionFinalVal,
|
||||
0
|
||||
);
|
||||
const rest = generateSumStatistics(
|
||||
dayEnd,
|
||||
end,
|
||||
period,
|
||||
productionFinalVal,
|
||||
1
|
||||
);
|
||||
return [...morning, ...production, ...evening, ...rest];
|
||||
},
|
||||
"sensor.solar_production": (_id, start, end, period = "hour") => {
|
||||
if (period !== "hour") {
|
||||
return generateSumStatistics(
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
0,
|
||||
period === "day" ? 17 : 504
|
||||
);
|
||||
}
|
||||
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
|
||||
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
|
||||
const dayEnd = new Date(endOfDay(productionEnd));
|
||||
const production = generateCurvedStatistics(
|
||||
productionStart,
|
||||
productionEnd,
|
||||
period,
|
||||
0,
|
||||
0.3,
|
||||
true
|
||||
);
|
||||
const productionFinalVal = production.length
|
||||
? production[production.length - 1].sum!
|
||||
: 0;
|
||||
const morning = generateSumStatistics(start, productionStart, period, 0, 0);
|
||||
const evening = generateSumStatistics(
|
||||
productionEnd,
|
||||
dayEnd,
|
||||
period,
|
||||
productionFinalVal,
|
||||
0
|
||||
);
|
||||
const rest = generateSumStatistics(
|
||||
dayEnd,
|
||||
end,
|
||||
period,
|
||||
productionFinalVal,
|
||||
2
|
||||
);
|
||||
return [...morning, ...production, ...evening, ...rest];
|
||||
},
|
||||
};
|
||||
export const mockRecorder = (mockHass: MockHomeAssistant) => {
|
||||
mockHass.mockWS(
|
||||
"recorder/get_statistics_metadata",
|
||||
(): StatisticsMetaData[] => []
|
||||
);
|
||||
mockHass.mockWS(
|
||||
"recorder/list_statistic_ids",
|
||||
(): StatisticsMetaData[] => []
|
||||
);
|
||||
mockHass.mockWS(
|
||||
"recorder/statistics_during_period",
|
||||
({ statistic_ids, start_time, end_time, period }, hass): Statistics => {
|
||||
const start = new Date(start_time);
|
||||
const end = end_time ? new Date(end_time) : new Date();
|
||||
|
||||
const statistics: Record<string, StatisticValue[]> = {};
|
||||
|
||||
statistic_ids.forEach((id: string) => {
|
||||
if (id in statisticsFunctions) {
|
||||
statistics[id] = statisticsFunctions[id](id, start, end, period);
|
||||
} else {
|
||||
const entityState = hass.states[id];
|
||||
const state = entityState ? Number(entityState.state) : 1;
|
||||
statistics[id] =
|
||||
entityState && "last_reset" in entityState.attributes
|
||||
? generateSumStatistics(
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
state,
|
||||
state * (state > 80 ? 0.01 : 0.05)
|
||||
)
|
||||
: generateMeanStatistics(
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
state,
|
||||
state * (state > 80 ? 0.05 : 0.1)
|
||||
);
|
||||
}
|
||||
});
|
||||
return statistics;
|
||||
}
|
||||
);
|
||||
};
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
@@ -1,35 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
TARGET_LABEL="needs design preview"
|
||||
|
||||
if [[ "$NETLIFY" != "true" ]]; then
|
||||
echo "This script can only be run on Netlify"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function createStatus() {
|
||||
state="$1"
|
||||
description="$2"
|
||||
target_url="$3"
|
||||
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" \
|
||||
"https://api.github.com/repos/home-assistant/frontend/statuses/$COMMIT_REF" \
|
||||
-d '{"state": "'"${state}"'", "context": "Netlify/Design Preview Build", "description": "'"$description"'", "target_url": "'"$target_url"'"}'
|
||||
}
|
||||
|
||||
|
||||
if [[ "${PULL_REQUEST}" == "true" ]]; then
|
||||
if [[ "$(curl -sSLf -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" \
|
||||
"https://api.github.com/repos/home-assistant/frontend/pulls/${REVIEW_ID}" | jq '.labels[].name' -r)" =~ "$TARGET_LABEL" ]]; 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_PRIME_URL"
|
||||
else
|
||||
createStatus "error" "Build failed" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
|
||||
fi
|
||||
else
|
||||
createStatus "success" "Build was not requested by PR label"
|
||||
fi
|
||||
elif [[ "$INCOMING_HOOK_BODY" == "NIGHTLY" ]]; then
|
||||
gulp build-gallery
|
||||
fi
|
56
gallery/src/pages/Text/remove-delete-add-create.markdown
Normal file
56
gallery/src/pages/Text/remove-delete-add-create.markdown
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: When to use remove, delete, add and create
|
||||
subtitle: The difference between remove/delete and add/create.
|
||||
---
|
||||
|
||||
# Remove vs Delete
|
||||
Remove and Delete are quite similar, but can be frustrating if used inconsistently.
|
||||
|
||||
## Remove
|
||||
Take away and set aside, but kept in existence.
|
||||
|
||||
For example:
|
||||
* Removing a user's permission
|
||||
* Removing a user from a group
|
||||
* Removing links between items
|
||||
* Removing a widget
|
||||
* Removing a link
|
||||
* Removing an item from a cart
|
||||
|
||||
## Delete
|
||||
Erase, rendered nonexistent or nonrecoverable.
|
||||
|
||||
For example:
|
||||
* Deleting a field
|
||||
* Deleting a value in a field
|
||||
* Deleting a task
|
||||
* Deleting a group
|
||||
* Deleting a permission
|
||||
* Deleting a calendar event
|
||||
|
||||
# Add vs Create
|
||||
In most cases, Create can be paired with Delete, and Add can be paired with Remove.
|
||||
|
||||
## Add
|
||||
An already-exisiting item.
|
||||
|
||||
For example:
|
||||
* Adding a permission to a user
|
||||
* Adding a user to a group
|
||||
* Adding links between items
|
||||
* Adding a widget
|
||||
* Adding a link
|
||||
* Adding an item to a cart
|
||||
|
||||
## Create
|
||||
Something made from scratch.
|
||||
|
||||
For example:
|
||||
* Creating a new field
|
||||
* Creating a new value in a field
|
||||
* Creating a new task
|
||||
* Creating a new group
|
||||
* Creating a new permission
|
||||
* Creating a new calendar event
|
||||
|
||||
Based on this is [UX magazine article](https://uxmag.com/articles/ui-copy-remove-vs-delete2-banner).
|
@@ -36,6 +36,7 @@ const conditions = [
|
||||
{ condition: "sun", after: "sunset" },
|
||||
{ condition: "sun", after: "sunrise", offset: "-01:00" },
|
||||
{ condition: "zone", entity_id: "device_tracker.person", zone: "zone.home" },
|
||||
{ condition: "trigger", id: "motion" },
|
||||
{ condition: "time" },
|
||||
{ condition: "template" },
|
||||
];
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable lit/no-template-arrow */
|
||||
import { LitElement, TemplateResult, html } from "lit";
|
||||
import { LitElement, TemplateResult, html, css } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
@@ -47,6 +47,8 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [
|
||||
class DemoHaAutomationEditorAction extends LitElement {
|
||||
@state() private hass!: HomeAssistant;
|
||||
|
||||
@state() private _disabled = false;
|
||||
|
||||
private data: any = SCHEMAS.map((info) => info.actions);
|
||||
|
||||
constructor() {
|
||||
@@ -67,6 +69,15 @@ class DemoHaAutomationEditorAction extends LitElement {
|
||||
this.requestUpdate();
|
||||
};
|
||||
return html`
|
||||
<div class="options">
|
||||
<ha-formfield label="Disabled">
|
||||
<ha-switch
|
||||
.name=${"disabled"}
|
||||
.checked=${this._disabled}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
${SCHEMAS.map(
|
||||
(info, sampleIdx) => html`
|
||||
<demo-black-white-row
|
||||
@@ -81,6 +92,7 @@ class DemoHaAutomationEditorAction extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.actions=${this.data[sampleIdx]}
|
||||
.sampleIdx=${sampleIdx}
|
||||
.disabled=${this._disabled}
|
||||
@value-changed=${valueChanged}
|
||||
></ha-automation-action>
|
||||
`
|
||||
@@ -90,6 +102,20 @@ class DemoHaAutomationEditorAction extends LitElement {
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleOptionChange(ev) {
|
||||
this[`_${ev.target.name}`] = ev.target.checked;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.options {
|
||||
max-width: 800px;
|
||||
margin: 16px auto;
|
||||
}
|
||||
.options ha-formfield {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable lit/no-template-arrow */
|
||||
import { LitElement, TemplateResult, html } from "lit";
|
||||
import { LitElement, TemplateResult, html, css } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
@@ -83,6 +83,8 @@ const SCHEMAS: { name: string; conditions: ConditionWithShorthand[] }[] = [
|
||||
class DemoHaAutomationEditorCondition extends LitElement {
|
||||
@state() private hass!: HomeAssistant;
|
||||
|
||||
@state() private _disabled = false;
|
||||
|
||||
private data: any = SCHEMAS.map((info) => info.conditions);
|
||||
|
||||
constructor() {
|
||||
@@ -103,6 +105,15 @@ class DemoHaAutomationEditorCondition extends LitElement {
|
||||
this.requestUpdate();
|
||||
};
|
||||
return html`
|
||||
<div class="options">
|
||||
<ha-formfield label="Disabled">
|
||||
<ha-switch
|
||||
.name=${"disabled"}
|
||||
.checked=${this._disabled}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
${SCHEMAS.map(
|
||||
(info, sampleIdx) => html`
|
||||
<demo-black-white-row
|
||||
@@ -117,6 +128,7 @@ class DemoHaAutomationEditorCondition extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.conditions=${this.data[sampleIdx]}
|
||||
.sampleIdx=${sampleIdx}
|
||||
.disabled=${this._disabled}
|
||||
@value-changed=${valueChanged}
|
||||
></ha-automation-condition>
|
||||
`
|
||||
@@ -126,6 +138,20 @@ class DemoHaAutomationEditorCondition extends LitElement {
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleOptionChange(ev) {
|
||||
this[`_${ev.target.name}`] = ev.target.checked;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.options {
|
||||
max-width: 800px;
|
||||
margin: 16px auto;
|
||||
}
|
||||
.options ha-formfield {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable lit/no-template-arrow */
|
||||
import { LitElement, TemplateResult, html } from "lit";
|
||||
import { LitElement, TemplateResult, html, css } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
@@ -107,6 +107,8 @@ const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
|
||||
class DemoHaAutomationEditorTrigger extends LitElement {
|
||||
@state() private hass!: HomeAssistant;
|
||||
|
||||
@state() private _disabled = false;
|
||||
|
||||
private data: any = SCHEMAS.map((info) => info.triggers);
|
||||
|
||||
constructor() {
|
||||
@@ -127,6 +129,15 @@ class DemoHaAutomationEditorTrigger extends LitElement {
|
||||
this.requestUpdate();
|
||||
};
|
||||
return html`
|
||||
<div class="options">
|
||||
<ha-formfield label="Disabled">
|
||||
<ha-switch
|
||||
.name=${"disabled"}
|
||||
.checked=${this._disabled}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
${SCHEMAS.map(
|
||||
(info, sampleIdx) => html`
|
||||
<demo-black-white-row
|
||||
@@ -141,6 +152,7 @@ class DemoHaAutomationEditorTrigger extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.triggers=${this.data[sampleIdx]}
|
||||
.sampleIdx=${sampleIdx}
|
||||
.disabled=${this._disabled}
|
||||
@value-changed=${valueChanged}
|
||||
></ha-automation-trigger>
|
||||
`
|
||||
@@ -150,6 +162,20 @@ class DemoHaAutomationEditorTrigger extends LitElement {
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleOptionChange(ev) {
|
||||
this[`_${ev.target.name}`] = ev.target.checked;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.options {
|
||||
max-width: 800px;
|
||||
margin: 16px auto;
|
||||
}
|
||||
.options ha-formfield {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -2,8 +2,6 @@
|
||||
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.
|
||||
|
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Alerts
|
||||
title: Alert
|
||||
subtitle: An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task.
|
||||
---
|
||||
|
||||
|
3
gallery/src/pages/components/ha-bar-slider.markdown
Normal file
3
gallery/src/pages/components/ha-bar-slider.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Bar Slider
|
||||
---
|
169
gallery/src/pages/components/ha-bar-slider.ts
Normal file
169
gallery/src/pages/components/ha-bar-slider.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import "../../../../src/components/ha-bar-slider";
|
||||
import "../../../../src/components/ha-card";
|
||||
|
||||
const sliders: {
|
||||
id: string;
|
||||
label: string;
|
||||
mode?: "start" | "end" | "cursor";
|
||||
class?: string;
|
||||
}[] = [
|
||||
{
|
||||
id: "slider-start",
|
||||
label: "Slider (start mode)",
|
||||
mode: "start",
|
||||
},
|
||||
{
|
||||
id: "slider-end",
|
||||
label: "Slider (end mode)",
|
||||
mode: "end",
|
||||
},
|
||||
{
|
||||
id: "slider-cursor",
|
||||
label: "Slider (cursor mode)",
|
||||
mode: "cursor",
|
||||
},
|
||||
{
|
||||
id: "slider-start-custom",
|
||||
label: "Slider (start mode) and custom style",
|
||||
mode: "start",
|
||||
class: "custom",
|
||||
},
|
||||
{
|
||||
id: "slider-end-custom",
|
||||
label: "Slider (end mode) and custom style",
|
||||
mode: "end",
|
||||
class: "custom",
|
||||
},
|
||||
{
|
||||
id: "slider-cursor-custom",
|
||||
label: "Slider (cursor mode) and custom style",
|
||||
mode: "cursor",
|
||||
class: "custom",
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-components-ha-bar-slider")
|
||||
export class DemoHaBarSlider extends LitElement {
|
||||
@state() private value = 50;
|
||||
|
||||
@state() private sliderPosition?: number;
|
||||
|
||||
handleValueChanged(e: CustomEvent) {
|
||||
this.value = e.detail.value as number;
|
||||
}
|
||||
|
||||
handleSliderMoved(e: CustomEvent) {
|
||||
this.sliderPosition = e.detail.value as number;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<p><b>Slider values</b></p>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>position</td>
|
||||
<td>${this.sliderPosition ?? "-"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>value</td>
|
||||
<td>${this.value ?? "-"}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ha-card>
|
||||
${repeat(sliders, (slider) => {
|
||||
const { id, label, ...config } = slider;
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<label id=${id}>${label}</label>
|
||||
<pre>Config: ${JSON.stringify(config)}</pre>
|
||||
<ha-bar-slider
|
||||
.value=${this.value}
|
||||
.mode=${config.mode}
|
||||
class=${ifDefined(config.class)}
|
||||
@value-changed=${this.handleValueChanged}
|
||||
@slider-moved=${this.handleSliderMoved}
|
||||
aria-labelledby=${id}
|
||||
>
|
||||
</ha-bar-slider>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
})}
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<p class="title"><b>Vertical</b></p>
|
||||
<div class="vertical-sliders">
|
||||
${repeat(sliders, (slider) => {
|
||||
const { id, label, ...config } = slider;
|
||||
return html`
|
||||
<ha-bar-slider
|
||||
.value=${this.value}
|
||||
.mode=${config.mode}
|
||||
vertical
|
||||
class=${ifDefined(config.class)}
|
||||
@value-changed=${this.handleValueChanged}
|
||||
@slider-moved=${this.handleSliderMoved}
|
||||
aria-label=${label}
|
||||
>
|
||||
</ha-bar-slider>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
label {
|
||||
font-weight: 600;
|
||||
}
|
||||
.custom {
|
||||
--slider-bar-color: #ffcf4c;
|
||||
--slider-bar-background: #ffcf4c64;
|
||||
--slider-bar-thickness: 100px;
|
||||
--slider-bar-border-radius: 24px;
|
||||
}
|
||||
.vertical-sliders {
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
p.title {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.vertical-sliders > *:not(:last-child) {
|
||||
margin-right: 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-bar-slider": DemoHaBarSlider;
|
||||
}
|
||||
}
|
3
gallery/src/pages/components/ha-bar-switch.markdown
Normal file
3
gallery/src/pages/components/ha-bar-switch.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Bar Switch
|
||||
---
|
145
gallery/src/pages/components/ha-bar-switch.ts
Normal file
145
gallery/src/pages/components/ha-bar-switch.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import {
|
||||
mdiGarage,
|
||||
mdiGarageOpen,
|
||||
mdiLightbulb,
|
||||
mdiLightbulbOff,
|
||||
} from "@mdi/js";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import "../../../../src/components/ha-bar-switch";
|
||||
import "../../../../src/components/ha-card";
|
||||
|
||||
const switches: {
|
||||
id: string;
|
||||
label: string;
|
||||
class?: string;
|
||||
reversed?: boolean;
|
||||
disabled?: boolean;
|
||||
}[] = [
|
||||
{
|
||||
id: "switch",
|
||||
label: "Switch",
|
||||
},
|
||||
{
|
||||
id: "switch-reversed",
|
||||
label: "Switch Reversed",
|
||||
reversed: true,
|
||||
},
|
||||
{
|
||||
id: "switch-custom",
|
||||
label: "Switch and custom style",
|
||||
class: "custom",
|
||||
},
|
||||
{
|
||||
id: "switch-disabled",
|
||||
label: "Disabled Switch",
|
||||
disabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-components-ha-bar-switch")
|
||||
export class DemoHaBarSwitch extends LitElement {
|
||||
@state() private checked = false;
|
||||
|
||||
handleValueChanged(e: any) {
|
||||
this.checked = e.target.checked as boolean;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${repeat(switches, (sw) => {
|
||||
const { id, label, ...config } = sw;
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<label id=${id}>${label}</label>
|
||||
<pre>Config: ${JSON.stringify(config)}</pre>
|
||||
<ha-bar-switch
|
||||
.checked=${this.checked}
|
||||
class=${ifDefined(config.class)}
|
||||
@change=${this.handleValueChanged}
|
||||
.pathOn=${mdiLightbulb}
|
||||
.pathOff=${mdiLightbulbOff}
|
||||
aria-labelledby=${id}
|
||||
disabled=${ifDefined(config.disabled)}
|
||||
reversed=${ifDefined(config.reversed)}
|
||||
>
|
||||
</ha-bar-switch>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
})}
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<p class="title"><b>Vertical</b></p>
|
||||
<div class="vertical-switches">
|
||||
${repeat(switches, (sw) => {
|
||||
const { id, label, ...config } = sw;
|
||||
return html`
|
||||
<ha-bar-switch
|
||||
.checked=${this.checked}
|
||||
vertical
|
||||
class=${ifDefined(config.class)}
|
||||
@change=${this.handleValueChanged}
|
||||
aria-label=${label}
|
||||
.pathOn=${mdiGarageOpen}
|
||||
.pathOff=${mdiGarage}
|
||||
disabled=${ifDefined(config.disabled)}
|
||||
reversed=${ifDefined(config.reversed)}
|
||||
>
|
||||
</ha-bar-switch>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
label {
|
||||
font-weight: 600;
|
||||
}
|
||||
.custom {
|
||||
--switch-bar-color-on: var(--rgb-green-color);
|
||||
--switch-bar-color-off: var(--rgb-red-color);
|
||||
--switch-bar-thickness: 100px;
|
||||
--switch-bar-border-radius: 24px;
|
||||
--switch-bar-padding: 6px;
|
||||
--mdc-icon-size: 24px;
|
||||
}
|
||||
.vertical-switches {
|
||||
height: 300px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
p.title {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.vertical-switches > *:not(:last-child) {
|
||||
margin-right: 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-bar-switch": DemoHaBarSwitch;
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
---
|
||||
title: Progress Bars
|
||||
title: Bar
|
||||
subtitle: Can be used to communicate progress of a task.
|
||||
---
|
||||
|
@@ -1,3 +1,3 @@
|
||||
---
|
||||
title: Chips
|
||||
title: Chip
|
||||
---
|
||||
|
@@ -1,11 +1,11 @@
|
||||
---
|
||||
title: Dialgos
|
||||
title: Dialog
|
||||
subtitle: Dialogs provide important prompts in a user flow.
|
||||
---
|
||||
|
||||
# Material Desing 3
|
||||
# Material Design 3
|
||||
|
||||
Our dialogs are based on the latest version of Material Design. Specs and guidelines can be found on it's [website](https://m3.material.io/components/dialogs/overview).
|
||||
Our dialogs are based on the latest version of Material Design. Specs and guidelines can be found on its [website](https://m3.material.io/components/dialogs/overview).
|
||||
|
||||
# Highlighted guidelines
|
||||
|
61
gallery/src/pages/components/ha-gauge.markdown
Normal file
61
gallery/src/pages/components/ha-gauge.markdown
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: Gauge
|
||||
---
|
||||
|
||||
<style>
|
||||
ha-gauge {
|
||||
display: block;
|
||||
width: 200px;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
</style>
|
||||
|
||||
# Gauge `<ha-gauge>`
|
||||
|
||||
A gauge that can be used to represent sensor data and provide visual feedback about the value and the corresponding severity (success, warning, error).
|
||||
|
||||
## Examples
|
||||
|
||||
Info color gauge
|
||||
<ha-gauge value="75" style="--gauge-color: var(--info-color)"></ha-gauge>
|
||||
|
||||
Success color gauge
|
||||
<ha-gauge value="25" style="--gauge-color: var(--success-color)" label="°C"></ha-gauge>
|
||||
|
||||
Warning color gauge
|
||||
<ha-gauge value="50" style="--gauge-color: var(--warning-color)" label="°C"></ha-gauge>
|
||||
|
||||
Error color gauge
|
||||
<ha-gauge value="75" style="--gauge-color: var(--error-color)" label="°C"></ha-gauge>
|
||||
|
||||
Gauge with background color
|
||||
<ha-gauge value="75" style="--gauge-color: var(--info-color); --primary-background-color: lightgray"></ha-gauge>
|
||||
|
||||
|
||||
## CSS variables
|
||||
|
||||
### Gauge
|
||||
|
||||
`primary-background-color`
|
||||
Background color of the dial (rounded arch)
|
||||
|
||||
`primary-text-color`
|
||||
Text color below dial (value and unit of measurement) plus needle color (if gauge is in needle mode)
|
||||
|
||||
#### Dial colors
|
||||
|
||||
`gauge-color`
|
||||
Used in the coding to control what color the gauge value is rendered with, but cannot be set via theme since its value will dynamically be set (either to `info-color` or to the matching severity variable if the severity color mode is used). To control the used colors, adjust the following variables.
|
||||
|
||||
`success-color`
|
||||
Dial color for the "green" severity level
|
||||
|
||||
`warning-color`
|
||||
Dial color for the "yellow" severity level
|
||||
|
||||
`error-color`
|
||||
Dial color for the "red" severity level
|
||||
|
||||
`info-color`
|
||||
Static dial color if not in severity color mode
|
1
gallery/src/pages/components/ha-gauge.ts
Normal file
1
gallery/src/pages/components/ha-gauge.ts
Normal file
@@ -0,0 +1 @@
|
||||
import "../../../../src/components/ha-gauge";
|
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Selectors
|
||||
title: Selector
|
||||
---
|
||||
|
||||
See the website for [list of available selectors](https://www.home-assistant.io/docs/blueprint/selectors/).
|
||||
|
@@ -195,6 +195,48 @@ const SCHEMAS: {
|
||||
},
|
||||
},
|
||||
},
|
||||
select_disabled_list: {
|
||||
name: "Select disabled option",
|
||||
selector: {
|
||||
select: {
|
||||
options: [
|
||||
{ label: "Option 1", value: "Option 1" },
|
||||
{ label: "Option 2", value: "Option 2" },
|
||||
{ label: "Option 3", value: "Option 3", disabled: true },
|
||||
],
|
||||
mode: "list",
|
||||
},
|
||||
},
|
||||
},
|
||||
select_disabled_multiple: {
|
||||
name: "Select disabled option",
|
||||
selector: {
|
||||
select: {
|
||||
multiple: true,
|
||||
options: [
|
||||
{ label: "Option 1", value: "Option 1" },
|
||||
{ label: "Option 2", value: "Option 2" },
|
||||
{ label: "Option 3", value: "Option 3", disabled: true },
|
||||
],
|
||||
mode: "list",
|
||||
},
|
||||
},
|
||||
},
|
||||
select_disabled: {
|
||||
name: "Select disabled option",
|
||||
selector: {
|
||||
select: {
|
||||
options: [
|
||||
{ label: "Option 1", value: "Option 1" },
|
||||
{ label: "Option 2", value: "Option 2" },
|
||||
{ label: "Option 3", value: "Option 3", disabled: true },
|
||||
{ label: "Option 4", value: "Option 4", disabled: true },
|
||||
{ label: "Option 5", value: "Option 5", disabled: true },
|
||||
{ label: "Option 6", value: "Option 6" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
select_custom: {
|
||||
name: "Select (Custom)",
|
||||
selector: {
|
||||
|
39
gallery/src/pages/components/ha-switch.markdown
Normal file
39
gallery/src/pages/components/ha-switch.markdown
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Switch / Toggle
|
||||
---
|
||||
|
||||
<style>
|
||||
ha-switch {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
# Switch `<ha-switch>`
|
||||
|
||||
A toggle switch can represent two states: on and off.
|
||||
|
||||
## Examples
|
||||
|
||||
Switch in on state
|
||||
<ha-switch checked></ha-switch>
|
||||
|
||||
Switch in off state
|
||||
<ha-switch></ha-switch>
|
||||
|
||||
Disabled switch
|
||||
<ha-switch disabled></ha-switch>
|
||||
|
||||
## CSS variables
|
||||
|
||||
For the switch / toggle there are always two variables, one for the on / checked state and one for the off / unchecked state.
|
||||
|
||||
The track element (background rounded rectangle that the round circular handle travels on) is set to being half transparent, so the final color will also be impacted by the color behind the track.
|
||||
|
||||
`switch-checked-color` / `switch-unchecked-color`
|
||||
Set both the color of the round handle and the track behind it. If you want to control them separately, use the variables below instead.
|
||||
|
||||
`switch-checked-button-color` / `switch-unchecked-button-color`
|
||||
Color of the round handle
|
||||
|
||||
`switch-checked-track-color` / `switch-unchecked-track-color`
|
||||
Color of the track behind the round handle
|
1
gallery/src/pages/components/ha-switch.ts
Normal file
1
gallery/src/pages/components/ha-switch.ts
Normal file
@@ -0,0 +1 @@
|
||||
import "../../../../src/components/ha-switch";
|
@@ -1,3 +1,3 @@
|
||||
---
|
||||
title: Tips
|
||||
title: Tip
|
||||
---
|
||||
|
@@ -98,6 +98,9 @@ const ENTITIES = [
|
||||
minimum: 0,
|
||||
maximum: 10,
|
||||
}),
|
||||
getEntity("text", "message", "Hello!", {
|
||||
friendly_name: "Message",
|
||||
}),
|
||||
|
||||
getEntity("light", "unavailable", "unavailable", {
|
||||
friendly_name: "Bed Light",
|
||||
@@ -129,6 +132,9 @@ const ENTITIES = [
|
||||
friendly_name: "Who cooks",
|
||||
icon: "mdi:cheff",
|
||||
}),
|
||||
getEntity("text", "unavailable", "unavailable", {
|
||||
friendly_name: "Message",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
@@ -147,6 +153,26 @@ const CONFIGS = [
|
||||
- climate.ecobee
|
||||
- input_number.number
|
||||
- sensor.humidity
|
||||
- text.message
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "With enabled state color",
|
||||
config: `
|
||||
- type: entities
|
||||
state_color: true
|
||||
entities:
|
||||
- scene.romantic_lights
|
||||
- device_tracker.demo_paulus
|
||||
- cover.kitchen_window
|
||||
- group.kitchen
|
||||
- lock.kitchen_door
|
||||
- light.bed_light
|
||||
- light.non_existing
|
||||
- climate.ecobee
|
||||
- input_number.number
|
||||
- sensor.humidity
|
||||
- text.message
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -219,6 +245,7 @@ const CONFIGS = [
|
||||
- climate.unavailable
|
||||
- input_number.unavailable
|
||||
- input_select.unavailable
|
||||
- text.unavailable
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@@ -35,11 +35,11 @@ const CONFIGS = [
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Without State",
|
||||
heading: "With State",
|
||||
config: `
|
||||
- type: button
|
||||
entity: light.bed_light
|
||||
show_state: false
|
||||
show_state: true
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@@ -23,13 +23,12 @@ const CONFIGS = [
|
||||
heading: "Basic example",
|
||||
config: `
|
||||
- type: gauge
|
||||
title: Humidity
|
||||
entity: sensor.outside_humidity
|
||||
name: Outside Humidity
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Custom Unit of Measurement",
|
||||
heading: "Custom unit of measurement",
|
||||
config: `
|
||||
- type: gauge
|
||||
entity: sensor.outside_temperature
|
||||
@@ -38,7 +37,16 @@ const CONFIGS = [
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Setting Severity Levels",
|
||||
heading: "Rendering needle",
|
||||
config: `
|
||||
- type: gauge
|
||||
entity: sensor.outside_humidity
|
||||
name: Outside Humidity
|
||||
needle: true
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Setting severity levels",
|
||||
config: `
|
||||
- type: gauge
|
||||
entity: sensor.brightness
|
||||
@@ -50,7 +58,7 @@ const CONFIGS = [
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Setting Severity Levels",
|
||||
heading: "Setting severity levels",
|
||||
config: `
|
||||
- type: gauge
|
||||
entity: sensor.brightness_medium
|
||||
@@ -62,7 +70,7 @@ const CONFIGS = [
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Setting Severity Levels",
|
||||
heading: "Setting severity levels",
|
||||
config: `
|
||||
- type: gauge
|
||||
entity: sensor.brightness_high
|
||||
@@ -74,7 +82,7 @@ const CONFIGS = [
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Setting Min (0) and Max (15) Values",
|
||||
heading: "Setting min (0) and mx (15) values",
|
||||
config: `
|
||||
- type: gauge
|
||||
entity: sensor.brightness
|
||||
@@ -84,14 +92,14 @@ const CONFIGS = [
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Invalid Entity",
|
||||
heading: "Invalid entity",
|
||||
config: `
|
||||
- type: gauge
|
||||
entity: sensor.invalid_entity
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Non-Numeric Value",
|
||||
heading: "Non-numeric value",
|
||||
config: `
|
||||
- type: gauge
|
||||
entity: plant.bonsai
|
||||
|
@@ -62,6 +62,21 @@ const CONFIGS = [
|
||||
heading: "Basic example",
|
||||
config: `
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "No state colors",
|
||||
config: `
|
||||
- type: glance
|
||||
state_color: false
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
|
@@ -1,3 +1,3 @@
|
||||
---
|
||||
title: Grid And Stack Card
|
||||
title: Grid and Stack Card
|
||||
---
|
||||
|
3
gallery/src/pages/misc/entity-state.markdown
Normal file
3
gallery/src/pages/misc/entity-state.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Entity State
|
||||
---
|
405
gallery/src/pages/misc/entity-state.ts
Normal file
405
gallery/src/pages/misc/entity-state.ts
Normal file
@@ -0,0 +1,405 @@
|
||||
import {
|
||||
HassEntity,
|
||||
HassEntityAttributeBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeDomain } from "../../../../src/common/entity/compute_domain";
|
||||
import { computeStateDisplay } from "../../../../src/common/entity/compute_state_display";
|
||||
import "../../../../src/components/data-table/ha-data-table";
|
||||
import type { DataTableColumnContainer } from "../../../../src/components/data-table/ha-data-table";
|
||||
import "../../../../src/components/entity/state-badge";
|
||||
import "../../../../src/components/ha-chip";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
const SENSOR_DEVICE_CLASSES = [
|
||||
"apparent_power",
|
||||
"aqi",
|
||||
// "battery"
|
||||
"carbon_dioxide",
|
||||
"carbon_monoxide",
|
||||
"current",
|
||||
"date",
|
||||
"distance",
|
||||
"duration",
|
||||
"energy",
|
||||
"frequency",
|
||||
"gas",
|
||||
"humidity",
|
||||
"illuminance",
|
||||
"moisture",
|
||||
"monetary",
|
||||
"nitrogen_dioxide",
|
||||
"nitrogen_monoxide",
|
||||
"nitrous_oxide",
|
||||
"ozone",
|
||||
"pm1",
|
||||
"pm10",
|
||||
"pm25",
|
||||
"power_factor",
|
||||
"power",
|
||||
"precipitation",
|
||||
"precipitation_intensity",
|
||||
"pressure",
|
||||
"reactive_power",
|
||||
"signal_strength",
|
||||
"speed",
|
||||
"sulphur_dioxide",
|
||||
"temperature",
|
||||
"timestamp",
|
||||
"volatile_organic_compounds",
|
||||
"voltage",
|
||||
"volume",
|
||||
"water",
|
||||
"weight",
|
||||
"wind_speed",
|
||||
];
|
||||
|
||||
const BINARY_SENSOR_DEVICE_CLASSES = [
|
||||
"battery",
|
||||
"battery_charging",
|
||||
"carbon_monoxide",
|
||||
"cold",
|
||||
"connectivity",
|
||||
"door",
|
||||
"garage_door",
|
||||
"gas",
|
||||
"heat",
|
||||
"light",
|
||||
"lock",
|
||||
"moisture",
|
||||
"motion",
|
||||
"moving",
|
||||
"occupancy",
|
||||
"opening",
|
||||
"plug",
|
||||
"power",
|
||||
"presence",
|
||||
"problem",
|
||||
"running",
|
||||
"safety",
|
||||
"smoke",
|
||||
"sound",
|
||||
"tamper",
|
||||
"update",
|
||||
"vibration",
|
||||
"window",
|
||||
];
|
||||
|
||||
const ENTITIES: HassEntity[] = [
|
||||
// Alarm control panel
|
||||
createEntity("alarm_control_panel.disarmed", "disarmed"),
|
||||
createEntity("alarm_control_panel.armed_home", "armed_home"),
|
||||
createEntity("alarm_control_panel.armed_away", "armed_away"),
|
||||
createEntity("alarm_control_panel.armed_night", "armed_night"),
|
||||
createEntity("alarm_control_panel.armed_vacation", "armed_vacation"),
|
||||
createEntity(
|
||||
"alarm_control_panel.armed_custom_bypass",
|
||||
"armed_custom_bypass"
|
||||
),
|
||||
createEntity("alarm_control_panel.pending", "pending"),
|
||||
createEntity("alarm_control_panel.arming", "arming"),
|
||||
createEntity("alarm_control_panel.disarming", "disarming"),
|
||||
createEntity("alarm_control_panel.triggered", "triggered"),
|
||||
// Alert
|
||||
createEntity("alert.off", "off"),
|
||||
createEntity("alert.on", "on"),
|
||||
// Automation
|
||||
createEntity("automation.off", "off"),
|
||||
createEntity("automation.on", "on"),
|
||||
// Binary Sensor
|
||||
...BINARY_SENSOR_DEVICE_CLASSES.map((dc) =>
|
||||
createEntity(`binary_sensor.${dc}`, "on", dc)
|
||||
),
|
||||
// Button
|
||||
createEntity("button.restart", "unknown", "restart"),
|
||||
createEntity("button.update", "unknown", "update"),
|
||||
// Calendar
|
||||
createEntity("calendar.off", "off"),
|
||||
createEntity("calendar.on", "on"),
|
||||
// Camera
|
||||
createEntity("camera.idle", "idle"),
|
||||
createEntity("camera.streaming", "streaming"),
|
||||
// Climate
|
||||
createEntity("climate.off", "off"),
|
||||
createEntity("climate.heat", "heat"),
|
||||
createEntity("climate.cool", "cool"),
|
||||
createEntity("climate.heat_cool", "heat_cool"),
|
||||
createEntity("climate.auto", "auto"),
|
||||
createEntity("climate.dry", "dry"),
|
||||
createEntity("climate.fan_only", "fan_only"),
|
||||
createEntity("climate.auto_idle", "auto", undefined, { hvac_action: "idle" }),
|
||||
createEntity("climate.auto_off", "auto", undefined, { hvac_action: "off" }),
|
||||
createEntity("climate.auto_heating", "auto", undefined, {
|
||||
hvac_action: "heating",
|
||||
}),
|
||||
createEntity("climate.auto_cooling", "auto", undefined, {
|
||||
hvac_action: "cooling",
|
||||
}),
|
||||
createEntity("climate.auto_dry", "auto", undefined, {
|
||||
hvac_action: "drying",
|
||||
}),
|
||||
// Cover
|
||||
createEntity("cover.closing", "closing"),
|
||||
createEntity("cover.closed", "closed"),
|
||||
createEntity("cover.opening", "opening"),
|
||||
createEntity("cover.open", "open"),
|
||||
createEntity("cover.awning", "open", "awning"),
|
||||
createEntity("cover.blind", "open", "blind"),
|
||||
createEntity("cover.curtain", "open", "curtain"),
|
||||
createEntity("cover.damper", "open", "damper"),
|
||||
createEntity("cover.door", "open", "door"),
|
||||
createEntity("cover.garage", "open", "garage"),
|
||||
createEntity("cover.gate", "open", "gate"),
|
||||
createEntity("cover.shade", "open", "shade"),
|
||||
createEntity("cover.shutter", "open", "shutter"),
|
||||
createEntity("cover.window", "open", "window"),
|
||||
// Device tracker/person
|
||||
createEntity("device_tracker.not_home", "not_home"),
|
||||
createEntity("device_tracker.home", "home"),
|
||||
createEntity("device_tracker.work", "work"),
|
||||
createEntity("person.home", "home"),
|
||||
createEntity("person.not_home", "not_home"),
|
||||
createEntity("person.work", "work"),
|
||||
// Fan
|
||||
createEntity("fan.off", "off"),
|
||||
createEntity("fan.on", "on"),
|
||||
// Camera
|
||||
createEntity("group.off", "off"),
|
||||
createEntity("group.on", "on"),
|
||||
// Humidifier
|
||||
createEntity("humidifier.off", "off"),
|
||||
createEntity("humidifier.on", "on"),
|
||||
// Helpers
|
||||
createEntity("input_boolean.off", "off"),
|
||||
createEntity("input_boolean.on", "on"),
|
||||
// Light
|
||||
createEntity("light.off", "off"),
|
||||
createEntity("light.on", "on"),
|
||||
// Locks
|
||||
createEntity("lock.unlocked", "unlocked"),
|
||||
createEntity("lock.locked", "locked"),
|
||||
createEntity("lock.locking", "locking"),
|
||||
createEntity("lock.unlocking", "unlocking"),
|
||||
createEntity("lock.jammed", "jammed"),
|
||||
// Media Player
|
||||
createEntity("media_player.off", "off"),
|
||||
createEntity("media_player.on", "on"),
|
||||
createEntity("media_player.idle", "idle"),
|
||||
createEntity("media_player.playing", "playing"),
|
||||
createEntity("media_player.paused", "paused"),
|
||||
createEntity("media_player.standby", "standby"),
|
||||
createEntity("media_player.buffering", "buffering"),
|
||||
createEntity("media_player.tv_off", "off", "tv"),
|
||||
createEntity("media_player.tv_playing", "playing", "tv"),
|
||||
createEntity("media_player.tv_paused", "paused", "tv"),
|
||||
createEntity("media_player.tv_standby", "standby", "tv"),
|
||||
createEntity("media_player.receiver_off", "off", "receiver"),
|
||||
createEntity("media_player.receiver_playing", "playing", "receiver"),
|
||||
createEntity("media_player.receiver_paused", "paused", "receiver"),
|
||||
createEntity("media_player.receiver_standby", "standby", "receiver"),
|
||||
createEntity("media_player.speaker_off", "off", "speaker"),
|
||||
createEntity("media_player.speaker_playing", "playing", "speaker"),
|
||||
createEntity("media_player.speaker_paused", "paused", "speaker"),
|
||||
createEntity("media_player.speaker_standby", "standby", "speaker"),
|
||||
// Remote
|
||||
createEntity("remote.off", "off"),
|
||||
createEntity("remote.on", "on"),
|
||||
// Script
|
||||
createEntity("script.off", "off"),
|
||||
createEntity("script.on", "on"),
|
||||
// Sensor
|
||||
...SENSOR_DEVICE_CLASSES.map((dc) => createEntity(`sensor.${dc}`, "10", dc)),
|
||||
// Battery sensor
|
||||
...[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100].map((value) =>
|
||||
createEntity(`sensor.battery_${value}`, value.toString(), "battery")
|
||||
),
|
||||
// Siren
|
||||
createEntity("siren.off", "off"),
|
||||
createEntity("siren.on", "on"),
|
||||
// Switch
|
||||
createEntity("switch.off", "off"),
|
||||
createEntity("switch.on", "on"),
|
||||
createEntity("switch.outlet_off", "off", "outlet"),
|
||||
createEntity("switch.outlet_on", "on", "outlet"),
|
||||
createEntity("switch.switch_off", "off", "switch"),
|
||||
createEntity("switch.switch_on", "on", "switch"),
|
||||
// Timer
|
||||
createEntity("timer.idle", "idle"),
|
||||
createEntity("timer.active", "active"),
|
||||
createEntity("timer.paused", "paused"),
|
||||
// Vacuum
|
||||
createEntity("vacuum.docked", "docked"),
|
||||
createEntity("vacuum.cleaning", "cleaning"),
|
||||
createEntity("vacuum.paused", "paused"),
|
||||
createEntity("vacuum.idle", "idle"),
|
||||
createEntity("vacuum.returning", "returning"),
|
||||
createEntity("vacuum.error", "error"),
|
||||
createEntity("vacuum.cleaning", "cleaning"),
|
||||
createEntity("vacuum.off", "off"),
|
||||
createEntity("vacuum.on", "on"),
|
||||
// Update
|
||||
createEntity("update.off", "off", undefined, {
|
||||
installed_version: "1.0.0",
|
||||
latest_version: "2.0.0",
|
||||
}),
|
||||
createEntity("update.on", "on", undefined, {
|
||||
installed_version: "1.0.0",
|
||||
latest_version: "2.0.0",
|
||||
}),
|
||||
createEntity("update.installing", "on", undefined, {
|
||||
installed_version: "1.0.0",
|
||||
latest_version: "2.0.0",
|
||||
in_progress: true,
|
||||
}),
|
||||
createEntity("update.off", "off", "firmware", {
|
||||
installed_version: "1.0.0",
|
||||
latest_version: "2.0.0",
|
||||
}),
|
||||
createEntity("update.on", "on", "firmware", {
|
||||
installed_version: "1.0.0",
|
||||
latest_version: "2.0.0",
|
||||
}),
|
||||
];
|
||||
|
||||
function createEntity(
|
||||
entity_id: string,
|
||||
state: string,
|
||||
device_class?: string,
|
||||
attributes?: HassEntityAttributeBase | HassEntity["attributes"]
|
||||
): HassEntity {
|
||||
return {
|
||||
entity_id,
|
||||
state,
|
||||
attributes: {
|
||||
...attributes,
|
||||
device_class: device_class,
|
||||
},
|
||||
last_changed: new Date().toString(),
|
||||
last_updated: new Date().toString(),
|
||||
context: {
|
||||
id: "1",
|
||||
parent_id: null,
|
||||
user_id: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
type EntityRowData = {
|
||||
stateObj: HassEntity;
|
||||
entity_id: string;
|
||||
state: string;
|
||||
device_class?: string;
|
||||
domain: string;
|
||||
};
|
||||
|
||||
function createRowData(stateObj: HassEntity): EntityRowData {
|
||||
return {
|
||||
stateObj,
|
||||
entity_id: stateObj.entity_id,
|
||||
state: stateObj.state,
|
||||
device_class: stateObj.attributes.device_class,
|
||||
domain: computeDomain(stateObj.entity_id),
|
||||
};
|
||||
}
|
||||
|
||||
@customElement("demo-misc-entity-state")
|
||||
export class DemoEntityState extends LitElement {
|
||||
@property({ attribute: false }) hass?: HomeAssistant;
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(hass: HomeAssistant): DataTableColumnContainer => {
|
||||
const columns: DataTableColumnContainer<EntityRowData> = {
|
||||
icon: {
|
||||
title: "Icon",
|
||||
template: (_, entry) => html`
|
||||
<state-badge
|
||||
.stateObj=${entry.stateObj}
|
||||
.stateColor=${true}
|
||||
></state-badge>
|
||||
`,
|
||||
},
|
||||
entity_id: {
|
||||
title: "Entity id",
|
||||
width: "30%",
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
},
|
||||
state: {
|
||||
title: "State",
|
||||
width: "20%",
|
||||
sortable: true,
|
||||
template: (_, entry) =>
|
||||
html`${computeStateDisplay(
|
||||
hass.localize,
|
||||
entry.stateObj,
|
||||
hass.locale,
|
||||
hass.entities
|
||||
)}`,
|
||||
},
|
||||
device_class: {
|
||||
title: "Device class",
|
||||
template: (dc) => html`${dc ?? "-"}`,
|
||||
width: "20%",
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
},
|
||||
domain: {
|
||||
title: "Domain",
|
||||
template: (_, entry) => html`${computeDomain(entry.entity_id)}`,
|
||||
width: "20%",
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
},
|
||||
};
|
||||
|
||||
return columns;
|
||||
}
|
||||
);
|
||||
|
||||
private _rows = memoizeOne((): EntityRowData[] =>
|
||||
ENTITIES.map(createRowData)
|
||||
);
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-data-table
|
||||
.hass=${this.hass}
|
||||
.columns=${this._columns(this.hass)}
|
||||
.data=${this._rows()}
|
||||
auto-height
|
||||
></ha-data-table>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
.color {
|
||||
display: block;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 10px;
|
||||
background-color: rgb(--color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-misc-entity-state": DemoEntityState;
|
||||
}
|
||||
}
|
@@ -191,10 +191,12 @@ const createEntityRegistryEntries = (
|
||||
hidden_by: null,
|
||||
entity_category: null,
|
||||
entity_id: "binary_sensor.updater",
|
||||
id: "binary_sensor.updater",
|
||||
name: null,
|
||||
icon: null,
|
||||
platform: "updater",
|
||||
has_entity_name: false,
|
||||
unique_id: "updater",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -281,7 +283,7 @@ export class DemoIntegrationCard extends LitElement {
|
||||
.deviceRegistryEntries=${createDeviceRegistryEntries(
|
||||
info.items[0]
|
||||
)}
|
||||
?disabled=${info.disabled}
|
||||
?entryDisabled=${info.disabled}
|
||||
.selectedConfigEntryId=${info.highlight}
|
||||
></ha-integration-card>
|
||||
`
|
||||
|
@@ -1,16 +1,7 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
SUPPORT_OPEN,
|
||||
SUPPORT_STOP,
|
||||
SUPPORT_CLOSE,
|
||||
SUPPORT_SET_POSITION,
|
||||
SUPPORT_OPEN_TILT,
|
||||
SUPPORT_STOP_TILT,
|
||||
SUPPORT_CLOSE_TILT,
|
||||
SUPPORT_SET_TILT_POSITION,
|
||||
} from "../../../../src/data/cover";
|
||||
import { CoverEntityFeature } from "../../../../src/data/cover";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import {
|
||||
@@ -22,113 +13,127 @@ import "../../components/demo-more-infos";
|
||||
const ENTITIES = [
|
||||
getEntity("cover", "position_buttons", "on", {
|
||||
friendly_name: "Position Buttons",
|
||||
supported_features: SUPPORT_OPEN + SUPPORT_STOP + SUPPORT_CLOSE,
|
||||
supported_features:
|
||||
CoverEntityFeature.OPEN +
|
||||
CoverEntityFeature.STOP +
|
||||
CoverEntityFeature.CLOSE,
|
||||
}),
|
||||
getEntity("cover", "position_slider_half", "on", {
|
||||
friendly_name: "Position Half-Open",
|
||||
supported_features:
|
||||
SUPPORT_OPEN + SUPPORT_STOP + SUPPORT_CLOSE + SUPPORT_SET_POSITION,
|
||||
CoverEntityFeature.OPEN +
|
||||
CoverEntityFeature.STOP +
|
||||
CoverEntityFeature.CLOSE +
|
||||
CoverEntityFeature.SET_POSITION,
|
||||
current_position: 50,
|
||||
}),
|
||||
getEntity("cover", "position_slider_open", "on", {
|
||||
friendly_name: "Position Open",
|
||||
supported_features:
|
||||
SUPPORT_OPEN + SUPPORT_STOP + SUPPORT_CLOSE + SUPPORT_SET_POSITION,
|
||||
CoverEntityFeature.OPEN +
|
||||
CoverEntityFeature.STOP +
|
||||
CoverEntityFeature.CLOSE +
|
||||
CoverEntityFeature.SET_POSITION,
|
||||
current_position: 100,
|
||||
}),
|
||||
getEntity("cover", "position_slider_closed", "on", {
|
||||
friendly_name: "Position Closed",
|
||||
supported_features:
|
||||
SUPPORT_OPEN + SUPPORT_STOP + SUPPORT_CLOSE + SUPPORT_SET_POSITION,
|
||||
CoverEntityFeature.OPEN +
|
||||
CoverEntityFeature.STOP +
|
||||
CoverEntityFeature.CLOSE +
|
||||
CoverEntityFeature.SET_POSITION,
|
||||
current_position: 0,
|
||||
}),
|
||||
getEntity("cover", "tilt_buttons", "on", {
|
||||
friendly_name: "Tilt Buttons",
|
||||
supported_features:
|
||||
SUPPORT_OPEN_TILT + SUPPORT_STOP_TILT + SUPPORT_CLOSE_TILT,
|
||||
CoverEntityFeature.OPEN_TILT +
|
||||
CoverEntityFeature.STOP_TILT +
|
||||
CoverEntityFeature.CLOSE_TILT,
|
||||
}),
|
||||
getEntity("cover", "tilt_slider_half", "on", {
|
||||
friendly_name: "Tilt Half-Open",
|
||||
supported_features:
|
||||
SUPPORT_OPEN_TILT +
|
||||
SUPPORT_STOP_TILT +
|
||||
SUPPORT_CLOSE_TILT +
|
||||
SUPPORT_SET_TILT_POSITION,
|
||||
CoverEntityFeature.OPEN_TILT +
|
||||
CoverEntityFeature.STOP_TILT +
|
||||
CoverEntityFeature.CLOSE_TILT +
|
||||
CoverEntityFeature.SET_TILT_POSITION,
|
||||
current_tilt_position: 50,
|
||||
}),
|
||||
getEntity("cover", "tilt_slider_open", "on", {
|
||||
friendly_name: "Tilt Open",
|
||||
supported_features:
|
||||
SUPPORT_OPEN_TILT +
|
||||
SUPPORT_STOP_TILT +
|
||||
SUPPORT_CLOSE_TILT +
|
||||
SUPPORT_SET_TILT_POSITION,
|
||||
CoverEntityFeature.OPEN_TILT +
|
||||
CoverEntityFeature.STOP_TILT +
|
||||
CoverEntityFeature.CLOSE_TILT +
|
||||
CoverEntityFeature.SET_TILT_POSITION,
|
||||
current_tilt_position: 100,
|
||||
}),
|
||||
getEntity("cover", "tilt_slider_closed", "on", {
|
||||
friendly_name: "Tilt Closed",
|
||||
supported_features:
|
||||
SUPPORT_OPEN_TILT +
|
||||
SUPPORT_STOP_TILT +
|
||||
SUPPORT_CLOSE_TILT +
|
||||
SUPPORT_SET_TILT_POSITION,
|
||||
CoverEntityFeature.OPEN_TILT +
|
||||
CoverEntityFeature.STOP_TILT +
|
||||
CoverEntityFeature.CLOSE_TILT +
|
||||
CoverEntityFeature.SET_TILT_POSITION,
|
||||
current_tilt_position: 0,
|
||||
}),
|
||||
getEntity("cover", "position_slider_tilt_slider", "on", {
|
||||
friendly_name: "Both Sliders",
|
||||
supported_features:
|
||||
SUPPORT_OPEN +
|
||||
SUPPORT_STOP +
|
||||
SUPPORT_CLOSE +
|
||||
SUPPORT_SET_POSITION +
|
||||
SUPPORT_OPEN_TILT +
|
||||
SUPPORT_STOP_TILT +
|
||||
SUPPORT_CLOSE_TILT +
|
||||
SUPPORT_SET_TILT_POSITION,
|
||||
CoverEntityFeature.OPEN +
|
||||
CoverEntityFeature.STOP +
|
||||
CoverEntityFeature.CLOSE +
|
||||
CoverEntityFeature.SET_POSITION +
|
||||
CoverEntityFeature.OPEN_TILT +
|
||||
CoverEntityFeature.STOP_TILT +
|
||||
CoverEntityFeature.CLOSE_TILT +
|
||||
CoverEntityFeature.SET_TILT_POSITION,
|
||||
current_position: 30,
|
||||
current_tilt_position: 70,
|
||||
}),
|
||||
getEntity("cover", "position_tilt_slider", "on", {
|
||||
friendly_name: "Position & Tilt Slider",
|
||||
supported_features:
|
||||
SUPPORT_OPEN +
|
||||
SUPPORT_STOP +
|
||||
SUPPORT_CLOSE +
|
||||
SUPPORT_OPEN_TILT +
|
||||
SUPPORT_STOP_TILT +
|
||||
SUPPORT_CLOSE_TILT +
|
||||
SUPPORT_SET_TILT_POSITION,
|
||||
CoverEntityFeature.OPEN +
|
||||
CoverEntityFeature.STOP +
|
||||
CoverEntityFeature.CLOSE +
|
||||
CoverEntityFeature.OPEN_TILT +
|
||||
CoverEntityFeature.STOP_TILT +
|
||||
CoverEntityFeature.CLOSE_TILT +
|
||||
CoverEntityFeature.SET_TILT_POSITION,
|
||||
current_tilt_position: 70,
|
||||
}),
|
||||
getEntity("cover", "position_slider_tilt", "on", {
|
||||
friendly_name: "Position Slider & Tilt",
|
||||
supported_features:
|
||||
SUPPORT_OPEN +
|
||||
SUPPORT_STOP +
|
||||
SUPPORT_CLOSE +
|
||||
SUPPORT_SET_POSITION +
|
||||
SUPPORT_OPEN_TILT +
|
||||
SUPPORT_STOP_TILT +
|
||||
SUPPORT_CLOSE_TILT,
|
||||
CoverEntityFeature.OPEN +
|
||||
CoverEntityFeature.STOP +
|
||||
CoverEntityFeature.CLOSE +
|
||||
CoverEntityFeature.SET_POSITION +
|
||||
CoverEntityFeature.OPEN_TILT +
|
||||
CoverEntityFeature.STOP_TILT +
|
||||
CoverEntityFeature.CLOSE_TILT,
|
||||
current_position: 30,
|
||||
}),
|
||||
getEntity("cover", "position_slider_only_tilt_slider", "on", {
|
||||
friendly_name: "Position Slider Only & Tilt Buttons",
|
||||
supported_features:
|
||||
SUPPORT_SET_POSITION +
|
||||
SUPPORT_OPEN_TILT +
|
||||
SUPPORT_STOP_TILT +
|
||||
SUPPORT_CLOSE_TILT,
|
||||
CoverEntityFeature.SET_POSITION +
|
||||
CoverEntityFeature.OPEN_TILT +
|
||||
CoverEntityFeature.STOP_TILT +
|
||||
CoverEntityFeature.CLOSE_TILT,
|
||||
current_position: 30,
|
||||
}),
|
||||
getEntity("cover", "position_slider_only_tilt", "on", {
|
||||
friendly_name: "Position Slider Only & Tilt",
|
||||
supported_features:
|
||||
SUPPORT_SET_POSITION +
|
||||
SUPPORT_OPEN_TILT +
|
||||
SUPPORT_STOP_TILT +
|
||||
SUPPORT_CLOSE_TILT +
|
||||
SUPPORT_SET_TILT_POSITION,
|
||||
CoverEntityFeature.SET_POSITION +
|
||||
CoverEntityFeature.OPEN_TILT +
|
||||
CoverEntityFeature.STOP_TILT +
|
||||
CoverEntityFeature.CLOSE_TILT +
|
||||
CoverEntityFeature.SET_TILT_POSITION,
|
||||
current_position: 30,
|
||||
current_tilt_position: 70,
|
||||
}),
|
||||
|
3
gallery/src/pages/more-info/input-number.markdown
Normal file
3
gallery/src/pages/more-info/input-number.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Input Number
|
||||
---
|
60
gallery/src/pages/more-info/input-number.ts
Normal file
60
gallery/src/pages/more-info/input-number.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
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";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("input_number", "box1", 0, {
|
||||
friendly_name: "Box1",
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
initial: 0,
|
||||
mode: "box",
|
||||
unit_of_measurement: "items",
|
||||
}),
|
||||
getEntity("input_number", "slider1", 0, {
|
||||
friendly_name: "Slider1",
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
initial: 0,
|
||||
mode: "slider",
|
||||
unit_of_measurement: "items",
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-more-info-input-number")
|
||||
class DemoMoreInfoInputNumber 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);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-more-info-input-number": DemoMoreInfoInputNumber;
|
||||
}
|
||||
}
|
@@ -1,12 +1,7 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
LightColorModes,
|
||||
SUPPORT_EFFECT,
|
||||
SUPPORT_FLASH,
|
||||
SUPPORT_TRANSITION,
|
||||
} from "../../../../src/data/light";
|
||||
import { LightColorMode, LightEntityFeature } from "../../../../src/data/light";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import {
|
||||
@@ -22,8 +17,8 @@ const ENTITIES = [
|
||||
getEntity("light", "kitchen_light", "on", {
|
||||
friendly_name: "Brightness Light",
|
||||
brightness: 200,
|
||||
supported_color_modes: [LightColorModes.BRIGHTNESS],
|
||||
color_mode: LightColorModes.BRIGHTNESS,
|
||||
supported_color_modes: [LightColorMode.BRIGHTNESS],
|
||||
color_mode: LightColorMode.BRIGHTNESS,
|
||||
}),
|
||||
getEntity("light", "color_temperature_light", "on", {
|
||||
friendly_name: "White Color Temperature Light",
|
||||
@@ -32,10 +27,10 @@ const ENTITIES = [
|
||||
min_mireds: 30,
|
||||
max_mireds: 150,
|
||||
supported_color_modes: [
|
||||
LightColorModes.BRIGHTNESS,
|
||||
LightColorModes.COLOR_TEMP,
|
||||
LightColorMode.BRIGHTNESS,
|
||||
LightColorMode.COLOR_TEMP,
|
||||
],
|
||||
color_mode: LightColorModes.COLOR_TEMP,
|
||||
color_mode: LightColorMode.COLOR_TEMP,
|
||||
}),
|
||||
getEntity("light", "color_hs_light", "on", {
|
||||
friendly_name: "Color HS Light",
|
||||
@@ -44,13 +39,16 @@ const ENTITIES = [
|
||||
rgb_color: [30, 100, 255],
|
||||
min_mireds: 30,
|
||||
max_mireds: 150,
|
||||
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
|
||||
supported_features:
|
||||
LightEntityFeature.EFFECT +
|
||||
LightEntityFeature.FLASH +
|
||||
LightEntityFeature.TRANSITION,
|
||||
supported_color_modes: [
|
||||
LightColorModes.BRIGHTNESS,
|
||||
LightColorModes.COLOR_TEMP,
|
||||
LightColorModes.HS,
|
||||
LightColorMode.BRIGHTNESS,
|
||||
LightColorMode.COLOR_TEMP,
|
||||
LightColorMode.HS,
|
||||
],
|
||||
color_mode: LightColorModes.HS,
|
||||
color_mode: LightColorMode.HS,
|
||||
effect_list: ["random", "colorloop"],
|
||||
}),
|
||||
getEntity("light", "color_rgb_ct_light", "on", {
|
||||
@@ -59,22 +57,28 @@ const ENTITIES = [
|
||||
color_temp: 75,
|
||||
min_mireds: 30,
|
||||
max_mireds: 150,
|
||||
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
|
||||
supported_features:
|
||||
LightEntityFeature.EFFECT +
|
||||
LightEntityFeature.FLASH +
|
||||
LightEntityFeature.TRANSITION,
|
||||
supported_color_modes: [
|
||||
LightColorModes.BRIGHTNESS,
|
||||
LightColorModes.COLOR_TEMP,
|
||||
LightColorModes.RGB,
|
||||
LightColorMode.BRIGHTNESS,
|
||||
LightColorMode.COLOR_TEMP,
|
||||
LightColorMode.RGB,
|
||||
],
|
||||
color_mode: LightColorModes.COLOR_TEMP,
|
||||
color_mode: LightColorMode.COLOR_TEMP,
|
||||
effect_list: ["random", "colorloop"],
|
||||
}),
|
||||
getEntity("light", "color_RGB_light", "on", {
|
||||
friendly_name: "Color Effects Light",
|
||||
brightness: 255,
|
||||
rgb_color: [30, 100, 255],
|
||||
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
|
||||
supported_color_modes: [LightColorModes.BRIGHTNESS, LightColorModes.RGB],
|
||||
color_mode: LightColorModes.RGB,
|
||||
supported_features:
|
||||
LightEntityFeature.EFFECT +
|
||||
LightEntityFeature.FLASH +
|
||||
LightEntityFeature.TRANSITION,
|
||||
supported_color_modes: [LightColorMode.BRIGHTNESS, LightColorMode.RGB],
|
||||
color_mode: LightColorMode.RGB,
|
||||
effect_list: ["random", "colorloop"],
|
||||
}),
|
||||
getEntity("light", "color_rgbw_light", "on", {
|
||||
@@ -83,13 +87,16 @@ const ENTITIES = [
|
||||
rgbw_color: [30, 100, 255, 125],
|
||||
min_mireds: 30,
|
||||
max_mireds: 150,
|
||||
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
|
||||
supported_features:
|
||||
LightEntityFeature.EFFECT +
|
||||
LightEntityFeature.FLASH +
|
||||
LightEntityFeature.TRANSITION,
|
||||
supported_color_modes: [
|
||||
LightColorModes.BRIGHTNESS,
|
||||
LightColorModes.COLOR_TEMP,
|
||||
LightColorModes.RGBW,
|
||||
LightColorMode.BRIGHTNESS,
|
||||
LightColorMode.COLOR_TEMP,
|
||||
LightColorMode.RGBW,
|
||||
],
|
||||
color_mode: LightColorModes.RGBW,
|
||||
color_mode: LightColorMode.RGBW,
|
||||
effect_list: ["random", "colorloop"],
|
||||
}),
|
||||
getEntity("light", "color_rgbww_light", "on", {
|
||||
@@ -98,13 +105,16 @@ const ENTITIES = [
|
||||
rgbww_color: [30, 100, 255, 125, 10],
|
||||
min_mireds: 30,
|
||||
max_mireds: 150,
|
||||
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
|
||||
supported_features:
|
||||
LightEntityFeature.EFFECT +
|
||||
LightEntityFeature.FLASH +
|
||||
LightEntityFeature.TRANSITION,
|
||||
supported_color_modes: [
|
||||
LightColorModes.BRIGHTNESS,
|
||||
LightColorModes.COLOR_TEMP,
|
||||
LightColorModes.RGBWW,
|
||||
LightColorMode.BRIGHTNESS,
|
||||
LightColorMode.COLOR_TEMP,
|
||||
LightColorMode.RGBWW,
|
||||
],
|
||||
color_mode: LightColorModes.RGBWW,
|
||||
color_mode: LightColorMode.RGBWW,
|
||||
effect_list: ["random", "colorloop"],
|
||||
}),
|
||||
getEntity("light", "color_xy_light", "on", {
|
||||
@@ -114,13 +124,16 @@ const ENTITIES = [
|
||||
rgb_color: [30, 100, 255],
|
||||
min_mireds: 30,
|
||||
max_mireds: 150,
|
||||
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
|
||||
supported_features:
|
||||
LightEntityFeature.EFFECT +
|
||||
LightEntityFeature.FLASH +
|
||||
LightEntityFeature.TRANSITION,
|
||||
supported_color_modes: [
|
||||
LightColorModes.BRIGHTNESS,
|
||||
LightColorModes.COLOR_TEMP,
|
||||
LightColorModes.XY,
|
||||
LightColorMode.BRIGHTNESS,
|
||||
LightColorMode.COLOR_TEMP,
|
||||
LightColorMode.XY,
|
||||
],
|
||||
color_mode: LightColorModes.XY,
|
||||
color_mode: LightColorMode.XY,
|
||||
effect_list: ["random", "colorloop"],
|
||||
}),
|
||||
];
|
||||
|
@@ -139,6 +139,13 @@ const ENTITIES = [
|
||||
title: undefined,
|
||||
friendly_name: "Installing without title",
|
||||
}),
|
||||
getEntity("update", "update21", "on", {
|
||||
...base_attributes,
|
||||
in_progress: true,
|
||||
friendly_name: "Update with in_progress true and UPDATE_SUPPORT_PROGRESS",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-more-info-update")
|
||||
|
0
hassio/.gitignore
vendored
0
hassio/.gitignore
vendored
@@ -118,7 +118,7 @@ class HassioAddonRepositoryEl extends LitElement {
|
||||
}
|
||||
|
||||
private _addonTapped(ev) {
|
||||
navigate(`/hassio/addon/${ev.currentTarget.addon.slug}`);
|
||||
navigate(`/hassio/addon/${ev.currentTarget.addon.slug}?store=true`);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@@ -53,7 +53,13 @@ class HassioAddonDashboard extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@state() _error?: string;
|
||||
@state() private _error?: string;
|
||||
|
||||
private _backPath = new URLSearchParams(window.parent.location.search).get(
|
||||
"store"
|
||||
)
|
||||
? "/hassio/store"
|
||||
: "/hassio/dashboard";
|
||||
|
||||
private _computeTail = memoizeOne((route: Route) => {
|
||||
const dividerPos = route.path.indexOf("/", 1);
|
||||
@@ -119,6 +125,7 @@ class HassioAddonDashboard extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
.route=${route}
|
||||
.tabs=${addonTabs}
|
||||
.backPath=${this._backPath}
|
||||
supervisor
|
||||
>
|
||||
<span slot="header">${this.addon.name}</span>
|
||||
|
@@ -1024,10 +1024,13 @@ class HassioAddonInfo extends LitElement {
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.addon.name,
|
||||
text: "Are you sure you want to uninstall this add-on?",
|
||||
confirmText: "uninstall add-on",
|
||||
dismissText: "no",
|
||||
title: this.supervisor.localize("dialog.uninstall_addon.title", {
|
||||
name: this.addon.name,
|
||||
}),
|
||||
text: this.supervisor.localize("dialog.uninstall_addon.text"),
|
||||
confirmText: this.supervisor.localize("dialog.uninstall_addon.uninstall"),
|
||||
dismissText: this.supervisor.localize("common.cancel"),
|
||||
destructive: true,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
|
@@ -119,6 +119,7 @@ export class HassioBackups extends LitElement {
|
||||
(narrow: boolean): DataTableColumnContainer => ({
|
||||
name: {
|
||||
title: this.supervisor.localize("backup.name"),
|
||||
main: true,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
grows: true,
|
||||
|
@@ -28,6 +28,7 @@ class HassioDashboard extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
back-path="/config"
|
||||
.header=${this.supervisor.localize("panel.addons")}
|
||||
>
|
||||
<hassio-addons
|
||||
|
@@ -18,9 +18,11 @@ export const suggestAddonRestart = async (
|
||||
addon: HassioAddonDetails
|
||||
): Promise<void> => {
|
||||
const confirmed = await showConfirmationDialog(element, {
|
||||
title: supervisor.localize("common.restart_name", "name", addon.name),
|
||||
title: supervisor.localize("dialog.restart_addon.title", {
|
||||
name: addon.name,
|
||||
}),
|
||||
text: supervisor.localize("dialog.restart_addon.text"),
|
||||
confirmText: supervisor.localize("dialog.restart_addon.confirm_text"),
|
||||
confirmText: supervisor.localize("dialog.restart_addon.restart"),
|
||||
dismissText: supervisor.localize("common.cancel"),
|
||||
});
|
||||
if (confirmed) {
|
||||
@@ -28,11 +30,9 @@ export const suggestAddonRestart = async (
|
||||
await restartHassioAddon(hass, addon.slug);
|
||||
} catch (err: any) {
|
||||
showAlertDialog(element, {
|
||||
title: supervisor.localize(
|
||||
"common.failed_to_restart_name",
|
||||
"name",
|
||||
addon.name
|
||||
),
|
||||
title: supervisor.localize("common.failed_to_restart_name", {
|
||||
name: addon.name,
|
||||
}),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { showJoinBetaDialog } from "../../../src/panels/config/core/updates/show-dialog-join-beta";
|
||||
import {
|
||||
UNHEALTHY_REASON_URL,
|
||||
UNSUPPORTED_REASON_URL,
|
||||
@@ -230,36 +231,27 @@ class HassioSupervisorInfo extends LitElement {
|
||||
button.progress = true;
|
||||
|
||||
if (this.supervisor.supervisor.channel === "stable") {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.supervisor.localize("system.supervisor.warning"),
|
||||
text: html`${this.supervisor.localize("system.supervisor.beta_warning")}
|
||||
<br />
|
||||
<b> ${this.supervisor.localize("system.supervisor.beta_backup")} </b>
|
||||
<br /><br />
|
||||
${this.supervisor.localize("system.supervisor.beta_release_items")}
|
||||
<ul>
|
||||
<li>Home Assistant Core</li>
|
||||
<li>Home Assistant Supervisor</li>
|
||||
<li>Home Assistant Operating System</li>
|
||||
</ul>
|
||||
<br />
|
||||
${this.supervisor.localize("system.supervisor.beta_join_confirm")}`,
|
||||
confirmText: this.supervisor.localize(
|
||||
"system.supervisor.join_beta_action"
|
||||
),
|
||||
dismissText: this.supervisor.localize("common.cancel"),
|
||||
showJoinBetaDialog(this, {
|
||||
join: async () => {
|
||||
await this._setChannel("beta");
|
||||
button.progress = false;
|
||||
},
|
||||
cancel: () => {
|
||||
button.progress = false;
|
||||
},
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
await this._setChannel("stable");
|
||||
button.progress = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _setChannel(
|
||||
channel: SupervisorOptions["channel"]
|
||||
): Promise<void> {
|
||||
try {
|
||||
const data: Partial<SupervisorOptions> = {
|
||||
channel:
|
||||
this.supervisor.supervisor.channel === "stable" ? "beta" : "stable",
|
||||
channel,
|
||||
};
|
||||
await setSupervisorOption(this.hass, data);
|
||||
await this._reloadSupervisor();
|
||||
@@ -270,8 +262,6 @@ class HassioSupervisorInfo extends LitElement {
|
||||
),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
} finally {
|
||||
button.progress = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,11 +1,8 @@
|
||||
module.exports = {
|
||||
"*.{js,ts}": [
|
||||
"prettier --write",
|
||||
'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix',
|
||||
],
|
||||
"*.{js,ts}": ["prettier --write", "eslint --fix"],
|
||||
"!(/translations)*.{json,css,md,html}": "prettier --write",
|
||||
"translations/*/*.json": (files) =>
|
||||
'printf "%s\n" "These files should not be modified. Instead, make the necessary modifications in src/translations/en.json. Please see translations/README.md for details." ' +
|
||||
'printf "%s\n" "Translation files should not be added or modified here. Instead, make the necessary modifications in src/translations/en.json. Other languages are managed externally. Please see https://developers.home-assistant.io/docs/translations/ for details." ' +
|
||||
files.join(" ") +
|
||||
" >&2 && exit 1",
|
||||
};
|
||||
|
55
package.json
55
package.json
@@ -24,7 +24,7 @@
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^5.0.2",
|
||||
"@braintree/sanitize-url": "^6.0.0",
|
||||
"@codemirror/autocomplete": "^0.19.12",
|
||||
"@codemirror/commands": "^0.19.8",
|
||||
"@codemirror/gutter": "^0.19.9",
|
||||
@@ -43,7 +43,6 @@
|
||||
"@formatjs/intl-numberformat": "^7.2.5",
|
||||
"@formatjs/intl-pluralrules": "^4.1.5",
|
||||
"@formatjs/intl-relativetimeformat": "^9.3.2",
|
||||
"@formatjs/intl-utils": "^3.8.4",
|
||||
"@fullcalendar/common": "5.9.0",
|
||||
"@fullcalendar/core": "5.9.0",
|
||||
"@fullcalendar/daygrid": "5.9.0",
|
||||
@@ -93,15 +92,15 @@
|
||||
"@polymer/paper-tooltip": "^3.0.1",
|
||||
"@polymer/polymer": "3.4.1",
|
||||
"@thomasloven/round-slider": "0.5.4",
|
||||
"@vaadin/combo-box": "^23.1.5",
|
||||
"@vaadin/vaadin-themable-mixin": "^23.1.5",
|
||||
"@vaadin/combo-box": "^23.2.9",
|
||||
"@vaadin/vaadin-themable-mixin": "^23.2.9",
|
||||
"@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",
|
||||
"app-datepicker": "^5.1.0",
|
||||
"chart.js": "^3.3.2",
|
||||
"comlink": "^4.3.1",
|
||||
"core-js": "^3.15.2",
|
||||
@@ -111,8 +110,9 @@
|
||||
"deep-freeze": "^0.0.1",
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^1.2.1",
|
||||
"home-assistant-js-websocket": "^8.0.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
"hls.js": "^1.2.5",
|
||||
"home-assistant-js-websocket": "^8.0.1",
|
||||
"idb-keyval": "^5.1.3",
|
||||
"intl-messageformat": "^9.9.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
@@ -129,6 +129,7 @@
|
||||
"regenerator-runtime": "^0.13.8",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"rrule": "^2.7.1",
|
||||
"sortablejs": "^1.14.0",
|
||||
"superstruct": "^0.15.2",
|
||||
"tinykeys": "^1.1.3",
|
||||
@@ -138,6 +139,7 @@
|
||||
"vis-network": "^8.5.4",
|
||||
"vue": "^2.6.12",
|
||||
"vue2-daterange-picker": "^0.5.1",
|
||||
"weekstart": "^1.1.0",
|
||||
"workbox-cacheable-response": "^6.4.2",
|
||||
"workbox-core": "^6.4.2",
|
||||
"workbox-expiration": "^6.4.2",
|
||||
@@ -147,19 +149,21 @@
|
||||
"xss": "^1.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.15.5",
|
||||
"@babel/plugin-external-helpers": "^7.14.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/plugin-proposal-decorators": "^7.15.4",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.15.6",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.14.5",
|
||||
"@babel/core": "^7.20.2",
|
||||
"@babel/plugin-external-helpers": "^7.18.6",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-proposal-decorators": "^7.20.2",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.20.2",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.18.9",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
||||
"@babel/plugin-syntax-top-level-await": "^7.14.5",
|
||||
"@babel/preset-env": "^7.15.6",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@koa/cors": "^3.1.0",
|
||||
"@octokit/auth-oauth-device": "^4.0.2",
|
||||
"@octokit/rest": "^19.0.4",
|
||||
"@open-wc/dev-server-hmr": "^0.0.2",
|
||||
"@rollup/plugin-babel": "^5.2.1",
|
||||
"@rollup/plugin-commonjs": "^11.1.0",
|
||||
@@ -169,6 +173,7 @@
|
||||
"@types/chromecast-caf-receiver": "5.0.12",
|
||||
"@types/chromecast-caf-sender": "^1.0.3",
|
||||
"@types/glob": "^7",
|
||||
"@types/hammerjs": "^2.0.41",
|
||||
"@types/js-yaml": "^4",
|
||||
"@types/leaflet": "^1",
|
||||
"@types/leaflet-draw": "^1",
|
||||
@@ -176,23 +181,23 @@
|
||||
"@types/mocha": "^8",
|
||||
"@types/qrcode": "^1.4.2",
|
||||
"@types/sortablejs": "^1",
|
||||
"@types/tar": "^6",
|
||||
"@types/webspeechapi": "^0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "^4.32.0",
|
||||
"@typescript-eslint/parser": "^4.32.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
||||
"@typescript-eslint/parser": "^5.44.0",
|
||||
"@web/dev-server": "^0.0.24",
|
||||
"@web/dev-server-rollup": "^0.2.11",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-loader": "^9.1.0",
|
||||
"chai": "^4.3.4",
|
||||
"del": "^4.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-airbnb-base": "^14.2.1",
|
||||
"eslint-config-airbnb-typescript": "^14.0.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-webpack": "^0.13.1",
|
||||
"eslint-plugin-disable": "^2.0.1",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-lit": "^1.6.1",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-unused-imports": "^1.1.5",
|
||||
"eslint-plugin-wc": "^1.3.2",
|
||||
"fancy-log": "^1.3.3",
|
||||
@@ -207,6 +212,7 @@
|
||||
"html-minifier": "^4.0.0",
|
||||
"husky": "^8.0.1",
|
||||
"instant-mocha": "^1.3.1",
|
||||
"jszip": "^3.10.1",
|
||||
"lint-staged": "^13.0.3",
|
||||
"lit-analyzer": "^1.2.1",
|
||||
"lodash.template": "^4.5.0",
|
||||
@@ -217,7 +223,7 @@
|
||||
"object-hash": "^2.0.3",
|
||||
"open": "^7.0.4",
|
||||
"pinst": "^3.0.0",
|
||||
"prettier": "^2.4.1",
|
||||
"prettier": "^2.8.1",
|
||||
"require-dir": "^1.2.0",
|
||||
"rollup": "^2.8.2",
|
||||
"rollup-plugin-string": "^3.0.0",
|
||||
@@ -227,9 +233,10 @@
|
||||
"sinon": "^11.0.0",
|
||||
"source-map-url": "^0.4.0",
|
||||
"systemjs": "^6.3.2",
|
||||
"tar": "^6.1.11",
|
||||
"terser-webpack-plugin": "^5.2.4",
|
||||
"ts-lit-plugin": "^1.2.1",
|
||||
"typescript": "^4.4.3",
|
||||
"typescript": "^4.9.3",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"webpack": "^5.55.1",
|
||||
@@ -253,5 +260,5 @@
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "always"
|
||||
},
|
||||
"packageManager": "yarn@3.2.0"
|
||||
"packageManager": "yarn@3.2.3"
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20220816.0"
|
||||
version = "20221213.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@@ -7,4 +7,4 @@ set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Install node modules
|
||||
yarn install
|
||||
yarn install
|
@@ -46,6 +46,14 @@ frontend:
|
||||
# development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
||||
fi
|
||||
|
||||
if [ ! -z "${CODESPACES}" ]; then
|
||||
echo "
|
||||
http:
|
||||
use_x_forwarded_for: true
|
||||
trusted_proxies:
|
||||
- 127.0.0.1
|
||||
" >> "${WD}/config/configuration.yaml"
|
||||
fi
|
||||
fi
|
||||
|
||||
hass -c "${WD}/config"
|
||||
|
9
script/setup_translations
Executable file
9
script/setup_translations
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
# Setup translation fetching during development
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
./node_modules/.bin/gulp setup-and-fetch-nightly-translations
|
@@ -20,24 +20,28 @@ fi
|
||||
# Load token from file if not already in the environment
|
||||
[ -z "${LOKALISE_TOKEN-}" ] && LOKALISE_TOKEN="$(<.lokalise_token)"
|
||||
|
||||
PROJECT_ID="3420425759f6d6d241f598.13594006"
|
||||
LOCAL_DIR="$(pwd)/translations/downloads"
|
||||
FILE_FORMAT=json
|
||||
declare -A PROJECT_ID=( \
|
||||
[frontend]="3420425759f6d6d241f598.13594006" \
|
||||
[backend]="130246255a974bd3b5e8a1.51616605" \
|
||||
)
|
||||
|
||||
mkdir -p ${LOCAL_DIR}
|
||||
|
||||
docker run \
|
||||
-v ${LOCAL_DIR}:/opt/dest/locale \
|
||||
--rm \
|
||||
lokalise/lokalise-cli-2@sha256:f1860b26be22fa73b8c93bc5f8690f2afc867610a42de6fc27adc790e5d4425d lokalise2 \
|
||||
--token ${LOKALISE_TOKEN} \
|
||||
--project-id ${PROJECT_ID} \
|
||||
file download \
|
||||
--export-empty-as skip \
|
||||
--format json \
|
||||
--json-unescaped-slashes=true \
|
||||
--replace-breaks=false \
|
||||
--original-filenames=false \
|
||||
--unzip-to /opt/dest
|
||||
for project in ${!PROJECT_ID[*]}; do
|
||||
LOCAL_DIR=`pwd`/translations/${project}
|
||||
rm -f ${LOCAL_DIR}/* || mkdir -p ${LOCAL_DIR}
|
||||
docker run \
|
||||
-v ${LOCAL_DIR}:/opt/dest/locale \
|
||||
--rm \
|
||||
lokalise/lokalise-cli-2@sha256:f1860b26be22fa73b8c93bc5f8690f2afc867610a42de6fc27adc790e5d4425d \
|
||||
lokalise2 \
|
||||
--token ${LOKALISE_TOKEN} \
|
||||
--project-id ${PROJECT_ID[${project}]} \
|
||||
file download \
|
||||
--export-empty-as skip \
|
||||
--format json \
|
||||
--json-unescaped-slashes=true \
|
||||
--replace-breaks=false \
|
||||
--original-filenames=false \
|
||||
--unzip-to /opt/dest
|
||||
done
|
||||
|
||||
./node_modules/.bin/gulp check-downloaded-translations
|
@@ -15,7 +15,7 @@ import { computeInitialHaFormData } from "../components/ha-form/compute-initial-
|
||||
import "../components/ha-form/ha-form";
|
||||
import "../components/ha-formfield";
|
||||
import "../components/ha-markdown";
|
||||
import { AuthProvider } from "../data/auth";
|
||||
import { AuthProvider, autocompleteLoginFields } from "../data/auth";
|
||||
import {
|
||||
DataEntryFlowStep,
|
||||
DataEntryFlowStepForm,
|
||||
@@ -204,7 +204,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
: html``}
|
||||
<ha-form
|
||||
.data=${this._stepData}
|
||||
.schema=${step.data_schema}
|
||||
.schema=${autocompleteLoginFields(step.data_schema)}
|
||||
.error=${step.errors}
|
||||
.disabled=${this._submitting}
|
||||
.computeLabel=${this._computeLabelCallback(step)}
|
||||
|
@@ -3,6 +3,7 @@ import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { HaFormSchema } from "../components/ha-form/types";
|
||||
import { autocompleteLoginFields } from "../data/auth";
|
||||
import type { DataEntryFlowStep } from "../data/data_entry_flow";
|
||||
|
||||
declare global {
|
||||
@@ -69,7 +70,9 @@ export class HaPasswordManagerPolyfill extends LitElement {
|
||||
aria-hidden="true"
|
||||
@submit=${this._handleSubmit}
|
||||
>
|
||||
${this.step.data_schema.map((input) => this.render_input(input))}
|
||||
${autocompleteLoginFields(this.step.data_schema).map((input) =>
|
||||
this.render_input(input)
|
||||
)}
|
||||
<input type="submit" />
|
||||
<style>
|
||||
${this.styles}
|
||||
@@ -89,8 +92,10 @@ export class HaPasswordManagerPolyfill extends LitElement {
|
||||
<input
|
||||
tabindex="-1"
|
||||
.id=${schema.name}
|
||||
.name=${schema.name}
|
||||
.type=${inputType}
|
||||
.value=${this.stepData[schema.name] || ""}
|
||||
.autocomplete=${schema.autocomplete}
|
||||
@input=${this._valueChanged}
|
||||
/>
|
||||
`;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user