mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-18 05:39:26 +00:00
Compare commits
667 Commits
20240307.0
...
dashboard_
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ebe5207b6e | ||
![]() |
bd1ede4145 | ||
![]() |
321a085c0e | ||
![]() |
6a3041988a | ||
![]() |
23fcdf876c | ||
![]() |
00f325e961 | ||
![]() |
d00b3cfc61 | ||
![]() |
4cc9e74ea8 | ||
![]() |
a56b9a96ce | ||
![]() |
d4b5f4bc14 | ||
![]() |
cf1523ee73 | ||
![]() |
f5d571ca84 | ||
![]() |
362e92f313 | ||
![]() |
5ddf72b973 | ||
![]() |
6e78c28f51 | ||
![]() |
772f0bb669 | ||
![]() |
846c2a848f | ||
![]() |
8495757005 | ||
![]() |
9960d38b91 | ||
![]() |
d3222f8bb0 | ||
![]() |
2e5cce5409 | ||
![]() |
f78946447f | ||
![]() |
eb0579ddc5 | ||
![]() |
686424fc70 | ||
![]() |
039e9b40bd | ||
![]() |
8272bef890 | ||
![]() |
62528b2413 | ||
![]() |
fa24f529e0 | ||
![]() |
43a54f6cda | ||
![]() |
9c153bbd58 | ||
![]() |
27afe9ecb7 | ||
![]() |
72f989e2bd | ||
![]() |
a6ef46565f | ||
![]() |
a35ac09688 | ||
![]() |
27024135ea | ||
![]() |
8759ed740a | ||
![]() |
bb3e8ae33d | ||
![]() |
b5b60c9bf0 | ||
![]() |
3b6a2cf7d8 | ||
![]() |
7e10e14102 | ||
![]() |
a580abab4a | ||
![]() |
11523c08c4 | ||
![]() |
7a8988528b | ||
![]() |
2a6380f083 | ||
![]() |
29881c8bb4 | ||
![]() |
56254ddf03 | ||
![]() |
007ba70641 | ||
![]() |
3e1227b064 | ||
![]() |
067e179f26 | ||
![]() |
9a3f7df25e | ||
![]() |
c7b4e8f37c | ||
![]() |
bfa8b886ab | ||
![]() |
433c00b73a | ||
![]() |
a497f42f73 | ||
![]() |
165723cb5b | ||
![]() |
42b5fa696a | ||
![]() |
59062d96a8 | ||
![]() |
d36bbfe07d | ||
![]() |
0d489213a4 | ||
![]() |
c54acc9369 | ||
![]() |
562bc084f0 | ||
![]() |
6fce2f35a5 | ||
![]() |
f4e24bed2e | ||
![]() |
09969c0e2d | ||
![]() |
4b0181774b | ||
![]() |
272db5e9e8 | ||
![]() |
9ae3a824d9 | ||
![]() |
9db55c9391 | ||
![]() |
59697127c0 | ||
![]() |
565600e945 | ||
![]() |
721eebf367 | ||
![]() |
f5ae842167 | ||
![]() |
3575734ed0 | ||
![]() |
4e8de1f64d | ||
![]() |
cd73b8ac29 | ||
![]() |
74eca6b1f5 | ||
![]() |
9ef0bd6e46 | ||
![]() |
53eb7f771f | ||
![]() |
cd62f064cb | ||
![]() |
db82b856e0 | ||
![]() |
ab340e13e9 | ||
![]() |
22c54b3fea | ||
![]() |
2dd7e598d5 | ||
![]() |
d1ce06e368 | ||
![]() |
9717304b68 | ||
![]() |
c646f3c39a | ||
![]() |
0297ec5a7b | ||
![]() |
e48286c2a0 | ||
![]() |
f2b2da9877 | ||
![]() |
250f87cfd8 | ||
![]() |
9f6afb162a | ||
![]() |
0add65feff | ||
![]() |
c08d9a9166 | ||
![]() |
d9a9038cec | ||
![]() |
6bee3ef45c | ||
![]() |
3a855f95ad | ||
![]() |
e7f3393ec6 | ||
![]() |
d7cb4cb537 | ||
![]() |
c55720c933 | ||
![]() |
f78e757485 | ||
![]() |
fa03c58a93 | ||
![]() |
49f1ad633f | ||
![]() |
d449e10120 | ||
![]() |
474c8c243e | ||
![]() |
e9e53e9451 | ||
![]() |
cfa84f30be | ||
![]() |
7416ae7dfd | ||
![]() |
6f1fa139e7 | ||
![]() |
b38a348957 | ||
![]() |
f97971faf6 | ||
![]() |
c5ae9e8497 | ||
![]() |
c00287c401 | ||
![]() |
c0e048023d | ||
![]() |
431f4937c1 | ||
![]() |
0a55220837 | ||
![]() |
13f01492b4 | ||
![]() |
ce5bcf61f9 | ||
![]() |
d31a777135 | ||
![]() |
5cc08cfe0b | ||
![]() |
3eea7dc6cd | ||
![]() |
a629f01300 | ||
![]() |
f1345af526 | ||
![]() |
064c51f487 | ||
![]() |
d88670034a | ||
![]() |
5fab1969a8 | ||
![]() |
b3e14d449e | ||
![]() |
97206ee8fe | ||
![]() |
7748315fc3 | ||
![]() |
e059ca146b | ||
![]() |
56cabeb497 | ||
![]() |
7a7bd87f50 | ||
![]() |
ff9c794659 | ||
![]() |
2921161336 | ||
![]() |
91e5fcacd5 | ||
![]() |
febbf34de6 | ||
![]() |
5a2977f4d4 | ||
![]() |
7a7a355765 | ||
![]() |
ccebae84a7 | ||
![]() |
f96f38ee82 | ||
![]() |
d4056e6a32 | ||
![]() |
389a7a6ed9 | ||
![]() |
05aecaaaf1 | ||
![]() |
085131d546 | ||
![]() |
c2737d5cec | ||
![]() |
29ae46d775 | ||
![]() |
dfee3ba089 | ||
![]() |
1dbb70b964 | ||
![]() |
1eecc5c0e2 | ||
![]() |
81c0bcff0b | ||
![]() |
6ccbeb8a75 | ||
![]() |
f59ed0a72b | ||
![]() |
a9f00ded0f | ||
![]() |
74730ba201 | ||
![]() |
95b2f7d821 | ||
![]() |
661b14da54 | ||
![]() |
41e34c0d61 | ||
![]() |
5d044a06eb | ||
![]() |
f617426808 | ||
![]() |
3c3d54243c | ||
![]() |
afc624bf4b | ||
![]() |
0991628843 | ||
![]() |
34b9c7b9d1 | ||
![]() |
d52641b495 | ||
![]() |
80c7fd2bf2 | ||
![]() |
e0062cf190 | ||
![]() |
7d2cee650d | ||
![]() |
66560b1f1c | ||
![]() |
a500b582e3 | ||
![]() |
19f94ff8cc | ||
![]() |
0b6994d402 | ||
![]() |
9fe8f507ec | ||
![]() |
2113cf5280 | ||
![]() |
ae9e1b724f | ||
![]() |
9b28c7cf69 | ||
![]() |
d9bac06806 | ||
![]() |
b1e37cb1e1 | ||
![]() |
a2a89502d8 | ||
![]() |
4cc5d2d04b | ||
![]() |
79abcca3b3 | ||
![]() |
043f383a35 | ||
![]() |
d4dd767941 | ||
![]() |
174f1991b1 | ||
![]() |
e15495a626 | ||
![]() |
a8a9a797cb | ||
![]() |
914dbc1e28 | ||
![]() |
111816f08a | ||
![]() |
1b4534890c | ||
![]() |
ed6542469d | ||
![]() |
3774a3d6ba | ||
![]() |
bfa293ae3a | ||
![]() |
9264adb799 | ||
![]() |
829ea4a9e4 | ||
![]() |
be26f8bc24 | ||
![]() |
c864b34a9a | ||
![]() |
099ea61a94 | ||
![]() |
3ebe6027be | ||
![]() |
f5f2a5ad5b | ||
![]() |
d046700d06 | ||
![]() |
2ad84b2832 | ||
![]() |
f9ccb9fc72 | ||
![]() |
6d3940db1e | ||
![]() |
20d174431d | ||
![]() |
1900710e06 | ||
![]() |
ed86a48e1c | ||
![]() |
d2bdb52926 | ||
![]() |
9c57c9f151 | ||
![]() |
9e9cb15a42 | ||
![]() |
6421a9443d | ||
![]() |
f2b43ddad8 | ||
![]() |
e55b59d9b7 | ||
![]() |
4a77359a06 | ||
![]() |
505d7b6ddb | ||
![]() |
79cdc43699 | ||
![]() |
8ff9823cd7 | ||
![]() |
3488c60818 | ||
![]() |
b2af21ba5c | ||
![]() |
12a61a0021 | ||
![]() |
649917cdde | ||
![]() |
3ed27ee853 | ||
![]() |
c1d3a76917 | ||
![]() |
571ed6b9e9 | ||
![]() |
a347315fa7 | ||
![]() |
57d1405115 | ||
![]() |
e5ff6bd2f5 | ||
![]() |
43a422cdca | ||
![]() |
11f2bef05c | ||
![]() |
ff9f331287 | ||
![]() |
cdf64ccdaa | ||
![]() |
8b220acca2 | ||
![]() |
8fdb7fa1d5 | ||
![]() |
008c842431 | ||
![]() |
bc41de0d9c | ||
![]() |
7310c9cf6d | ||
![]() |
84b436c08e | ||
![]() |
1925a47bdc | ||
![]() |
438a426458 | ||
![]() |
f923deb71d | ||
![]() |
e79bc71ab7 | ||
![]() |
11b0990d2b | ||
![]() |
870cb0c65f | ||
![]() |
deda2009f8 | ||
![]() |
b2797ab8da | ||
![]() |
644dcb0381 | ||
![]() |
c65f4f7a6e | ||
![]() |
e2266aa671 | ||
![]() |
68a79490dc | ||
![]() |
6febe8552e | ||
![]() |
f611f23f6f | ||
![]() |
ef4f11fdf8 | ||
![]() |
627e06663b | ||
![]() |
ab01633069 | ||
![]() |
17dcc90638 | ||
![]() |
d0df029ff1 | ||
![]() |
86a7e69812 | ||
![]() |
af9417f2a6 | ||
![]() |
7120ad99b9 | ||
![]() |
334c245b65 | ||
![]() |
bcb72d83b8 | ||
![]() |
c99e0e846b | ||
![]() |
ec3f63e8a3 | ||
![]() |
1bc33a30ec | ||
![]() |
8cca233b7c | ||
![]() |
a78608bfb4 | ||
![]() |
e7c1ac94af | ||
![]() |
1a797b3415 | ||
![]() |
2b27a4da2b | ||
![]() |
1df92fa863 | ||
![]() |
cdde85315a | ||
![]() |
dc67f9faf4 | ||
![]() |
3ad1be50a2 | ||
![]() |
8aadfe7d28 | ||
![]() |
cff54b73a4 | ||
![]() |
b54cfeb0c0 | ||
![]() |
cefe612b11 | ||
![]() |
4bc874b497 | ||
![]() |
f3abaa8e02 | ||
![]() |
21a563fe98 | ||
![]() |
1acbcccd62 | ||
![]() |
35d6c638ab | ||
![]() |
68f8239708 | ||
![]() |
0db64cca0b | ||
![]() |
accfda5f4b | ||
![]() |
c97c20f57d | ||
![]() |
2725d0191d | ||
![]() |
852cc62398 | ||
![]() |
654e3ce437 | ||
![]() |
20a3a00aec | ||
![]() |
22b927d666 | ||
![]() |
709d6be2e3 | ||
![]() |
64f54d9aaa | ||
![]() |
fbda9ca418 | ||
![]() |
4e97e3763e | ||
![]() |
4c9c52d27d | ||
![]() |
87bcd3e471 | ||
![]() |
7e9b01b56d | ||
![]() |
713763fc21 | ||
![]() |
5b7ab1bfcb | ||
![]() |
8712adbf8d | ||
![]() |
4b0d19b615 | ||
![]() |
90e5d259af | ||
![]() |
af3a331f57 | ||
![]() |
67c60a4aa8 | ||
![]() |
62de16bb8e | ||
![]() |
d9b71e754d | ||
![]() |
5fc950f09f | ||
![]() |
0725c7b160 | ||
![]() |
469dbbcccc | ||
![]() |
ffdd661b1f | ||
![]() |
81922f5a3e | ||
![]() |
7e25366897 | ||
![]() |
8ab61b5468 | ||
![]() |
8239f6dd60 | ||
![]() |
45dce18e4d | ||
![]() |
a428ad0655 | ||
![]() |
1b54d51e4a | ||
![]() |
eb1354d229 | ||
![]() |
4d21f9e80c | ||
![]() |
62f46baacf | ||
![]() |
a3090796d2 | ||
![]() |
c34c5d64f9 | ||
![]() |
66228f5858 | ||
![]() |
ac378cfe6d | ||
![]() |
7ecf8b755e | ||
![]() |
141107f1f3 | ||
![]() |
b5277dee53 | ||
![]() |
4b593c1c96 | ||
![]() |
50ce1b94c8 | ||
![]() |
8bf27a83ec | ||
![]() |
389f0d3d23 | ||
![]() |
b966601e6a | ||
![]() |
f2a0881821 | ||
![]() |
50a49eae43 | ||
![]() |
1c04561004 | ||
![]() |
b80d94d260 | ||
![]() |
87012e23e7 | ||
![]() |
f39758b103 | ||
![]() |
697bbf428e | ||
![]() |
c7444a2605 | ||
![]() |
3a5f4d33d2 | ||
![]() |
c3dc62523b | ||
![]() |
424622061a | ||
![]() |
a3b021b11d | ||
![]() |
b60ad8b143 | ||
![]() |
e376efc579 | ||
![]() |
382035a1d4 | ||
![]() |
542e22fe0e | ||
![]() |
af37d57779 | ||
![]() |
fbef0b0186 | ||
![]() |
9e67d6add8 | ||
![]() |
25c702ad2b | ||
![]() |
6516597c93 | ||
![]() |
1df9c38a8c | ||
![]() |
bd7217145a | ||
![]() |
569fef38a4 | ||
![]() |
f21c89cf1a | ||
![]() |
02cc418969 | ||
![]() |
4faba159c0 | ||
![]() |
29816e6c5e | ||
![]() |
5317a11c39 | ||
![]() |
27c53b3241 | ||
![]() |
919befa961 | ||
![]() |
f9c02ed099 | ||
![]() |
b35c325f43 | ||
![]() |
b82f1128fe | ||
![]() |
178feb7330 | ||
![]() |
0118a5bf4c | ||
![]() |
e0087bd142 | ||
![]() |
c2d3e7900e | ||
![]() |
fb8312110b | ||
![]() |
16de57342e | ||
![]() |
ad6e041c04 | ||
![]() |
e22e3e88a0 | ||
![]() |
dc8a50965c | ||
![]() |
1914de7ddf | ||
![]() |
2e505cfb1f | ||
![]() |
ab49aca815 | ||
![]() |
c96968e476 | ||
![]() |
8f050516ec | ||
![]() |
27d2b244a4 | ||
![]() |
be2f2c6271 | ||
![]() |
8dc2797b16 | ||
![]() |
7ca8dabc44 | ||
![]() |
baeb55e217 | ||
![]() |
a8502fcc11 | ||
![]() |
9f5bc5b196 | ||
![]() |
7556ab9506 | ||
![]() |
bf176ac314 | ||
![]() |
9903e22eaa | ||
![]() |
1e0f7d9629 | ||
![]() |
e8a140af44 | ||
![]() |
b091d4f298 | ||
![]() |
35cf3063cb | ||
![]() |
7141ef17be | ||
![]() |
be2c68c0bb | ||
![]() |
7c944d3767 | ||
![]() |
1d4f02df2e | ||
![]() |
2007a74a20 | ||
![]() |
8c0839ad57 | ||
![]() |
516b9a54c4 | ||
![]() |
0d3e730c9c | ||
![]() |
c7a87d02b2 | ||
![]() |
dd082c204b | ||
![]() |
c4af3d1579 | ||
![]() |
10eadbcbbb | ||
![]() |
17141824f7 | ||
![]() |
4cfd6c010f | ||
![]() |
daa9024bff | ||
![]() |
e96aca90fe | ||
![]() |
0580a31961 | ||
![]() |
5c42c5130c | ||
![]() |
72d1e37a23 | ||
![]() |
61c9072a08 | ||
![]() |
08b25f9c2a | ||
![]() |
1a03b49700 | ||
![]() |
2d4a8e2e45 | ||
![]() |
8486377604 | ||
![]() |
3a4e9b6856 | ||
![]() |
5f5ac5419b | ||
![]() |
92b7a3b477 | ||
![]() |
4326519a3f | ||
![]() |
00837acdfc | ||
![]() |
7704be12b1 | ||
![]() |
712ddb531b | ||
![]() |
d52afc3f71 | ||
![]() |
92f6083e0b | ||
![]() |
5751fdbe56 | ||
![]() |
962b30adb9 | ||
![]() |
3b5b3f3bb6 | ||
![]() |
1a6d96cf3a | ||
![]() |
034fd9b4df | ||
![]() |
eb79a1e7d7 | ||
![]() |
e25d4f17aa | ||
![]() |
ccde9cceee | ||
![]() |
578d3c4260 | ||
![]() |
bfdc9a3d86 | ||
![]() |
5315545a4d | ||
![]() |
82a3b9d80f | ||
![]() |
3de985a3b8 | ||
![]() |
567ee8000d | ||
![]() |
03939001b2 | ||
![]() |
30d18050d1 | ||
![]() |
95caf8c7df | ||
![]() |
6c1f328d71 | ||
![]() |
bb20ab8c2c | ||
![]() |
29eb73176a | ||
![]() |
17ad3a87f3 | ||
![]() |
ed7c9c33b9 | ||
![]() |
59b66219cb | ||
![]() |
1e2c1d1464 | ||
![]() |
5b86b1277f | ||
![]() |
41fdf31e34 | ||
![]() |
9bef5c2af9 | ||
![]() |
ed1a69071b | ||
![]() |
56d328b4db | ||
![]() |
33c7e0fa2d | ||
![]() |
4f1cf1110f | ||
![]() |
a434bfd944 | ||
![]() |
21ed8e4206 | ||
![]() |
169d782580 | ||
![]() |
8a015f4e38 | ||
![]() |
cbb08c6202 | ||
![]() |
6301bc713c | ||
![]() |
a5d7043ce4 | ||
![]() |
912d2cbd79 | ||
![]() |
48ee3a34eb | ||
![]() |
21263a1ffb | ||
![]() |
db59e138e9 | ||
![]() |
bc8012dcc9 | ||
![]() |
d8b43597a0 | ||
![]() |
d3bf0da289 | ||
![]() |
871949e760 | ||
![]() |
4fb42d3545 | ||
![]() |
2e58d6656c | ||
![]() |
a3024b38e9 | ||
![]() |
85f2016371 | ||
![]() |
1ce3347c2e | ||
![]() |
4f8415e8a7 | ||
![]() |
b202a36feb | ||
![]() |
7e3e224746 | ||
![]() |
503a7979d0 | ||
![]() |
f3ba6e7996 | ||
![]() |
f13dcb4139 | ||
![]() |
e8dc61ec36 | ||
![]() |
88c59c5c13 | ||
![]() |
fd06d434f2 | ||
![]() |
85f80ff863 | ||
![]() |
d56abe6b72 | ||
![]() |
d24d29e42f | ||
![]() |
bc14b8468d | ||
![]() |
f924f81ec1 | ||
![]() |
3a6382df55 | ||
![]() |
1dba049038 | ||
![]() |
f539516252 | ||
![]() |
abd02eda0f | ||
![]() |
99695d6cb3 | ||
![]() |
cb1c2b59df | ||
![]() |
8368f977b9 | ||
![]() |
e05595f318 | ||
![]() |
11cf2ec39d | ||
![]() |
e5c43fcfcd | ||
![]() |
520581c165 | ||
![]() |
d1119a3b61 | ||
![]() |
5dd029cc05 | ||
![]() |
510e010f97 | ||
![]() |
1300cffa3b | ||
![]() |
8fbcbb0b68 | ||
![]() |
e02a47a16a | ||
![]() |
7b26c1ffcb | ||
![]() |
d3e62454a5 | ||
![]() |
6b8f4e92a7 | ||
![]() |
b590b21183 | ||
![]() |
a08484f450 | ||
![]() |
2978ca13c5 | ||
![]() |
31c0850b14 | ||
![]() |
1d85f0717a | ||
![]() |
795c16a941 | ||
![]() |
55c8589841 | ||
![]() |
4687add37a | ||
![]() |
c25e23ccd6 | ||
![]() |
e42ddb8f0f | ||
![]() |
705c0e58fc | ||
![]() |
7427e17926 | ||
![]() |
2c4b31dcaa | ||
![]() |
ae8671af96 | ||
![]() |
f5ff55abc5 | ||
![]() |
b662512995 | ||
![]() |
64c3fb1723 | ||
![]() |
fb99dc4cd0 | ||
![]() |
e08a0c44ba | ||
![]() |
68935d46ce | ||
![]() |
141c8c5192 | ||
![]() |
7ca5467f4c | ||
![]() |
5de53964d9 | ||
![]() |
8d8807e659 | ||
![]() |
9347944cbd | ||
![]() |
480448acbb | ||
![]() |
202fa82646 | ||
![]() |
feecc9f838 | ||
![]() |
2f9e667517 | ||
![]() |
5547bc7356 | ||
![]() |
eb4ae926b7 | ||
![]() |
b239ec2b71 | ||
![]() |
e9cac94aee | ||
![]() |
5289cd3af1 | ||
![]() |
45a5c1c235 | ||
![]() |
db3709952c | ||
![]() |
447932eedb | ||
![]() |
10cc3bdd3f | ||
![]() |
6ee2bfed36 | ||
![]() |
01efb831b7 | ||
![]() |
9e1e20bd94 | ||
![]() |
869ace74ad | ||
![]() |
94d56367fc | ||
![]() |
68a5ba668e | ||
![]() |
b2b590cf67 | ||
![]() |
6f7c071769 | ||
![]() |
c1a7164ce7 | ||
![]() |
b77839c139 | ||
![]() |
e2f2a9322c | ||
![]() |
e4bd6c885d | ||
![]() |
8201701d17 | ||
![]() |
a5e6b78e1d | ||
![]() |
027eccba06 | ||
![]() |
12f10513f0 | ||
![]() |
9907ed51f0 | ||
![]() |
90e9f79841 | ||
![]() |
c30b9cdfcf | ||
![]() |
7e1fa0cf38 | ||
![]() |
b6587488d4 | ||
![]() |
552eeeddf6 | ||
![]() |
cbc150bad2 | ||
![]() |
8a4ed121b5 | ||
![]() |
a9793dc0a5 | ||
![]() |
c9deef84ca | ||
![]() |
1582aaeb4c | ||
![]() |
707520c15c | ||
![]() |
d5de435f06 | ||
![]() |
9e3dfaa400 | ||
![]() |
7aa92ec249 | ||
![]() |
2fdcd40f00 | ||
![]() |
3b15b786ff | ||
![]() |
b212b30e58 | ||
![]() |
6fd89f8585 | ||
![]() |
0406d21703 | ||
![]() |
293f89a07b | ||
![]() |
520a0b4075 | ||
![]() |
488602e232 | ||
![]() |
1e8d353162 | ||
![]() |
b3718b8b4a | ||
![]() |
097cba5c60 | ||
![]() |
fa6d8d0891 | ||
![]() |
31797c55df | ||
![]() |
56a23c5c3d | ||
![]() |
adc89f1487 | ||
![]() |
7facc375bc | ||
![]() |
91d3fb0ea8 | ||
![]() |
4ab0047dc1 | ||
![]() |
279eeaa442 | ||
![]() |
d4d0fb2a03 | ||
![]() |
d56fe8a542 | ||
![]() |
292701925d | ||
![]() |
52fc854cc3 | ||
![]() |
90ca039768 | ||
![]() |
9e81055070 | ||
![]() |
cea402ebf8 | ||
![]() |
6b939b95c0 | ||
![]() |
b24621d1ea | ||
![]() |
cc0fde2c08 | ||
![]() |
3732998fb7 | ||
![]() |
c699e265ef | ||
![]() |
98bb726f1a | ||
![]() |
c132e7ed85 | ||
![]() |
233c969402 | ||
![]() |
3b885dd01f | ||
![]() |
52c8554d89 | ||
![]() |
b55baef985 | ||
![]() |
b593b15f27 | ||
![]() |
00669ac0c3 | ||
![]() |
a5bcf87c08 | ||
![]() |
8ca5b7528b | ||
![]() |
d951e68c10 | ||
![]() |
32e8d2043c | ||
![]() |
bf028915ec | ||
![]() |
b03f483e4f | ||
![]() |
6f6202eb69 | ||
![]() |
7ab2d1496e | ||
![]() |
acc229a7e1 | ||
![]() |
64ffa86fe3 | ||
![]() |
8b77024fb9 | ||
![]() |
42aa18ac16 | ||
![]() |
1b7742ef7f | ||
![]() |
0c6bf701c7 | ||
![]() |
05e2e305e4 | ||
![]() |
5523cd6203 | ||
![]() |
50da4bcd37 | ||
![]() |
b99072d986 | ||
![]() |
b9a7a7c422 | ||
![]() |
88ccbcd883 | ||
![]() |
b5bb6c6fe5 | ||
![]() |
19a3810168 | ||
![]() |
8ccc38eb00 | ||
![]() |
70146a08c1 | ||
![]() |
19d50b9c92 | ||
![]() |
05c1328ca7 | ||
![]() |
99c2dd9765 | ||
![]() |
edbe6851f7 | ||
![]() |
a7867a9253 | ||
![]() |
94e70f81ed | ||
![]() |
3d8654253a | ||
![]() |
39bd07de73 | ||
![]() |
3202ea55d2 | ||
![]() |
329a8c0c90 | ||
![]() |
c05824c641 | ||
![]() |
3abdffda9c | ||
![]() |
67da851efc | ||
![]() |
5463a27255 | ||
![]() |
ec0434c9b0 | ||
![]() |
7d8cb5c863 | ||
![]() |
4f01348ffb | ||
![]() |
2af3400464 | ||
![]() |
b6e220a4c5 | ||
![]() |
d5d45f100e | ||
![]() |
6b9ca60c47 | ||
![]() |
bc445a1e27 | ||
![]() |
a087b4c43e | ||
![]() |
8f67ddf968 | ||
![]() |
9ef07484dd |
@@ -1,5 +1,5 @@
|
|||||||
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile
|
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile
|
||||||
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.11
|
FROM mcr.microsoft.com/devcontainers/python:3.12
|
||||||
|
|
||||||
ENV \
|
ENV \
|
||||||
DEBIAN_FRONTEND=noninteractive \
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
|
@@ -115,6 +115,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"unused-imports/no-unused-imports": "error",
|
"unused-imports/no-unused-imports": "error",
|
||||||
|
"lit/attribute-names": "warn",
|
||||||
"lit/attribute-value-entities": "off",
|
"lit/attribute-value-entities": "off",
|
||||||
"lit/no-template-map": "off",
|
"lit/no-template-map": "off",
|
||||||
"lit/no-native-attributes": "warn",
|
"lit/no-native-attributes": "warn",
|
||||||
@@ -125,6 +126,5 @@
|
|||||||
"lit-a11y/anchor-is-valid": "warn",
|
"lit-a11y/anchor-is-valid": "warn",
|
||||||
"lit-a11y/role-has-required-aria-attrs": "warn"
|
"lit-a11y/role-has-required-aria-attrs": "warn"
|
||||||
},
|
},
|
||||||
"plugins": ["disable", "unused-imports"],
|
"plugins": ["unused-imports"]
|
||||||
"processor": "disable/disable"
|
|
||||||
}
|
}
|
||||||
|
4
.github/workflows/cast_deployment.yaml
vendored
4
.github/workflows/cast_deployment.yaml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
|
14
.github/workflows/ci.yaml
vendored
14
.github/workflows/ci.yaml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
- name: Build resources
|
- name: Build resources
|
||||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
|
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
|
||||||
- name: Setup lint cache
|
- name: Setup lint cache
|
||||||
uses: actions/cache@v4.0.0
|
uses: actions/cache@v4.0.2
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
node_modules/.cache/prettier
|
node_modules/.cache/prettier
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
@@ -76,7 +76,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
IS_TEST: "true"
|
IS_TEST: "true"
|
||||||
- name: Upload bundle stats
|
- name: Upload bundle stats
|
||||||
uses: actions/upload-artifact@v4.3.1
|
uses: actions/upload-artifact@v4.3.3
|
||||||
with:
|
with:
|
||||||
name: frontend-bundle-stats
|
name: frontend-bundle-stats
|
||||||
path: build/stats/*.json
|
path: build/stats/*.json
|
||||||
@@ -100,7 +100,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
IS_TEST: "true"
|
IS_TEST: "true"
|
||||||
- name: Upload bundle stats
|
- name: Upload bundle stats
|
||||||
uses: actions/upload-artifact@v4.3.1
|
uses: actions/upload-artifact@v4.3.3
|
||||||
with:
|
with:
|
||||||
name: supervisor-bundle-stats
|
name: supervisor-bundle-stats
|
||||||
path: build/stats/*.json
|
path: build/stats/*.json
|
||||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
# We must fetch at least the immediate parents so that if this is
|
# We must fetch at least the immediate parents so that if this is
|
||||||
# a pull request then we can checkout the head.
|
# a pull request then we can checkout the head.
|
||||||
|
4
.github/workflows/demo_deployment.yaml
vendored
4
.github/workflows/demo_deployment.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.7
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
|
2
.github/workflows/design_deployment.yaml
vendored
2
.github/workflows/design_deployment.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
|
2
.github/workflows/design_preview.yaml
vendored
2
.github/workflows/design_preview.yaml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.2
|
||||||
|
8
.github/workflows/nightly.yaml
vendored
8
.github/workflows/nightly.yaml
vendored
@@ -6,7 +6,7 @@ on:
|
|||||||
- cron: "0 1 * * *"
|
- cron: "0 1 * * *"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHON_VERSION: "3.11"
|
PYTHON_VERSION: "3.12"
|
||||||
NODE_OPTIONS: --max_old_space_size=6144
|
NODE_OPTIONS: --max_old_space_size=6144
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
@@ -57,14 +57,14 @@ jobs:
|
|||||||
run: tar -czvf translations.tar.gz translations
|
run: tar -czvf translations.tar.gz translations
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v4.3.1
|
uses: actions/upload-artifact@v4.3.3
|
||||||
with:
|
with:
|
||||||
name: wheels
|
name: wheels
|
||||||
path: dist/home_assistant_frontend*.whl
|
path: dist/home_assistant_frontend*.whl
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload translations
|
- name: Upload translations
|
||||||
uses: actions/upload-artifact@v4.3.1
|
uses: actions/upload-artifact@v4.3.3
|
||||||
with:
|
with:
|
||||||
name: translations
|
name: translations
|
||||||
path: translations.tar.gz
|
path: translations.tar.gz
|
||||||
|
2
.github/workflows/relative-ci.yaml
vendored
2
.github/workflows/relative-ci.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Send bundle stats and build information to RelativeCI
|
- name: Send bundle stats and build information to RelativeCI
|
||||||
uses: relative-ci/agent-action@v2.1.10
|
uses: relative-ci/agent-action@v2.1.11
|
||||||
with:
|
with:
|
||||||
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -6,7 +6,7 @@ on:
|
|||||||
- published
|
- published
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHON_VERSION: "3.11"
|
PYTHON_VERSION: "3.12"
|
||||||
NODE_OPTIONS: --max_old_space_size=6144
|
NODE_OPTIONS: --max_old_space_size=6144
|
||||||
|
|
||||||
# Set default workflow permissions
|
# Set default workflow permissions
|
||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
contents: write # Required to upload release assets
|
contents: write # Required to upload release assets
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Verify version
|
- name: Verify version
|
||||||
uses: home-assistant/actions/helpers/verify-version@master
|
uses: home-assistant/actions/helpers/verify-version@master
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
script/release
|
script/release
|
||||||
|
|
||||||
- name: Upload release assets
|
- name: Upload release assets
|
||||||
uses: softprops/action-gh-release@v0.1.15
|
uses: softprops/action-gh-release@v2.0.6
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
dist/*.whl
|
dist/*.whl
|
||||||
|
2
.github/workflows/translations.yaml
vendored
2
.github/workflows/translations.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Upload Translations
|
- name: Upload Translations
|
||||||
run: |
|
run: |
|
||||||
|
@@ -1,13 +0,0 @@
|
|||||||
diff --git a/simple-tooltip.js b/simple-tooltip.js
|
|
||||||
index 78a87f6a223925f0e29fbedb268c85a142ec6985..3d686dd6a3d5a93342b4b01408089fc316b408ca 100644
|
|
||||||
--- a/simple-tooltip.js
|
|
||||||
+++ b/simple-tooltip.js
|
|
||||||
@@ -195,6 +195,8 @@ class SimpleTooltip extends LitElement {
|
|
||||||
.hidden {
|
|
||||||
position: absolute;
|
|
||||||
left: -10000px;
|
|
||||||
+ inset-inline-start: -10000px;
|
|
||||||
+ inset-inline-end: initial;
|
|
||||||
top: auto;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
893
.yarn/releases/yarn-4.1.0.cjs
vendored
893
.yarn/releases/yarn-4.1.0.cjs
vendored
File diff suppressed because one or more lines are too long
894
.yarn/releases/yarn-4.3.1.cjs
vendored
Executable file
894
.yarn/releases/yarn-4.3.1.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
@@ -6,4 +6,4 @@ enableGlobalCache: false
|
|||||||
|
|
||||||
nodeLinker: node-modules
|
nodeLinker: node-modules
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-4.1.0.cjs
|
yarnPath: .yarn/releases/yarn-4.3.1.cjs
|
||||||
|
@@ -1,7 +1,56 @@
|
|||||||
import defineProvider from "@babel/helper-define-polyfill-provider";
|
import defineProvider from "@babel/helper-define-polyfill-provider";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import paths from "../paths.cjs";
|
||||||
|
|
||||||
|
const POLYFILL_DIR = join(paths.polymer_dir, "src/resources/polyfills");
|
||||||
|
|
||||||
// List of polyfill keys with supported browser targets for the functionality
|
// List of polyfill keys with supported browser targets for the functionality
|
||||||
const PolyfillSupport = {
|
const PolyfillSupport = {
|
||||||
|
// Note states and shadowRoot properties should be supported.
|
||||||
|
"element-internals": {
|
||||||
|
android: 90,
|
||||||
|
chrome: 90,
|
||||||
|
edge: 90,
|
||||||
|
firefox: 126,
|
||||||
|
ios: 17.4,
|
||||||
|
opera: 76,
|
||||||
|
opera_mobile: 64,
|
||||||
|
safari: 17.4,
|
||||||
|
samsung: 15.0,
|
||||||
|
},
|
||||||
|
"element-append": {
|
||||||
|
android: 54,
|
||||||
|
chrome: 54,
|
||||||
|
edge: 17,
|
||||||
|
firefox: 49,
|
||||||
|
ios: 10.0,
|
||||||
|
opera: 41,
|
||||||
|
opera_mobile: 41,
|
||||||
|
safari: 10.0,
|
||||||
|
samsung: 6.0,
|
||||||
|
},
|
||||||
|
"element-getattributenames": {
|
||||||
|
android: 61,
|
||||||
|
chrome: 61,
|
||||||
|
edge: 18,
|
||||||
|
firefox: 45,
|
||||||
|
ios: 10.3,
|
||||||
|
opera: 48,
|
||||||
|
opera_mobile: 45,
|
||||||
|
safari: 10.1,
|
||||||
|
samsung: 8.0,
|
||||||
|
},
|
||||||
|
"element-toggleattribute": {
|
||||||
|
android: 69,
|
||||||
|
chrome: 69,
|
||||||
|
edge: 18,
|
||||||
|
firefox: 63,
|
||||||
|
ios: 12.0,
|
||||||
|
opera: 56,
|
||||||
|
opera_mobile: 48,
|
||||||
|
safari: 12.0,
|
||||||
|
samsung: 10.0,
|
||||||
|
},
|
||||||
fetch: {
|
fetch: {
|
||||||
android: 42,
|
android: 42,
|
||||||
chrome: 42,
|
chrome: 42,
|
||||||
@@ -13,6 +62,31 @@ const PolyfillSupport = {
|
|||||||
safari: 10.1,
|
safari: 10.1,
|
||||||
samsung: 4.0,
|
samsung: 4.0,
|
||||||
},
|
},
|
||||||
|
"intl-getcanonicallocales": {
|
||||||
|
android: 54,
|
||||||
|
chrome: 54,
|
||||||
|
edge: 16,
|
||||||
|
firefox: 48,
|
||||||
|
ios: 10.3,
|
||||||
|
opera: 41,
|
||||||
|
opera_mobile: 41,
|
||||||
|
safari: 10.1,
|
||||||
|
samsung: 6.0,
|
||||||
|
},
|
||||||
|
"intl-locale": {
|
||||||
|
android: 74,
|
||||||
|
chrome: 74,
|
||||||
|
edge: 79,
|
||||||
|
firefox: 75,
|
||||||
|
ios: 14.0,
|
||||||
|
opera: 62,
|
||||||
|
opera_mobile: 53,
|
||||||
|
safari: 14.0,
|
||||||
|
samsung: 11.0,
|
||||||
|
},
|
||||||
|
"intl-other": {
|
||||||
|
// Not specified (i.e. always try polyfill) since compatibility depends on supported locales
|
||||||
|
},
|
||||||
proxy: {
|
proxy: {
|
||||||
android: 49,
|
android: 49,
|
||||||
chrome: 49,
|
chrome: 49,
|
||||||
@@ -24,17 +98,67 @@ const PolyfillSupport = {
|
|||||||
safari: 10.0,
|
safari: 10.0,
|
||||||
samsung: 5.0,
|
samsung: 5.0,
|
||||||
},
|
},
|
||||||
|
"resize-observer": {
|
||||||
|
android: 64,
|
||||||
|
chrome: 64,
|
||||||
|
edge: 79,
|
||||||
|
firefox: 69,
|
||||||
|
ios: 13.4,
|
||||||
|
opera: 51,
|
||||||
|
opera_mobile: 47,
|
||||||
|
safari: 13.1,
|
||||||
|
samsung: 9.0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Map of global variables and/or instance and static properties to the
|
// Map of global variables and/or instance and static properties to the
|
||||||
// corresponding polyfill key and actual module to import
|
// corresponding polyfill key and actual module to import
|
||||||
const polyfillMap = {
|
const polyfillMap = {
|
||||||
global: {
|
global: {
|
||||||
Proxy: { key: "proxy", module: "proxy-polyfill" },
|
|
||||||
fetch: { key: "fetch", module: "unfetch/polyfill" },
|
fetch: { key: "fetch", module: "unfetch/polyfill" },
|
||||||
|
Proxy: { key: "proxy", module: "proxy-polyfill" },
|
||||||
|
ResizeObserver: {
|
||||||
|
key: "resize-observer",
|
||||||
|
module: join(POLYFILL_DIR, "resize-observer.ts"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
instance: {
|
||||||
|
attachInternals: {
|
||||||
|
key: "element-internals",
|
||||||
|
module: "element-internals-polyfill",
|
||||||
|
},
|
||||||
|
...Object.fromEntries(
|
||||||
|
["append", "getAttributeNames", "toggleAttribute"].map((prop) => {
|
||||||
|
const key = `element-${prop.toLowerCase()}`;
|
||||||
|
return [prop, { key, module: join(POLYFILL_DIR, `${key}.ts`) }];
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
static: {
|
||||||
|
Intl: {
|
||||||
|
getCanonicalLocales: {
|
||||||
|
key: "intl-getcanonicallocales",
|
||||||
|
module: join(POLYFILL_DIR, "intl-polyfill.ts"),
|
||||||
|
},
|
||||||
|
Locale: {
|
||||||
|
key: "intl-locale",
|
||||||
|
module: join(POLYFILL_DIR, "intl-polyfill.ts"),
|
||||||
|
},
|
||||||
|
...Object.fromEntries(
|
||||||
|
[
|
||||||
|
"DateTimeFormat",
|
||||||
|
"DisplayNames",
|
||||||
|
"ListFormat",
|
||||||
|
"NumberFormat",
|
||||||
|
"PluralRules",
|
||||||
|
"RelativeTimeFormat",
|
||||||
|
].map((obj) => [
|
||||||
|
obj,
|
||||||
|
{ key: "intl-other", module: join(POLYFILL_DIR, "intl-polyfill.ts") },
|
||||||
|
])
|
||||||
|
),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
instance: {},
|
|
||||||
static: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create plugin using the same factory as for CoreJS
|
// Create plugin using the same factory as for CoreJS
|
||||||
@@ -42,14 +166,16 @@ export default defineProvider(
|
|||||||
({ createMetaResolver, debug, shouldInjectPolyfill }) => {
|
({ createMetaResolver, debug, shouldInjectPolyfill }) => {
|
||||||
const resolvePolyfill = createMetaResolver(polyfillMap);
|
const resolvePolyfill = createMetaResolver(polyfillMap);
|
||||||
return {
|
return {
|
||||||
name: "HA Custom",
|
name: "custom-polyfill",
|
||||||
polyfills: PolyfillSupport,
|
polyfills: PolyfillSupport,
|
||||||
usageGlobal(meta, utils) {
|
usageGlobal(meta, utils) {
|
||||||
const polyfill = resolvePolyfill(meta);
|
const polyfill = resolvePolyfill(meta);
|
||||||
if (polyfill && shouldInjectPolyfill(polyfill.desc.key)) {
|
if (polyfill && shouldInjectPolyfill(polyfill.desc.key)) {
|
||||||
debug(polyfill.desc.key);
|
debug(polyfill.desc.key);
|
||||||
utils.injectGlobalImport(polyfill.desc.module);
|
utils.injectGlobalImport(polyfill.desc.module);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,8 @@ const env = require("./env.cjs");
|
|||||||
const paths = require("./paths.cjs");
|
const paths = require("./paths.cjs");
|
||||||
const { dependencies } = require("../package.json");
|
const { dependencies } = require("../package.json");
|
||||||
|
|
||||||
|
const BABEL_PLUGINS = path.join(__dirname, "babel-plugins");
|
||||||
|
|
||||||
// GitHub base URL to use for production source maps
|
// GitHub base URL to use for production source maps
|
||||||
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
|
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
|
||||||
module.exports.sourceMapURL = () => {
|
module.exports.sourceMapURL = () => {
|
||||||
@@ -90,8 +92,8 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
|||||||
[
|
[
|
||||||
"@babel/preset-env",
|
"@babel/preset-env",
|
||||||
{
|
{
|
||||||
useBuiltIns: latestBuild ? false : "usage",
|
useBuiltIns: "usage",
|
||||||
corejs: latestBuild ? false : dependencies["core-js"],
|
corejs: dependencies["core-js"],
|
||||||
bugfixes: true,
|
bugfixes: true,
|
||||||
shippedProposals: true,
|
shippedProposals: true,
|
||||||
},
|
},
|
||||||
@@ -100,22 +102,12 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
|||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
[
|
[
|
||||||
path.resolve(
|
path.join(BABEL_PLUGINS, "inline-constants-plugin.cjs"),
|
||||||
paths.polymer_dir,
|
|
||||||
"build-scripts/babel-plugins/inline-constants-plugin.cjs"
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
modules: ["@mdi/js"],
|
modules: ["@mdi/js"],
|
||||||
ignoreModuleNotFound: true,
|
ignoreModuleNotFound: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
|
||||||
path.resolve(
|
|
||||||
paths.polymer_dir,
|
|
||||||
"build-scripts/babel-plugins/custom-polyfill-plugin.js"
|
|
||||||
),
|
|
||||||
{ method: "usage-global" },
|
|
||||||
],
|
|
||||||
// Minify template literals for production
|
// Minify template literals for production
|
||||||
isProdBuild && [
|
isProdBuild && [
|
||||||
"template-html-minifier",
|
"template-html-minifier",
|
||||||
@@ -153,6 +145,27 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
|||||||
],
|
],
|
||||||
sourceMaps: !isTestBuild,
|
sourceMaps: !isTestBuild,
|
||||||
overrides: [
|
overrides: [
|
||||||
|
{
|
||||||
|
// Add plugin to inject various polyfills, excluding the polyfills
|
||||||
|
// themselves to prevent self-injection.
|
||||||
|
plugins: [
|
||||||
|
[
|
||||||
|
path.join(BABEL_PLUGINS, "custom-polyfill-plugin.js"),
|
||||||
|
{ method: "usage-global" },
|
||||||
|
],
|
||||||
|
],
|
||||||
|
exclude: [
|
||||||
|
path.join(paths.polymer_dir, "src/resources/polyfills"),
|
||||||
|
...[
|
||||||
|
"@formatjs/(?:ecma402-abstract|intl-\\w+)",
|
||||||
|
"@lit-labs/virtualizer/polyfills",
|
||||||
|
"@webcomponents/scoped-custom-element-registry",
|
||||||
|
"element-internals-polyfill",
|
||||||
|
"proxy-polyfill",
|
||||||
|
"unfetch",
|
||||||
|
].map((p) => new RegExp(`/node_modules/${p}/`)),
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Use unambiguous for dependencies so that require() is correctly injected into CommonJS files
|
// Use unambiguous for dependencies so that require() is correctly injected into CommonJS files
|
||||||
// Exclusions are needed in some cases where ES modules have no static imports or exports, such as polyfills
|
// Exclusions are needed in some cases where ES modules have no static imports or exports, such as polyfills
|
||||||
|
@@ -9,7 +9,7 @@ import gulp from "gulp";
|
|||||||
import jszip from "jszip";
|
import jszip from "jszip";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import process from "process";
|
import process from "process";
|
||||||
import tar from "tar";
|
import { extract } from "tar";
|
||||||
|
|
||||||
const MAX_AGE = 24; // hours
|
const MAX_AGE = 24; // hours
|
||||||
const OWNER = "home-assistant";
|
const OWNER = "home-assistant";
|
||||||
@@ -156,7 +156,7 @@ gulp.task("fetch-nightly-translations", async function () {
|
|||||||
console.log("Unpacking downloaded translations...");
|
console.log("Unpacking downloaded translations...");
|
||||||
const zip = await jszip.loadAsync(downloadResponse.data);
|
const zip = await jszip.loadAsync(downloadResponse.data);
|
||||||
await deleteCurrent;
|
await deleteCurrent;
|
||||||
const extractStream = zip.file(/.*/)[0].nodeStream().pipe(tar.extract());
|
const extractStream = zip.file(/.*/)[0].nodeStream().pipe(extract());
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
extractStream.on("close", resolve).on("error", reject);
|
extractStream.on("close", resolve).on("error", reject);
|
||||||
});
|
});
|
||||||
|
@@ -1,92 +1,112 @@
|
|||||||
import { createHash } from "crypto";
|
/* eslint-disable max-classes-per-file */
|
||||||
import { deleteSync } from "del";
|
|
||||||
import { mkdirSync, readdirSync, readFileSync, renameSync } from "fs";
|
import { deleteAsync } from "del";
|
||||||
import { writeFile } from "node:fs/promises";
|
import { glob } from "glob";
|
||||||
import gulp from "gulp";
|
import gulp from "gulp";
|
||||||
import flatmap from "gulp-flatmap";
|
|
||||||
import transform from "gulp-json-transform";
|
|
||||||
import merge from "gulp-merge-json";
|
|
||||||
import rename from "gulp-rename";
|
import rename from "gulp-rename";
|
||||||
import path from "path";
|
import merge from "lodash.merge";
|
||||||
import vinylBuffer from "vinyl-buffer";
|
import { createHash } from "node:crypto";
|
||||||
import source from "vinyl-source-stream";
|
import { mkdir, readFile } from "node:fs/promises";
|
||||||
|
import { basename, join } from "node:path";
|
||||||
|
import { PassThrough, Transform } from "node:stream";
|
||||||
|
import { finished } from "node:stream/promises";
|
||||||
import env from "../env.cjs";
|
import env from "../env.cjs";
|
||||||
import paths from "../paths.cjs";
|
import paths from "../paths.cjs";
|
||||||
import { mapFiles } from "../util.cjs";
|
|
||||||
import "./fetch-nightly-translations.js";
|
import "./fetch-nightly-translations.js";
|
||||||
|
|
||||||
const inFrontendDir = "translations/frontend";
|
const inFrontendDir = "translations/frontend";
|
||||||
const inBackendDir = "translations/backend";
|
const inBackendDir = "translations/backend";
|
||||||
const workDir = "build/translations";
|
const workDir = "build/translations";
|
||||||
const fullDir = workDir + "/full";
|
const outDir = join(workDir, "output");
|
||||||
const coreDir = workDir + "/core";
|
const EN_SRC = join(paths.translations_src, "en.json");
|
||||||
const outDir = workDir + "/output";
|
const TEST_LOCALE = "en-x-test";
|
||||||
|
|
||||||
let mergeBackend = false;
|
let mergeBackend = false;
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"translations-enable-merge-backend",
|
"translations-enable-merge-backend",
|
||||||
gulp.parallel((done) => {
|
gulp.parallel(async () => {
|
||||||
mergeBackend = true;
|
mergeBackend = true;
|
||||||
done();
|
|
||||||
}, "allow-setup-fetch-nightly-translations")
|
}, "allow-setup-fetch-nightly-translations")
|
||||||
);
|
);
|
||||||
|
|
||||||
// Panel translations which should be split from the core translations.
|
// Transform stream to apply a function on Vinyl JSON files (buffer mode only).
|
||||||
const TRANSLATION_FRAGMENTS = Object.keys(
|
// The provided function can either return a new object, or an array of
|
||||||
JSON.parse(
|
// [object, subdirectory] pairs for fragmentizing the JSON.
|
||||||
readFileSync(
|
class CustomJSON extends Transform {
|
||||||
path.resolve(paths.polymer_dir, "src/translations/en.json"),
|
constructor(func, reviver = null) {
|
||||||
"utf-8"
|
super({ objectMode: true });
|
||||||
)
|
this._func = func;
|
||||||
).ui.panel
|
this._reviver = reviver;
|
||||||
);
|
}
|
||||||
|
|
||||||
function recursiveFlatten(prefix, data) {
|
async _transform(file, _, callback) {
|
||||||
let output = {};
|
try {
|
||||||
Object.keys(data).forEach((key) => {
|
let obj = JSON.parse(file.contents.toString(), this._reviver);
|
||||||
if (typeof data[key] === "object") {
|
if (this._func) obj = this._func(obj, file.path);
|
||||||
output = {
|
for (const [outObj, dir] of Array.isArray(obj) ? obj : [[obj, ""]]) {
|
||||||
...output,
|
const outFile = file.clone({ contents: false });
|
||||||
...recursiveFlatten(prefix + key + ".", data[key]),
|
outFile.contents = Buffer.from(JSON.stringify(outObj));
|
||||||
};
|
outFile.dirname += `/${dir}`;
|
||||||
|
this.push(outFile);
|
||||||
|
}
|
||||||
|
callback(null);
|
||||||
|
} catch (err) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform stream to merge Vinyl JSON files (buffer mode only).
|
||||||
|
class MergeJSON extends Transform {
|
||||||
|
_objects = [];
|
||||||
|
|
||||||
|
constructor(stem, startObj = {}, reviver = null) {
|
||||||
|
super({ objectMode: true, allowHalfOpen: false });
|
||||||
|
this._stem = stem;
|
||||||
|
this._startObj = structuredClone(startObj);
|
||||||
|
this._reviver = reviver;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _transform(file, _, callback) {
|
||||||
|
try {
|
||||||
|
this._objects.push(JSON.parse(file.contents.toString(), this._reviver));
|
||||||
|
if (!this._outFile) this._outFile = file.clone({ contents: false });
|
||||||
|
callback(null);
|
||||||
|
} catch (err) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _flush(callback) {
|
||||||
|
try {
|
||||||
|
const mergedObj = merge(this._startObj, ...this._objects);
|
||||||
|
this._outFile.contents = Buffer.from(JSON.stringify(mergedObj));
|
||||||
|
this._outFile.stem = this._stem;
|
||||||
|
callback(null, this._outFile);
|
||||||
|
} catch (err) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility to flatten object keys to single level using separator
|
||||||
|
const flatten = (data, prefix = "", sep = ".") => {
|
||||||
|
const output = {};
|
||||||
|
for (const [key, value] of Object.entries(data)) {
|
||||||
|
if (typeof value === "object") {
|
||||||
|
Object.assign(output, flatten(value, prefix + key + sep, sep));
|
||||||
} else {
|
} else {
|
||||||
output[prefix + key] = data[key];
|
output[prefix + key] = value;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
};
|
||||||
|
|
||||||
function flatten(data) {
|
// Filter functions that can be passed directly to JSON.parse()
|
||||||
return recursiveFlatten("", data);
|
const emptyReviver = (_key, value) => value || undefined;
|
||||||
}
|
const testReviver = (_key, value) =>
|
||||||
|
value && typeof value === "string" ? "TRANSLATED" : value;
|
||||||
function emptyFilter(data) {
|
|
||||||
const newData = {};
|
|
||||||
Object.keys(data).forEach((key) => {
|
|
||||||
if (data[key]) {
|
|
||||||
if (typeof data[key] === "object") {
|
|
||||||
newData[key] = emptyFilter(data[key]);
|
|
||||||
} else {
|
|
||||||
newData[key] = data[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return newData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function recursiveEmpty(data) {
|
|
||||||
const newData = {};
|
|
||||||
Object.keys(data).forEach((key) => {
|
|
||||||
if (data[key]) {
|
|
||||||
if (typeof data[key] === "object") {
|
|
||||||
newData[key] = recursiveEmpty(data[key]);
|
|
||||||
} else {
|
|
||||||
newData[key] = "TRANSLATED";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return newData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace Lokalise key placeholders with their actual values.
|
* Replace Lokalise key placeholders with their actual values.
|
||||||
@@ -95,60 +115,44 @@ function recursiveEmpty(data) {
|
|||||||
* be included in src/translations/en.json, but still be usable while
|
* be included in src/translations/en.json, but still be usable while
|
||||||
* developing locally.
|
* developing locally.
|
||||||
*
|
*
|
||||||
* @link https://docs.lokalise.co/article/KO5SZWLLsy-key-referencing
|
* @link https://docs.lokalise.com/en/articles/1400528-key-referencing
|
||||||
*/
|
*/
|
||||||
const re_key_reference = /\[%key:([^%]+)%\]/;
|
const KEY_REFERENCE = /\[%key:([^%]+)%\]/;
|
||||||
function lokaliseTransform(data, original, file) {
|
const lokaliseTransform = (data, path, original = data) => {
|
||||||
const output = {};
|
const output = {};
|
||||||
Object.entries(data).forEach(([key, value]) => {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
if (value instanceof Object) {
|
if (typeof value === "object") {
|
||||||
output[key] = lokaliseTransform(value, original, file);
|
output[key] = lokaliseTransform(value, path, original);
|
||||||
} else {
|
} else {
|
||||||
output[key] = value.replace(re_key_reference, (_match, lokalise_key) => {
|
output[key] = value.replace(KEY_REFERENCE, (_match, lokalise_key) => {
|
||||||
const replace = lokalise_key.split("::").reduce((tr, k) => {
|
const replace = lokalise_key.split("::").reduce((tr, k) => {
|
||||||
if (!tr) {
|
if (!tr) {
|
||||||
throw Error(
|
throw Error(`Invalid key placeholder ${lokalise_key} in ${path}`);
|
||||||
`Invalid key placeholder ${lokalise_key} in ${file.path}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return tr[k];
|
return tr[k];
|
||||||
}, original);
|
}, original);
|
||||||
if (typeof replace !== "string") {
|
if (typeof replace !== "string") {
|
||||||
throw Error(
|
throw Error(`Invalid key placeholder ${lokalise_key} in ${path}`);
|
||||||
`Invalid key placeholder ${lokalise_key} in ${file.path}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return replace;
|
return replace;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
};
|
||||||
|
|
||||||
gulp.task("clean-translations", async () => deleteSync([workDir]));
|
gulp.task("clean-translations", () => deleteAsync([workDir]));
|
||||||
|
|
||||||
gulp.task("ensure-translations-build-dir", async () => {
|
const makeWorkDir = () => mkdir(workDir, { recursive: true });
|
||||||
mkdirSync(workDir, { recursive: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("create-test-metadata", () =>
|
const createTestTranslation = () =>
|
||||||
env.isProdBuild()
|
|
||||||
? Promise.resolve()
|
|
||||||
: writeFile(
|
|
||||||
workDir + "/testMetadata.json",
|
|
||||||
JSON.stringify({ test: { nativeName: "Test" } })
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task("create-test-translation", () =>
|
|
||||||
env.isProdBuild()
|
env.isProdBuild()
|
||||||
? Promise.resolve()
|
? Promise.resolve()
|
||||||
: gulp
|
: gulp
|
||||||
.src(path.join(paths.translations_src, "en.json"))
|
.src(EN_SRC)
|
||||||
.pipe(transform((data, _file) => recursiveEmpty(data)))
|
.pipe(new CustomJSON(null, testReviver))
|
||||||
.pipe(rename("test.json"))
|
.pipe(rename(`${TEST_LOCALE}.json`))
|
||||||
.pipe(gulp.dest(workDir))
|
.pipe(gulp.dest(workDir));
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This task will build a master translation file, to be used as the base for
|
* This task will build a master translation file, to be used as the base for
|
||||||
@@ -159,279 +163,164 @@ gulp.task("create-test-translation", () =>
|
|||||||
* project is buildable immediately after merging new translation keys, since
|
* project is buildable immediately after merging new translation keys, since
|
||||||
* the Lokalise update to translations/en.json will not happen immediately.
|
* the Lokalise update to translations/en.json will not happen immediately.
|
||||||
*/
|
*/
|
||||||
gulp.task("build-master-translation", () => {
|
const createMasterTranslation = () =>
|
||||||
const src = [path.join(paths.translations_src, "en.json")];
|
gulp
|
||||||
|
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
|
||||||
|
.pipe(new CustomJSON(lokaliseTransform))
|
||||||
|
.pipe(new MergeJSON("en"))
|
||||||
|
.pipe(gulp.dest(workDir));
|
||||||
|
|
||||||
if (mergeBackend) {
|
const FRAGMENTS = ["base"];
|
||||||
src.push(path.join(inBackendDir, "en.json"));
|
|
||||||
|
const toggleSupervisorFragment = async () => {
|
||||||
|
FRAGMENTS[0] = "supervisor";
|
||||||
|
};
|
||||||
|
|
||||||
|
const panelFragment = (fragment) =>
|
||||||
|
fragment !== "base" && fragment !== "supervisor";
|
||||||
|
|
||||||
|
const HASHES = new Map();
|
||||||
|
|
||||||
|
const createTranslations = async () => {
|
||||||
|
// Parse and store the master to avoid repeating this for each locale, then
|
||||||
|
// add the panel fragments when processing the app.
|
||||||
|
const enMaster = JSON.parse(await readFile(`${workDir}/en.json`, "utf-8"));
|
||||||
|
if (FRAGMENTS[0] === "base") {
|
||||||
|
FRAGMENTS.push(...Object.keys(enMaster.ui.panel));
|
||||||
}
|
}
|
||||||
|
|
||||||
return gulp
|
// The downstream pipeline is setup first. It hashes the merged data for
|
||||||
.src(src)
|
// each locale, then fragmentizes and flattens the data for final output.
|
||||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
const translationFiles = await glob([
|
||||||
|
`${inFrontendDir}/!(en).json`,
|
||||||
|
...(env.isProdBuild() ? [] : [`${workDir}/${TEST_LOCALE}.json`]),
|
||||||
|
]);
|
||||||
|
const hashStream = new Transform({
|
||||||
|
objectMode: true,
|
||||||
|
transform: async (file, _, callback) => {
|
||||||
|
const hash = env.isProdBuild()
|
||||||
|
? createHash("md5").update(file.contents).digest("hex")
|
||||||
|
: "dev";
|
||||||
|
HASHES.set(file.stem, hash);
|
||||||
|
file.stem += `-${hash}`;
|
||||||
|
callback(null, file);
|
||||||
|
},
|
||||||
|
}).setMaxListeners(translationFiles.length + 1);
|
||||||
|
const fragmentsStream = hashStream
|
||||||
.pipe(
|
.pipe(
|
||||||
merge({
|
new CustomJSON((data) =>
|
||||||
fileName: "en.json",
|
FRAGMENTS.map((fragment) => {
|
||||||
})
|
switch (fragment) {
|
||||||
)
|
case "base":
|
||||||
.pipe(gulp.dest(fullDir));
|
// Remove the panels and supervisor to create the base translations
|
||||||
});
|
return [
|
||||||
|
flatten({
|
||||||
gulp.task("build-merged-translations", () =>
|
...data,
|
||||||
gulp
|
ui: { ...data.ui, panel: undefined },
|
||||||
.src([
|
supervisor: undefined,
|
||||||
inFrontendDir + "/*.json",
|
}),
|
||||||
"!" + inFrontendDir + "/en.json",
|
"",
|
||||||
...(env.isProdBuild() ? [] : [workDir + "/test.json"]),
|
];
|
||||||
])
|
case "supervisor":
|
||||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
// Supervisor key is at the top level
|
||||||
.pipe(
|
return [flatten(data.supervisor), ""];
|
||||||
flatmap((stream, file) => {
|
default:
|
||||||
// For each language generate a merged json file. It begins with the master
|
// Create a fragment with only the given panel
|
||||||
// translation as a failsafe for untranslated strings, and merges all parent
|
return [
|
||||||
// tags into one file for each specific subtag
|
flatten(data.ui.panel[fragment], `ui.panel.${fragment}.`),
|
||||||
//
|
fragment,
|
||||||
// TODO: This is a naive interpretation of BCP47 that should be improved.
|
];
|
||||||
// Will be OK for now as long as we don't have anything more complicated
|
|
||||||
// than a base translation + region.
|
|
||||||
const tr = path.basename(file.history[0], ".json");
|
|
||||||
const subtags = tr.split("-");
|
|
||||||
const src = [fullDir + "/en.json"];
|
|
||||||
for (let i = 1; i <= subtags.length; i++) {
|
|
||||||
const lang = subtags.slice(0, i).join("-");
|
|
||||||
if (lang === "test") {
|
|
||||||
src.push(workDir + "/test.json");
|
|
||||||
} else if (lang !== "en") {
|
|
||||||
src.push(inFrontendDir + "/" + lang + ".json");
|
|
||||||
if (mergeBackend) {
|
|
||||||
src.push(inBackendDir + "/" + lang + ".json");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
return gulp
|
|
||||||
.src(src, { allowEmpty: true })
|
|
||||||
.pipe(transform((data) => emptyFilter(data)))
|
|
||||||
.pipe(
|
|
||||||
merge({
|
|
||||||
fileName: tr + ".json",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest(fullDir));
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let taskName;
|
|
||||||
|
|
||||||
const splitTasks = [];
|
|
||||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
|
||||||
taskName = "build-translation-fragment-" + fragment;
|
|
||||||
gulp.task(taskName, () =>
|
|
||||||
// Return only the translations for this fragment.
|
|
||||||
gulp
|
|
||||||
.src(fullDir + "/*.json")
|
|
||||||
.pipe(
|
|
||||||
transform((data) => ({
|
|
||||||
ui: {
|
|
||||||
panel: {
|
|
||||||
[fragment]: data.ui.panel[fragment],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest(workDir + "/" + fragment))
|
|
||||||
);
|
|
||||||
splitTasks.push(taskName);
|
|
||||||
});
|
|
||||||
|
|
||||||
taskName = "build-translation-core";
|
|
||||||
gulp.task(taskName, () =>
|
|
||||||
// Remove the fragment translations from the core translation.
|
|
||||||
gulp
|
|
||||||
.src(fullDir + "/*.json")
|
|
||||||
.pipe(
|
|
||||||
transform((data, _file) => {
|
|
||||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
|
||||||
delete data.ui.panel[fragment];
|
|
||||||
});
|
|
||||||
delete data.supervisor;
|
|
||||||
return data;
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest(coreDir))
|
|
||||||
);
|
|
||||||
|
|
||||||
splitTasks.push(taskName);
|
|
||||||
|
|
||||||
gulp.task("build-flattened-translations", () =>
|
|
||||||
// Flatten the split versions of our translations, and move them into outDir
|
|
||||||
gulp
|
|
||||||
.src(
|
|
||||||
TRANSLATION_FRAGMENTS.map(
|
|
||||||
(fragment) => workDir + "/" + fragment + "/*.json"
|
|
||||||
).concat(coreDir + "/*.json"),
|
|
||||||
{ base: workDir }
|
|
||||||
)
|
|
||||||
.pipe(
|
|
||||||
transform((data) =>
|
|
||||||
// Polymer.AppLocalizeBehavior requires flattened json
|
|
||||||
flatten(data)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.pipe(
|
.pipe(gulp.dest(outDir));
|
||||||
rename((filePath) => {
|
|
||||||
if (filePath.dirname === "core") {
|
// Send the English master downstream first, then for each other locale
|
||||||
filePath.dirname = "";
|
// generate merged JSON data to continue piping. It begins with the master
|
||||||
|
// translation as a failsafe for untranslated strings, and merges all parent
|
||||||
|
// tags into one file for each specific subtag
|
||||||
|
//
|
||||||
|
// TODO: This is a naive interpretation of BCP47 that should be improved.
|
||||||
|
// Will be OK for now as long as we don't have anything more complicated
|
||||||
|
// than a base translation + region.
|
||||||
|
gulp
|
||||||
|
.src(`${workDir}/en.json`)
|
||||||
|
.pipe(new PassThrough({ objectMode: true }))
|
||||||
|
.pipe(hashStream, { end: false });
|
||||||
|
const mergesFinished = [];
|
||||||
|
for (const translationFile of translationFiles) {
|
||||||
|
const locale = basename(translationFile, ".json");
|
||||||
|
const subtags = locale.split("-");
|
||||||
|
const mergeFiles = [];
|
||||||
|
for (let i = 1; i <= subtags.length; i++) {
|
||||||
|
const lang = subtags.slice(0, i).join("-");
|
||||||
|
if (lang === TEST_LOCALE) {
|
||||||
|
mergeFiles.push(`${workDir}/${TEST_LOCALE}.json`);
|
||||||
|
} else if (lang !== "en") {
|
||||||
|
mergeFiles.push(`${inFrontendDir}/${lang}.json`);
|
||||||
|
if (mergeBackend) {
|
||||||
|
mergeFiles.push(`${inBackendDir}/${lang}.json`);
|
||||||
}
|
}
|
||||||
// In dev we create the file with the fake hash in the filename
|
|
||||||
if (!env.isProdBuild()) {
|
|
||||||
filePath.basename += "-dev";
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest(outDir))
|
|
||||||
);
|
|
||||||
|
|
||||||
const fingerprints = {};
|
|
||||||
|
|
||||||
gulp.task("build-translation-fingerprints", () => {
|
|
||||||
// Fingerprint full file of each language
|
|
||||||
const files = readdirSync(fullDir);
|
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
fingerprints[files[i].split(".")[0]] = {
|
|
||||||
// In dev we create fake hashes
|
|
||||||
hash: env.isProdBuild()
|
|
||||||
? createHash("md5")
|
|
||||||
.update(readFileSync(path.join(fullDir, files[i]), "utf-8"))
|
|
||||||
.digest("hex")
|
|
||||||
: "dev",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// In dev we create the file with the fake hash in the filename
|
|
||||||
if (env.isProdBuild()) {
|
|
||||||
mapFiles(outDir, ".json", (filename) => {
|
|
||||||
const parsed = path.parse(filename);
|
|
||||||
|
|
||||||
// nl.json -> nl-<hash>.json
|
|
||||||
if (!(parsed.name in fingerprints)) {
|
|
||||||
throw new Error(`Unable to find hash for ${filename}`);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
renameSync(
|
const mergeStream = gulp
|
||||||
filename,
|
.src(mergeFiles, { allowEmpty: true })
|
||||||
`${parsed.dir}/${parsed.name}-${fingerprints[parsed.name].hash}${
|
.pipe(new MergeJSON(locale, enMaster, emptyReviver));
|
||||||
parsed.ext
|
mergesFinished.push(finished(mergeStream));
|
||||||
}`
|
mergeStream.pipe(hashStream, { end: false });
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const stream = source("translationFingerprints.json");
|
// Wait for all merges to finish, then it's safe to end writing to the
|
||||||
stream.write(JSON.stringify(fingerprints));
|
// downstream pipeline and wait for all fragments to finish writing.
|
||||||
process.nextTick(() => stream.end());
|
await Promise.all(mergesFinished);
|
||||||
return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
|
hashStream.end();
|
||||||
});
|
await finished(fragmentsStream);
|
||||||
|
};
|
||||||
|
|
||||||
gulp.task("build-translation-fragment-supervisor", () =>
|
const writeTranslationMetaData = () =>
|
||||||
gulp
|
gulp
|
||||||
.src(fullDir + "/*.json")
|
.src([`${paths.translations_src}/translationMetadata.json`])
|
||||||
.pipe(transform((data) => data.supervisor))
|
|
||||||
.pipe(
|
.pipe(
|
||||||
rename((filePath) => {
|
new CustomJSON((meta) => {
|
||||||
// In dev we create the file with the fake hash in the filename
|
// Add the test translation in development.
|
||||||
if (!env.isProdBuild()) {
|
if (!env.isProdBuild()) {
|
||||||
filePath.basename += "-dev";
|
meta[TEST_LOCALE] = { nativeName: "Translation Test" };
|
||||||
}
|
}
|
||||||
})
|
// Filter out locales without a native name, and add the hashes.
|
||||||
)
|
for (const locale of Object.keys(meta)) {
|
||||||
.pipe(gulp.dest(workDir + "/supervisor"))
|
if (!meta[locale].nativeName) {
|
||||||
);
|
meta[locale] = undefined;
|
||||||
|
|
||||||
gulp.task("build-translation-flatten-supervisor", () =>
|
|
||||||
gulp
|
|
||||||
.src(workDir + "/supervisor/*.json")
|
|
||||||
.pipe(
|
|
||||||
transform((data) =>
|
|
||||||
// Polymer.AppLocalizeBehavior requires flattened json
|
|
||||||
flatten(data)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest(outDir))
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task("build-translation-write-metadata", () =>
|
|
||||||
gulp
|
|
||||||
.src([
|
|
||||||
path.join(paths.translations_src, "translationMetadata.json"),
|
|
||||||
...(env.isProdBuild() ? [] : [workDir + "/testMetadata.json"]),
|
|
||||||
workDir + "/translationFingerprints.json",
|
|
||||||
])
|
|
||||||
.pipe(merge({}))
|
|
||||||
.pipe(
|
|
||||||
transform((data) => {
|
|
||||||
const newData = {};
|
|
||||||
Object.entries(data).forEach(([key, value]) => {
|
|
||||||
// Filter out translations without native name.
|
|
||||||
if (value.nativeName) {
|
|
||||||
newData[key] = value;
|
|
||||||
} else {
|
|
||||||
console.warn(
|
console.warn(
|
||||||
`Skipping language ${key}. Native name was not translated.`
|
`Skipping locale ${locale} because native name is not translated.`
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
meta[locale].hash = HASHES.get(locale);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return newData;
|
return {
|
||||||
|
fragments: FRAGMENTS.filter(panelFragment),
|
||||||
|
translations: meta,
|
||||||
|
};
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.pipe(
|
.pipe(gulp.dest(workDir));
|
||||||
transform((data) => ({
|
|
||||||
fragments: TRANSLATION_FRAGMENTS,
|
|
||||||
translations: data,
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
.pipe(rename("translationMetadata.json"))
|
|
||||||
.pipe(gulp.dest(workDir))
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"create-translations",
|
|
||||||
gulp.series(
|
|
||||||
gulp.parallel("create-test-metadata", "create-test-translation"),
|
|
||||||
"build-master-translation",
|
|
||||||
"build-merged-translations",
|
|
||||||
gulp.parallel(...splitTasks),
|
|
||||||
"build-flattened-translations"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"build-translations",
|
"build-translations",
|
||||||
gulp.series(
|
gulp.series(
|
||||||
gulp.parallel(
|
gulp.parallel(
|
||||||
"fetch-nightly-translations",
|
"fetch-nightly-translations",
|
||||||
gulp.series("clean-translations", "ensure-translations-build-dir")
|
gulp.series("clean-translations", makeWorkDir)
|
||||||
),
|
),
|
||||||
"create-translations",
|
createTestTranslation,
|
||||||
"build-translation-fingerprints",
|
createMasterTranslation,
|
||||||
"build-translation-write-metadata"
|
createTranslations,
|
||||||
|
writeTranslationMetaData
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"build-supervisor-translations",
|
"build-supervisor-translations",
|
||||||
gulp.series(
|
gulp.series(toggleSupervisorFragment, "build-translations")
|
||||||
gulp.parallel(
|
|
||||||
"fetch-nightly-translations",
|
|
||||||
gulp.series("clean-translations", "ensure-translations-build-dir")
|
|
||||||
),
|
|
||||||
gulp.parallel("create-test-metadata", "create-test-translation"),
|
|
||||||
"build-master-translation",
|
|
||||||
"build-merged-translations",
|
|
||||||
"build-translation-fragment-supervisor",
|
|
||||||
"build-translation-flatten-supervisor",
|
|
||||||
"build-translation-fingerprints",
|
|
||||||
"build-translation-write-metadata"
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
@@ -99,7 +99,7 @@ gulp.task("webpack-watch-app", () => {
|
|||||||
).watch({ poll: isWsl }, doneHandler());
|
).watch({ poll: isWsl }, doneHandler());
|
||||||
gulp.watch(
|
gulp.watch(
|
||||||
path.join(paths.translations_src, "en.json"),
|
path.join(paths.translations_src, "en.json"),
|
||||||
gulp.series("create-translations", "copy-translations-app")
|
gulp.series("build-translations", "copy-translations-app")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,16 +0,0 @@
|
|||||||
const path = require("path");
|
|
||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
// Helper function to map recursively over files in a folder and it's subfolders
|
|
||||||
module.exports.mapFiles = function mapFiles(startPath, filter, mapFunc) {
|
|
||||||
const files = fs.readdirSync(startPath);
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
const filename = path.join(startPath, files[i]);
|
|
||||||
const stat = fs.lstatSync(filename);
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
mapFiles(filename, filter, mapFunc);
|
|
||||||
} else if (filename.indexOf(filter) >= 0) {
|
|
||||||
mapFunc(filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@@ -10,6 +10,7 @@ const WebpackBar = require("webpackbar");
|
|||||||
const {
|
const {
|
||||||
TransformAsyncModulesPlugin,
|
TransformAsyncModulesPlugin,
|
||||||
} = require("transform-async-modules-webpack-plugin");
|
} = require("transform-async-modules-webpack-plugin");
|
||||||
|
const { dependencies } = require("../package.json");
|
||||||
const paths = require("./paths.cjs");
|
const paths = require("./paths.cjs");
|
||||||
const bundle = require("./bundle.cjs");
|
const bundle = require("./bundle.cjs");
|
||||||
|
|
||||||
@@ -156,11 +157,15 @@ const createWebpackConfig = ({
|
|||||||
transform: (stats) => JSON.stringify(filterStats(stats)),
|
transform: (stats) => JSON.stringify(filterStats(stats)),
|
||||||
}),
|
}),
|
||||||
!latestBuild &&
|
!latestBuild &&
|
||||||
new TransformAsyncModulesPlugin({ browserslistEnv: "legacy" }),
|
new TransformAsyncModulesPlugin({
|
||||||
|
browserslistEnv: "legacy",
|
||||||
|
runtime: { version: dependencies["@babel/runtime"] },
|
||||||
|
}),
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".ts", ".js", ".json"],
|
extensions: [".ts", ".js", ".json"],
|
||||||
alias: {
|
alias: {
|
||||||
|
"lit/static-html$": "lit/static-html.js",
|
||||||
"lit/decorators$": "lit/decorators.js",
|
"lit/decorators$": "lit/decorators.js",
|
||||||
"lit/directive$": "lit/directive.js",
|
"lit/directive$": "lit/directive.js",
|
||||||
"lit/directives/until$": "lit/directives/until.js",
|
"lit/directives/until$": "lit/directives/until.js",
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import { mdiCast, mdiCastConnected } from "@mdi/js";
|
import { ActionDetail } from "@material/mwc-list/mwc-list";
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import { Auth, Connection } from "home-assistant-js-websocket";
|
import { Auth, Connection } from "home-assistant-js-websocket";
|
||||||
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
|
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
@@ -28,6 +27,7 @@ import { LovelaceViewConfig } from "../../../../src/data/lovelace/config/view";
|
|||||||
import "../../../../src/layouts/hass-loading-screen";
|
import "../../../../src/layouts/hass-loading-screen";
|
||||||
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
|
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
|
||||||
import "./hc-layout";
|
import "./hc-layout";
|
||||||
|
import "../../../../src/components/ha-list-item";
|
||||||
|
|
||||||
@customElement("hc-cast")
|
@customElement("hc-cast")
|
||||||
class HcCast extends LitElement {
|
class HcCast extends LitElement {
|
||||||
@@ -83,34 +83,37 @@ class HcCast extends LitElement {
|
|||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<div class="section-header">PICK A VIEW</div>
|
<div class="section-header">PICK A VIEW</div>
|
||||||
<paper-listbox
|
<mwc-list @action=${this._handlePickView} activatable>
|
||||||
attr-for-selected="data-path"
|
|
||||||
.selected=${this.castManager.status.lovelacePath || ""}
|
|
||||||
>
|
|
||||||
${(
|
${(
|
||||||
this.lovelaceViews ?? [
|
this.lovelaceViews ?? [
|
||||||
generateDefaultViewConfig({}, {}, {}, {}, () => ""),
|
generateDefaultViewConfig({}, {}, {}, {}, () => ""),
|
||||||
]
|
]
|
||||||
).map(
|
).map(
|
||||||
(view, idx) => html`
|
(view, idx) =>
|
||||||
<paper-icon-item
|
html`<ha-list-item
|
||||||
@click=${this._handlePickView}
|
graphic="avatar"
|
||||||
data-path=${view.path || idx}
|
.activated=${this.castManager.status?.lovelacePath ===
|
||||||
|
(view.path ?? idx)}
|
||||||
|
.selected=${this.castManager.status?.lovelacePath ===
|
||||||
|
(view.path ?? idx)}
|
||||||
>
|
>
|
||||||
|
${view.title || view.path || "Unnamed view"}
|
||||||
${view.icon
|
${view.icon
|
||||||
? html`
|
? html`
|
||||||
<ha-icon
|
<ha-icon
|
||||||
.icon=${view.icon}
|
.icon=${view.icon}
|
||||||
slot="item-icon"
|
slot="graphic"
|
||||||
></ha-icon>
|
></ha-icon>
|
||||||
`
|
`
|
||||||
: ""}
|
: html`<ha-svg-icon
|
||||||
${view.title || view.path}
|
slot="item-icon"
|
||||||
</paper-icon-item>
|
.path=${mdiViewDashboard}
|
||||||
`
|
></ha-svg-icon>`}</ha-list-item
|
||||||
)}
|
> `
|
||||||
</paper-listbox>
|
)}</mwc-list
|
||||||
|
>
|
||||||
`}
|
`}
|
||||||
|
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
${this.castManager.status
|
${this.castManager.status
|
||||||
? html`
|
? html`
|
||||||
@@ -182,8 +185,8 @@ class HcCast extends LitElement {
|
|||||||
this.castManager.requestSession();
|
this.castManager.requestSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handlePickView(ev: Event) {
|
private async _handlePickView(ev: CustomEvent<ActionDetail>) {
|
||||||
const path = (ev.currentTarget as any).getAttribute("data-path");
|
const path = this.lovelaceViews![ev.detail.index].path ?? ev.detail.index;
|
||||||
await ensureConnectedCastSession(this.castManager!, this.auth!);
|
await ensureConnectedCastSession(this.castManager!, this.auth!);
|
||||||
castSendShowLovelaceView(this.castManager, this.auth.data.hassUrl, path);
|
castSendShowLovelaceView(this.castManager, this.auth.data.hassUrl, path);
|
||||||
}
|
}
|
||||||
@@ -246,25 +249,14 @@ class HcCast extends LitElement {
|
|||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-listbox {
|
ha-list-item ha-icon,
|
||||||
padding-top: 0;
|
ha-list-item ha-svg-icon {
|
||||||
}
|
|
||||||
|
|
||||||
paper-listbox ha-icon {
|
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-icon-item {
|
:host([hide-icons]) ha-icon {
|
||||||
cursor: pointer;
|
display: none;
|
||||||
}
|
|
||||||
|
|
||||||
paper-icon-item[disabled] {
|
|
||||||
cursor: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([hide-icons]) paper-icon-item {
|
|
||||||
--paper-item-icon-width: 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.spacer {
|
.spacer {
|
||||||
|
@@ -2,6 +2,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import { LovelaceConfig } from "../../../../src/data/lovelace/config/types";
|
import { LovelaceConfig } from "../../../../src/data/lovelace/config/types";
|
||||||
|
import { getPanelTitleFromUrlPath } from "../../../../src/data/panel";
|
||||||
import { Lovelace } from "../../../../src/panels/lovelace/types";
|
import { Lovelace } from "../../../../src/panels/lovelace/types";
|
||||||
import "../../../../src/panels/lovelace/views/hui-view";
|
import "../../../../src/panels/lovelace/views/hui-view";
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
@@ -61,7 +62,12 @@ class HcLovelace extends LitElement {
|
|||||||
const index = this._viewIndex;
|
const index = this._viewIndex;
|
||||||
|
|
||||||
if (index !== undefined) {
|
if (index !== undefined) {
|
||||||
const dashboardTitle = this.lovelaceConfig.title || this.urlPath;
|
const title = getPanelTitleFromUrlPath(
|
||||||
|
this.hass,
|
||||||
|
this.urlPath || "lovelace"
|
||||||
|
);
|
||||||
|
|
||||||
|
const dashboardTitle = title || this.urlPath;
|
||||||
|
|
||||||
const viewTitle =
|
const viewTitle =
|
||||||
this.lovelaceConfig.views[index].title ||
|
this.lovelaceConfig.views[index].title ||
|
||||||
@@ -80,10 +86,17 @@ class HcLovelace extends LitElement {
|
|||||||
this.lovelaceConfig.views[index].background ||
|
this.lovelaceConfig.views[index].background ||
|
||||||
this.lovelaceConfig.background;
|
this.lovelaceConfig.background;
|
||||||
|
|
||||||
if (configBackground) {
|
const backgroundStyle =
|
||||||
|
typeof configBackground === "string"
|
||||||
|
? configBackground
|
||||||
|
: configBackground?.image
|
||||||
|
? `center / cover no-repeat url('${configBackground.image}')`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (backgroundStyle) {
|
||||||
this._huiView!.style.setProperty(
|
this._huiView!.style.setProperty(
|
||||||
"--lovelace-background",
|
"--lovelace-background",
|
||||||
configBackground
|
backgroundStyle
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this._huiView!.style.removeProperty("--lovelace-background");
|
this._huiView!.style.removeProperty("--lovelace-background");
|
||||||
|
@@ -35,6 +35,7 @@ import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/lo
|
|||||||
import { HassElement } from "../../../../src/state/hass-element";
|
import { HassElement } from "../../../../src/state/hass-element";
|
||||||
import { castContext } from "../cast_context";
|
import { castContext } from "../cast_context";
|
||||||
import "./hc-launch-screen";
|
import "./hc-launch-screen";
|
||||||
|
import { getPanelTitleFromUrlPath } from "../../../../src/data/panel";
|
||||||
|
|
||||||
const DEFAULT_CONFIG: LovelaceDashboardStrategyConfig = {
|
const DEFAULT_CONFIG: LovelaceDashboardStrategyConfig = {
|
||||||
strategy: {
|
strategy: {
|
||||||
@@ -359,7 +360,11 @@ export class HcMain extends HassElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) {
|
private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) {
|
||||||
castContext.setApplicationState(lovelaceConfig.title || "");
|
const title = getPanelTitleFromUrlPath(
|
||||||
|
this.hass!,
|
||||||
|
this._urlPath || "lovelace"
|
||||||
|
);
|
||||||
|
castContext.setApplicationState(title || "");
|
||||||
this._lovelaceConfig = lovelaceConfig;
|
this._lovelaceConfig = lovelaceConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { until } from "lit/directives/until";
|
import { until } from "lit/directives/until";
|
||||||
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
|
import "../../../src/components/ha-button";
|
||||||
import "../../../src/components/ha-circular-progress";
|
import "../../../src/components/ha-circular-progress";
|
||||||
import { LovelaceCardConfig } from "../../../src/data/lovelace/config/card";
|
import { LovelaceCardConfig } from "../../../src/data/lovelace/config/card";
|
||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
@@ -11,7 +12,6 @@ import {
|
|||||||
demoConfigs,
|
demoConfigs,
|
||||||
selectedDemoConfig,
|
selectedDemoConfig,
|
||||||
selectedDemoConfigIndex,
|
selectedDemoConfigIndex,
|
||||||
setDemoConfig,
|
|
||||||
} from "../configs/demo-configs";
|
} from "../configs/demo-configs";
|
||||||
|
|
||||||
@customElement("ha-demo-card")
|
@customElement("ha-demo-card")
|
||||||
@@ -64,9 +64,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mwc-button @click=${this._nextConfig} .disabled=${this._switching}>
|
<ha-button @click=${this._nextConfig} .disabled=${this._switching}>
|
||||||
${this.hass.localize("ui.panel.page-demo.cards.demo.next_demo")}
|
${this.hass.localize("ui.panel.page-demo.cards.demo.next_demo")}
|
||||||
</mwc-button>
|
</ha-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<p class="small-hidden">
|
<p class="small-hidden">
|
||||||
@@ -87,9 +87,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||||||
</div>
|
</div>
|
||||||
<div class="actions small-hidden">
|
<div class="actions small-hidden">
|
||||||
<a href="https://www.home-assistant.io" target="_blank">
|
<a href="https://www.home-assistant.io" target="_blank">
|
||||||
<mwc-button>
|
<ha-button>
|
||||||
${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")}
|
${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")}
|
||||||
</mwc-button>
|
</ha-button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
@@ -113,13 +113,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
private async _updateConfig(index: number) {
|
private async _updateConfig(index: number) {
|
||||||
this._switching = true;
|
this._switching = true;
|
||||||
try {
|
fireEvent(this, "set-demo-config" as any, { index });
|
||||||
await setDemoConfig(this.hass, this.lovelace!, index);
|
|
||||||
} catch (err: any) {
|
|
||||||
alert("Failed to switch config :-(");
|
|
||||||
} finally {
|
|
||||||
this._switching = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
@@ -149,7 +143,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||||||
height: 60px;
|
height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker mwc-button {
|
.picker ha-button {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,6 +10,7 @@ import {
|
|||||||
import { HomeAssistantAppEl } from "../../src/layouts/home-assistant";
|
import { HomeAssistantAppEl } from "../../src/layouts/home-assistant";
|
||||||
import { HomeAssistant } from "../../src/types";
|
import { HomeAssistant } from "../../src/types";
|
||||||
import { selectedDemoConfig } from "./configs/demo-configs";
|
import { selectedDemoConfig } from "./configs/demo-configs";
|
||||||
|
import { mockAreaRegistry } from "./stubs/area_registry";
|
||||||
import { mockAuth } from "./stubs/auth";
|
import { mockAuth } from "./stubs/auth";
|
||||||
import { mockConfigEntries } from "./stubs/config_entries";
|
import { mockConfigEntries } from "./stubs/config_entries";
|
||||||
import { mockEnergy } from "./stubs/energy";
|
import { mockEnergy } from "./stubs/energy";
|
||||||
@@ -23,10 +24,10 @@ import { mockLovelace } from "./stubs/lovelace";
|
|||||||
import { mockMediaPlayer } from "./stubs/media_player";
|
import { mockMediaPlayer } from "./stubs/media_player";
|
||||||
import { mockPersistentNotification } from "./stubs/persistent_notification";
|
import { mockPersistentNotification } from "./stubs/persistent_notification";
|
||||||
import { mockRecorder } from "./stubs/recorder";
|
import { mockRecorder } from "./stubs/recorder";
|
||||||
import { mockTodo } from "./stubs/todo";
|
|
||||||
import { mockSensor } from "./stubs/sensor";
|
import { mockSensor } from "./stubs/sensor";
|
||||||
import { mockSystemLog } from "./stubs/system_log";
|
import { mockSystemLog } from "./stubs/system_log";
|
||||||
import { mockTemplate } from "./stubs/template";
|
import { mockTemplate } from "./stubs/template";
|
||||||
|
import { mockTodo } from "./stubs/todo";
|
||||||
import { mockTranslations } from "./stubs/translations";
|
import { mockTranslations } from "./stubs/translations";
|
||||||
|
|
||||||
@customElement("ha-demo")
|
@customElement("ha-demo")
|
||||||
@@ -62,6 +63,7 @@ export class HaDemo extends HomeAssistantAppEl {
|
|||||||
mockEnergy(hass);
|
mockEnergy(hass);
|
||||||
mockPersistentNotification(hass);
|
mockPersistentNotification(hass);
|
||||||
mockConfigEntries(hass);
|
mockConfigEntries(hass);
|
||||||
|
mockAreaRegistry(hass);
|
||||||
mockEntityRegistry(hass, [
|
mockEntityRegistry(hass, [
|
||||||
{
|
{
|
||||||
config_entry_id: "co2signal",
|
config_entry_id: "co2signal",
|
||||||
@@ -72,6 +74,8 @@ export class HaDemo extends HomeAssistantAppEl {
|
|||||||
id: "sensor.co2_intensity",
|
id: "sensor.co2_intensity",
|
||||||
name: null,
|
name: null,
|
||||||
icon: null,
|
icon: null,
|
||||||
|
labels: [],
|
||||||
|
categories: {},
|
||||||
platform: "co2signal",
|
platform: "co2signal",
|
||||||
hidden_by: null,
|
hidden_by: null,
|
||||||
entity_category: null,
|
entity_category: null,
|
||||||
@@ -88,6 +92,8 @@ export class HaDemo extends HomeAssistantAppEl {
|
|||||||
id: "sensor.co2_intensity",
|
id: "sensor.co2_intensity",
|
||||||
name: null,
|
name: null,
|
||||||
icon: null,
|
icon: null,
|
||||||
|
labels: [],
|
||||||
|
categories: {},
|
||||||
platform: "co2signal",
|
platform: "co2signal",
|
||||||
hidden_by: null,
|
hidden_by: null,
|
||||||
entity_category: null,
|
entity_category: null,
|
||||||
|
@@ -4,4 +4,11 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
|||||||
export const mockAreaRegistry = (
|
export const mockAreaRegistry = (
|
||||||
hass: MockHomeAssistant,
|
hass: MockHomeAssistant,
|
||||||
data: AreaRegistryEntry[] = []
|
data: AreaRegistryEntry[] = []
|
||||||
) => hass.mockWS("config/area_registry/list", () => data);
|
) => {
|
||||||
|
hass.mockWS("config/area_registry/list", () => data);
|
||||||
|
const areas = {};
|
||||||
|
data.forEach((area) => {
|
||||||
|
areas[area.area_id] = area;
|
||||||
|
});
|
||||||
|
hass.updateHass({ areas });
|
||||||
|
};
|
||||||
|
@@ -10,6 +10,7 @@ export const mockConfigEntries = (hass: MockHomeAssistant) => {
|
|||||||
supports_options: false,
|
supports_options: false,
|
||||||
supports_remove_device: false,
|
supports_remove_device: false,
|
||||||
supports_unload: true,
|
supports_unload: true,
|
||||||
|
supports_reconfigure: true,
|
||||||
pref_disable_new_entities: false,
|
pref_disable_new_entities: false,
|
||||||
pref_disable_polling: false,
|
pref_disable_polling: false,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
|
@@ -4,4 +4,11 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
|||||||
export const mockDeviceRegistry = (
|
export const mockDeviceRegistry = (
|
||||||
hass: MockHomeAssistant,
|
hass: MockHomeAssistant,
|
||||||
data: DeviceRegistryEntry[] = []
|
data: DeviceRegistryEntry[] = []
|
||||||
) => hass.mockWS("config/device_registry/list", () => data);
|
) => {
|
||||||
|
hass.mockWS("config/device_registry/list", () => data);
|
||||||
|
const devices = {};
|
||||||
|
data.forEach((device) => {
|
||||||
|
devices[device.id] = device;
|
||||||
|
});
|
||||||
|
hass.updateHass({ devices });
|
||||||
|
};
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { format, startOfToday, startOfTomorrow } from "date-fns/esm";
|
import { format, startOfToday, startOfTomorrow } from "date-fns";
|
||||||
import {
|
import {
|
||||||
EnergyInfo,
|
EnergyInfo,
|
||||||
EnergyPreferences,
|
EnergyPreferences,
|
||||||
|
7
demo/src/stubs/floor_registry.ts
Normal file
7
demo/src/stubs/floor_registry.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { FloorRegistryEntry } from "../../../src/data/floor_registry";
|
||||||
|
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
|
export const mockFloorRegistry = (
|
||||||
|
hass: MockHomeAssistant,
|
||||||
|
data: FloorRegistryEntry[] = []
|
||||||
|
) => hass.mockWS("config/floor_registry/list", () => data);
|
7
demo/src/stubs/label_registry.ts
Normal file
7
demo/src/stubs/label_registry.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { LabelRegistryEntry } from "../../../src/data/label_registry";
|
||||||
|
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
|
export const mockLabelRegistry = (
|
||||||
|
hass: MockHomeAssistant,
|
||||||
|
data: LabelRegistryEntry[] = []
|
||||||
|
) => hass.mockWS("config/label_registry/list", () => data);
|
@@ -1,9 +1,12 @@
|
|||||||
import type { LocalizeFunc } from "../../../src/common/translations/localize";
|
import type { LocalizeFunc } from "../../../src/common/translations/localize";
|
||||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
import { selectedDemoConfig } from "../configs/demo-configs";
|
import {
|
||||||
|
selectedDemoConfig,
|
||||||
|
selectedDemoConfigIndex,
|
||||||
|
setDemoConfig,
|
||||||
|
} from "../configs/demo-configs";
|
||||||
import "../custom-cards/cast-demo-row";
|
import "../custom-cards/cast-demo-row";
|
||||||
import "../custom-cards/ha-demo-card";
|
import "../custom-cards/ha-demo-card";
|
||||||
import type { HADemoCard } from "../custom-cards/ha-demo-card";
|
|
||||||
|
|
||||||
export const mockLovelace = (
|
export const mockLovelace = (
|
||||||
hass: MockHomeAssistant,
|
hass: MockHomeAssistant,
|
||||||
@@ -19,17 +22,22 @@ export const mockLovelace = (
|
|||||||
hass.mockWS("lovelace/resources", () => Promise.resolve([]));
|
hass.mockWS("lovelace/resources", () => Promise.resolve([]));
|
||||||
};
|
};
|
||||||
|
|
||||||
customElements.whenDefined("hui-view").then(() => {
|
customElements.whenDefined("hui-root").then(() => {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const HUIView = customElements.get("hui-view");
|
const HUIRoot = customElements.get("hui-root")!;
|
||||||
// Patch HUI-VIEW to make the lovelace object available to the demo card
|
|
||||||
const oldCreateCard = HUIView!.prototype.createCardElement;
|
|
||||||
|
|
||||||
HUIView!.prototype.createCardElement = function (config) {
|
const oldFirstUpdated = HUIRoot.prototype.firstUpdated;
|
||||||
const el = oldCreateCard.call(this, config);
|
|
||||||
if (el.tagName === "HA-DEMO-CARD") {
|
HUIRoot.prototype.firstUpdated = function (changedProperties) {
|
||||||
(el as HADemoCard).lovelace = this.lovelace;
|
oldFirstUpdated.call(this, changedProperties);
|
||||||
}
|
this.addEventListener("set-demo-config", async (ev) => {
|
||||||
return el;
|
const index = (ev as CustomEvent).detail.index;
|
||||||
|
try {
|
||||||
|
await setDemoConfig(this.hass, this.lovelace!, index);
|
||||||
|
} catch (err: any) {
|
||||||
|
setDemoConfig(this.hass, this.lovelace!, selectedDemoConfigIndex);
|
||||||
|
alert("Failed to switch config :-(");
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
import { load } from "js-yaml";
|
import { load } from "js-yaml";
|
||||||
import { html, css, LitElement, PropertyValues } from "lit";
|
import { LitElement, PropertyValueMap, css, html, nothing } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element";
|
import memoizeOne from "memoize-one";
|
||||||
|
import "../../../src/panels/lovelace/cards/hui-card";
|
||||||
|
import type { HuiCard } from "../../../src/panels/lovelace/cards/hui-card";
|
||||||
import { HomeAssistant } from "../../../src/types";
|
import { HomeAssistant } from "../../../src/types";
|
||||||
|
|
||||||
export interface DemoCardConfig {
|
export interface DemoCardConfig {
|
||||||
@@ -19,7 +21,12 @@ class DemoCard extends LitElement {
|
|||||||
|
|
||||||
@state() private _size?: number;
|
@state() private _size?: number;
|
||||||
|
|
||||||
@query("#card") private _card!: HTMLElement;
|
@query("hui-card", false) private _card?: HuiCard;
|
||||||
|
|
||||||
|
private _config = memoizeOne((config: string) => {
|
||||||
|
const c = (load(config) as any)[0];
|
||||||
|
return c;
|
||||||
|
});
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
@@ -30,63 +37,32 @@ class DemoCard extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<div id="card"></div>
|
<hui-card
|
||||||
${this.showConfig ? html`<pre>${this.config.config.trim()}</pre>` : ""}
|
.config=${this._config(this.config.config)}
|
||||||
|
.hass=${this.hass}
|
||||||
|
@card-updated=${this._cardUpdated}
|
||||||
|
></hui-card>
|
||||||
|
${this.showConfig
|
||||||
|
? html`<pre>${this.config.config.trim()}</pre>`
|
||||||
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
updated(changedProps: PropertyValues) {
|
private async _cardUpdated(ev) {
|
||||||
super.updated(changedProps);
|
ev.stopPropagation();
|
||||||
|
this._updateSize();
|
||||||
if (changedProps.has("config")) {
|
|
||||||
const card = this._card;
|
|
||||||
while (card.lastChild) {
|
|
||||||
card.removeChild(card.lastChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
const el = this._createCardElement((load(this.config.config) as any)[0]);
|
|
||||||
card.appendChild(el);
|
|
||||||
this._getSize(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changedProps.has("hass")) {
|
|
||||||
const card = this._card.lastChild;
|
|
||||||
if (card) {
|
|
||||||
(card as any).hass = this.hass;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getSize(el) {
|
private async _updateSize() {
|
||||||
await customElements.whenDefined(el.localName);
|
this._size = await this._card?.getCardSize();
|
||||||
|
|
||||||
if (!("getCardSize" in el)) {
|
|
||||||
this._size = undefined;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._size = await el.getCardSize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_createCardElement(cardConfig) {
|
protected update(
|
||||||
const element = createCardElement(cardConfig);
|
_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
||||||
if (this.hass) {
|
): void {
|
||||||
element.hass = this.hass;
|
super.update(_changedProperties);
|
||||||
}
|
this._updateSize();
|
||||||
element.addEventListener(
|
|
||||||
"ll-rebuild",
|
|
||||||
(ev) => {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._rebuildCard(element, cardConfig);
|
|
||||||
},
|
|
||||||
{ once: true }
|
|
||||||
);
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
_rebuildCard(cardElToReplace, config) {
|
|
||||||
const newCardEl = this._createCardElement(config);
|
|
||||||
cardElToReplace.parentElement.replaceChild(newCardEl, cardElToReplace);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
@@ -101,7 +77,7 @@ class DemoCard extends LitElement {
|
|||||||
font-size: 0.5em;
|
font-size: 0.5em;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
#card {
|
hui-card {
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,7 @@ export const basicTrace: DemoTrace = {
|
|||||||
{
|
{
|
||||||
path: "trigger/0",
|
path: "trigger/0",
|
||||||
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
||||||
|
changed_variables: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"condition/0": [
|
"condition/0": [
|
||||||
|
@@ -17,6 +17,7 @@ export const motionLightTrace: DemoTrace = {
|
|||||||
{
|
{
|
||||||
path: "trigger/0",
|
path: "trigger/0",
|
||||||
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
timestamp: "2021-03-25T04:36:51.223693+00:00",
|
||||||
|
changed_variables: {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"action/0": [
|
"action/0": [
|
||||||
|
@@ -64,6 +64,12 @@ const ACTIONS = [
|
|||||||
entity_id: "input_boolean.toggle_4",
|
entity_id: "input_boolean.toggle_4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
sequence: [
|
||||||
|
{ scene: "scene.kitchen_morning" },
|
||||||
|
{ service: "light.turn_off", target: { entity_id: "light.kitchen" } },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
parallel: [
|
parallel: [
|
||||||
{ scene: "scene.kitchen_morning" },
|
{ scene: "scene.kitchen_morning" },
|
||||||
@@ -136,7 +142,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
|||||||
<div class="action">
|
<div class="action">
|
||||||
<span>
|
<span>
|
||||||
${this._action
|
${this._action
|
||||||
? describeAction(this.hass, [], this._action)
|
? describeAction(this.hass, [], [], [], this._action)
|
||||||
: "<invalid YAML>"}
|
: "<invalid YAML>"}
|
||||||
</span>
|
</span>
|
||||||
<ha-yaml-editor
|
<ha-yaml-editor
|
||||||
@@ -149,7 +155,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
|||||||
${ACTIONS.map(
|
${ACTIONS.map(
|
||||||
(conf) => html`
|
(conf) => html`
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<span>${describeAction(this.hass, [], conf as any)}</span>
|
<span>${describeAction(this.hass, [], [], [], conf as any)}</span>
|
||||||
<pre>${dump(conf)}</pre>
|
<pre>${dump(conf)}</pre>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
@@ -21,10 +21,10 @@ const ENTITIES = [
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const conditions = [
|
const conditions: Condition[] = [
|
||||||
{ condition: "and" },
|
{ condition: "and", conditions: [] },
|
||||||
{ condition: "not" },
|
{ condition: "not", conditions: [] },
|
||||||
{ condition: "or" },
|
{ condition: "or", conditions: [] },
|
||||||
{ condition: "state", entity_id: "light.kitchen", state: "on" },
|
{ condition: "state", entity_id: "light.kitchen", state: "on" },
|
||||||
{
|
{
|
||||||
condition: "numeric_state",
|
condition: "numeric_state",
|
||||||
@@ -34,11 +34,11 @@ const conditions = [
|
|||||||
above: 20,
|
above: 20,
|
||||||
},
|
},
|
||||||
{ condition: "sun", after: "sunset" },
|
{ condition: "sun", after: "sunset" },
|
||||||
{ condition: "sun", after: "sunrise", offset: "-01:00" },
|
{ condition: "sun", after: "sunrise", before_offset: 3600 },
|
||||||
{ condition: "zone", entity_id: "device_tracker.person", zone: "zone.home" },
|
{ condition: "zone", entity_id: "device_tracker.person", zone: "zone.home" },
|
||||||
{ condition: "trigger", id: "motion" },
|
{ condition: "trigger", id: "motion" },
|
||||||
{ condition: "time" },
|
{ condition: "time" },
|
||||||
{ condition: "template" },
|
{ condition: "template", value_template: "" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const initialCondition: Condition = {
|
const initialCondition: Condition = {
|
||||||
|
@@ -20,6 +20,7 @@ import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation
|
|||||||
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
|
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
|
||||||
import { Action } from "../../../../src/data/script";
|
import { Action } from "../../../../src/data/script";
|
||||||
import { HaConditionAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-condition";
|
import { HaConditionAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-condition";
|
||||||
|
import { HaSequenceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-sequence";
|
||||||
import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel";
|
import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel";
|
||||||
import { HaIfAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-if";
|
import { HaIfAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-if";
|
||||||
import { HaStopAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-stop";
|
import { HaStopAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-stop";
|
||||||
@@ -39,6 +40,7 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [
|
|||||||
{ name: "If-Then", actions: [HaIfAction.defaultConfig] },
|
{ name: "If-Then", actions: [HaIfAction.defaultConfig] },
|
||||||
{ name: "Choose", actions: [HaChooseAction.defaultConfig] },
|
{ name: "Choose", actions: [HaChooseAction.defaultConfig] },
|
||||||
{ name: "Variables", actions: [{ variables: { hello: "1" } }] },
|
{ name: "Variables", actions: [{ variables: { hello: "1" } }] },
|
||||||
|
{ name: "Sequence", actions: [HaSequenceAction.defaultConfig] },
|
||||||
{ name: "Parallel", actions: [HaParallelAction.defaultConfig] },
|
{ name: "Parallel", actions: [HaParallelAction.defaultConfig] },
|
||||||
{ name: "Stop", actions: [HaStopAction.defaultConfig] },
|
{ name: "Stop", actions: [HaStopAction.defaultConfig] },
|
||||||
];
|
];
|
||||||
|
@@ -55,6 +55,7 @@ export class DemoAutomationTraceTimeline extends LitElement {
|
|||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
const hass = provideHass(this);
|
const hass = provideHass(this);
|
||||||
hass.updateTranslations(null, "en");
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.updateTranslations("config", "en");
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
|
@@ -60,6 +60,7 @@ export class DemoAutomationTrace extends LitElement {
|
|||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
const hass = provideHass(this);
|
const hass = provideHass(this);
|
||||||
hass.updateTranslations(null, "en");
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.updateTranslations("config", "en");
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
|
@@ -162,7 +162,7 @@ export class DemoHaBarButton extends LitElement {
|
|||||||
}
|
}
|
||||||
.custom-group {
|
.custom-group {
|
||||||
--control-button-group-thickness: 100px;
|
--control-button-group-thickness: 100px;
|
||||||
--control-button-group-border-radius: 18px;
|
--control-button-group-border-radius: 36px;
|
||||||
--control-button-group-spacing: 20px;
|
--control-button-group-spacing: 20px;
|
||||||
}
|
}
|
||||||
.custom-group ha-control-button {
|
.custom-group ha-control-button {
|
||||||
|
@@ -94,7 +94,7 @@ export class DemoHarControlNumberButtons extends LitElement {
|
|||||||
--control-number-buttons-background-color: #2196f3;
|
--control-number-buttons-background-color: #2196f3;
|
||||||
--control-number-buttons-background-opacity: 0.1;
|
--control-number-buttons-background-opacity: 0.1;
|
||||||
--control-number-buttons-thickness: 100px;
|
--control-number-buttons-thickness: 100px;
|
||||||
--control-number-buttons-border-radius: 24px;
|
--control-number-buttons-border-radius: 36px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -186,8 +186,8 @@ export class DemoHaControlSelect extends LitElement {
|
|||||||
.custom {
|
.custom {
|
||||||
--mdc-icon-size: 24px;
|
--mdc-icon-size: 24px;
|
||||||
--control-select-color: var(--state-fan-active-color);
|
--control-select-color: var(--state-fan-active-color);
|
||||||
--control-select-thickness: 100px;
|
--control-select-thickness: 130px;
|
||||||
--control-select-border-radius: 24px;
|
--control-select-border-radius: 36px;
|
||||||
}
|
}
|
||||||
.vertical-selects {
|
.vertical-selects {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
|
@@ -150,8 +150,8 @@ export class DemoHaBarSlider extends LitElement {
|
|||||||
--control-slider-color: #ffcf4c;
|
--control-slider-color: #ffcf4c;
|
||||||
--control-slider-background: #ffcf4c;
|
--control-slider-background: #ffcf4c;
|
||||||
--control-slider-background-opacity: 0.2;
|
--control-slider-background-opacity: 0.2;
|
||||||
--control-slider-thickness: 100px;
|
--control-slider-thickness: 130px;
|
||||||
--control-slider-border-radius: 24px;
|
--control-slider-border-radius: 36px;
|
||||||
}
|
}
|
||||||
.vertical-sliders {
|
.vertical-sliders {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
|
@@ -117,8 +117,8 @@ export class DemoHaControlSwitch extends LitElement {
|
|||||||
.custom {
|
.custom {
|
||||||
--control-switch-on-color: var(--green-color);
|
--control-switch-on-color: var(--green-color);
|
||||||
--control-switch-off-color: var(--red-color);
|
--control-switch-off-color: var(--red-color);
|
||||||
--control-switch-thickness: 100px;
|
--control-switch-thickness: 130px;
|
||||||
--control-switch-border-radius: 24px;
|
--control-switch-border-radius: 36px;
|
||||||
--control-switch-padding: 6px;
|
--control-switch-padding: 6px;
|
||||||
--mdc-icon-size: 24px;
|
--mdc-icon-size: 24px;
|
||||||
}
|
}
|
||||||
|
@@ -59,6 +59,7 @@ const DEVICES = [
|
|||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
@@ -77,6 +78,7 @@ const DEVICES = [
|
|||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: null,
|
area_id: null,
|
||||||
@@ -95,30 +97,37 @@ const DEVICES = [
|
|||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const AREAS: AreaRegistryEntry[] = [
|
const AREAS: AreaRegistryEntry[] = [
|
||||||
{
|
{
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
|
floor_id: null,
|
||||||
name: "Backyard",
|
name: "Backyard",
|
||||||
icon: null,
|
icon: null,
|
||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "bedroom",
|
area_id: "bedroom",
|
||||||
|
floor_id: null,
|
||||||
name: "Bedroom",
|
name: "Bedroom",
|
||||||
icon: "mdi:bed",
|
icon: "mdi:bed",
|
||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "livingroom",
|
area_id: "livingroom",
|
||||||
|
floor_id: null,
|
||||||
name: "Livingroom",
|
name: "Livingroom",
|
||||||
icon: "mdi:sofa",
|
icon: "mdi:sofa",
|
||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -17,6 +17,10 @@ import { provideHass } from "../../../../src/fake_data/provide_hass";
|
|||||||
import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
|
import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
|
||||||
import type { HomeAssistant } from "../../../../src/types";
|
import type { HomeAssistant } from "../../../../src/types";
|
||||||
import "../../components/demo-black-white-row";
|
import "../../components/demo-black-white-row";
|
||||||
|
import { FloorRegistryEntry } from "../../../../src/data/floor_registry";
|
||||||
|
import { LabelRegistryEntry } from "../../../../src/data/label_registry";
|
||||||
|
import { mockFloorRegistry } from "../../../../demo/src/stubs/floor_registry";
|
||||||
|
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
|
||||||
|
|
||||||
const ENTITIES = [
|
const ENTITIES = [
|
||||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||||
@@ -55,6 +59,7 @@ const DEVICES = [
|
|||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
@@ -73,6 +78,7 @@ const DEVICES = [
|
|||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: null,
|
area_id: null,
|
||||||
@@ -91,30 +97,78 @@ const DEVICES = [
|
|||||||
hw_version: null,
|
hw_version: null,
|
||||||
via_device_id: null,
|
via_device_id: null,
|
||||||
serial_number: null,
|
serial_number: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const AREAS: AreaRegistryEntry[] = [
|
const AREAS: AreaRegistryEntry[] = [
|
||||||
{
|
{
|
||||||
area_id: "backyard",
|
area_id: "backyard",
|
||||||
|
floor_id: "ground",
|
||||||
name: "Backyard",
|
name: "Backyard",
|
||||||
icon: null,
|
icon: null,
|
||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "bedroom",
|
area_id: "bedroom",
|
||||||
|
floor_id: "first",
|
||||||
name: "Bedroom",
|
name: "Bedroom",
|
||||||
icon: "mdi:bed",
|
icon: "mdi:bed",
|
||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
area_id: "livingroom",
|
area_id: "livingroom",
|
||||||
|
floor_id: "ground",
|
||||||
name: "Livingroom",
|
name: "Livingroom",
|
||||||
icon: "mdi:sofa",
|
icon: "mdi:sofa",
|
||||||
picture: null,
|
picture: null,
|
||||||
aliases: [],
|
aliases: [],
|
||||||
|
labels: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const FLOORS: FloorRegistryEntry[] = [
|
||||||
|
{
|
||||||
|
floor_id: "ground",
|
||||||
|
name: "Ground floor",
|
||||||
|
level: 0,
|
||||||
|
icon: null,
|
||||||
|
aliases: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
floor_id: "first",
|
||||||
|
name: "First floor",
|
||||||
|
level: 1,
|
||||||
|
icon: "mdi:numeric-1",
|
||||||
|
aliases: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
floor_id: "second",
|
||||||
|
name: "Second floor",
|
||||||
|
level: 2,
|
||||||
|
icon: "mdi:numeric-2",
|
||||||
|
aliases: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const LABELS: LabelRegistryEntry[] = [
|
||||||
|
{
|
||||||
|
label_id: "energy",
|
||||||
|
name: "Energy",
|
||||||
|
icon: null,
|
||||||
|
color: "yellow",
|
||||||
|
description: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label_id: "entertainment",
|
||||||
|
name: "Entertainment",
|
||||||
|
icon: "mdi:popcorn",
|
||||||
|
color: "blue",
|
||||||
|
description: null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -125,7 +179,12 @@ const SCHEMAS: {
|
|||||||
{
|
{
|
||||||
name: "One of each",
|
name: "One of each",
|
||||||
input: {
|
input: {
|
||||||
|
label: { name: "Label", selector: { label: {} } },
|
||||||
|
floor: { name: "Floor", selector: { floor: {} } },
|
||||||
|
area: { name: "Area", selector: { area: {} } },
|
||||||
|
device: { name: "Device", selector: { device: {} } },
|
||||||
entity: { name: "Entity", selector: { entity: {} } },
|
entity: { name: "Entity", selector: { entity: {} } },
|
||||||
|
target: { name: "Target", selector: { target: {} } },
|
||||||
state: {
|
state: {
|
||||||
name: "State",
|
name: "State",
|
||||||
selector: { state: { entity_id: "alarm_control_panel.alarm" } },
|
selector: { state: { entity_id: "alarm_control_panel.alarm" } },
|
||||||
@@ -134,15 +193,12 @@ const SCHEMAS: {
|
|||||||
name: "Attribute",
|
name: "Attribute",
|
||||||
selector: { attribute: { entity_id: "" } },
|
selector: { attribute: { entity_id: "" } },
|
||||||
},
|
},
|
||||||
device: { name: "Device", selector: { device: {} } },
|
|
||||||
config_entry: {
|
config_entry: {
|
||||||
name: "Integration",
|
name: "Integration",
|
||||||
selector: { config_entry: {} },
|
selector: { config_entry: {} },
|
||||||
},
|
},
|
||||||
duration: { name: "Duration", selector: { duration: {} } },
|
duration: { name: "Duration", selector: { duration: {} } },
|
||||||
addon: { name: "Addon", selector: { addon: {} } },
|
addon: { name: "Addon", selector: { addon: {} } },
|
||||||
area: { name: "Area", selector: { area: {} } },
|
|
||||||
target: { name: "Target", selector: { target: {} } },
|
|
||||||
number_box: {
|
number_box: {
|
||||||
name: "Number Box",
|
name: "Number Box",
|
||||||
selector: {
|
selector: {
|
||||||
@@ -291,6 +347,8 @@ const SCHEMAS: {
|
|||||||
entity: { name: "Entity", selector: { entity: { multiple: true } } },
|
entity: { name: "Entity", selector: { entity: { multiple: true } } },
|
||||||
device: { name: "Device", selector: { device: { multiple: true } } },
|
device: { name: "Device", selector: { device: { multiple: true } } },
|
||||||
area: { name: "Area", selector: { area: { multiple: true } } },
|
area: { name: "Area", selector: { area: { multiple: true } } },
|
||||||
|
floor: { name: "Floor", selector: { floor: { multiple: true } } },
|
||||||
|
label: { name: "Label", selector: { label: { multiple: true } } },
|
||||||
select: {
|
select: {
|
||||||
name: "Select Multiple",
|
name: "Select Multiple",
|
||||||
selector: {
|
selector: {
|
||||||
@@ -347,6 +405,8 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
|||||||
mockDeviceRegistry(hass, DEVICES);
|
mockDeviceRegistry(hass, DEVICES);
|
||||||
mockConfigEntries(hass);
|
mockConfigEntries(hass);
|
||||||
mockAreaRegistry(hass, AREAS);
|
mockAreaRegistry(hass, AREAS);
|
||||||
|
mockFloorRegistry(hass, FLOORS);
|
||||||
|
mockLabelRegistry(hass, LABELS);
|
||||||
mockHassioSupervisor(hass);
|
mockHassioSupervisor(hass);
|
||||||
hass.mockWS("auth/sign_path", (params) => params);
|
hass.mockWS("auth/sign_path", (params) => params);
|
||||||
hass.mockWS("media_player/browse_media", this._browseMedia);
|
hass.mockWS("media_player/browse_media", this._browseMedia);
|
||||||
|
@@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeNumeric extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatDateTimeNumeric(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatDateTimeNumeric(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateTimeNumeric(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateTimeNumeric(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatDateTimeNumeric(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatDateTimeNumeric(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeSeconds extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatDateTimeWithSeconds(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatDateTimeWithSeconds(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateTimeWithSeconds(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateTimeWithSeconds(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatDateTimeWithSeconds(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatDateTimeWithSeconds(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeShortYear extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatShortDateTimeWithYear(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatShortDateTimeWithYear(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatShortDateTimeWithYear(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatShortDateTimeWithYear(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatShortDateTimeWithYear(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatShortDateTimeWithYear(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -56,48 +56,46 @@ export class DemoDateTimeDateTimeShort extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatShortDateTime(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatShortDateTime(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatShortDateTime(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatShortDateTime(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatShortDateTime(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatShortDateTime(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -56,48 +56,46 @@ export class DemoDateTimeDateTime extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatDateTime(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatDateTime(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateTime(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateTime(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatDateTime(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatDateTime(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -35,59 +35,57 @@ export class DemoDateTimeDate extends LitElement {
|
|||||||
<div class="center">Month-Day-Year</div>
|
<div class="center">Month-Day-Year</div>
|
||||||
<div class="center">Year-Month-Day</div>
|
<div class="center">Year-Month-Day</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatDateNumeric(
|
||||||
<div class="center">
|
date,
|
||||||
${formatDateNumeric(
|
{
|
||||||
date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
date_format: DateFormat.language,
|
||||||
language: key,
|
},
|
||||||
date_format: DateFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateNumeric(
|
|
||||||
date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
date_format: DateFormat.DMY,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateNumeric(
|
|
||||||
date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
date_format: DateFormat.MDY,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatDateNumeric(
|
|
||||||
date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
date_format: DateFormat.YMD,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatDateNumeric(
|
||||||
|
date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
date_format: DateFormat.DMY,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatDateNumeric(
|
||||||
|
date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
date_format: DateFormat.MDY,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatDateNumeric(
|
||||||
|
date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
date_format: DateFormat.YMD,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -56,48 +56,46 @@ export class DemoDateTimeTimeSeconds extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatTimeWithSeconds(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatTimeWithSeconds(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatTimeWithSeconds(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatTimeWithSeconds(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatTimeWithSeconds(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatTimeWithSeconds(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -56,48 +56,46 @@ export class DemoDateTimeTimeWeekday extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatTimeWeekday(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatTimeWeekday(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatTimeWeekday(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatTimeWeekday(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatTimeWeekday(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatTimeWeekday(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -56,48 +56,46 @@ export class DemoDateTimeTime extends LitElement {
|
|||||||
<div class="center">12 Hours</div>
|
<div class="center">12 Hours</div>
|
||||||
<div class="center">24 Hours</div>
|
<div class="center">24 Hours</div>
|
||||||
</div>
|
</div>
|
||||||
${Object.entries(translationMetadata.translations)
|
${Object.entries(translationMetadata.translations).map(
|
||||||
.filter(([key, _]) => key !== "test")
|
([key, value]) => html`
|
||||||
.map(
|
<div class="container">
|
||||||
([key, value]) => html`
|
<div>${value.nativeName}</div>
|
||||||
<div class="container">
|
<div class="center">
|
||||||
<div>${value.nativeName}</div>
|
${formatTime(
|
||||||
<div class="center">
|
this.date,
|
||||||
${formatTime(
|
{
|
||||||
this.date,
|
...defaultLocale,
|
||||||
{
|
language: key,
|
||||||
...defaultLocale,
|
time_format: TimeFormat.language,
|
||||||
language: key,
|
},
|
||||||
time_format: TimeFormat.language,
|
demoConfig
|
||||||
},
|
)}
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatTime(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.am_pm,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
${formatTime(
|
|
||||||
this.date,
|
|
||||||
{
|
|
||||||
...defaultLocale,
|
|
||||||
language: key,
|
|
||||||
time_format: TimeFormat.twenty_four,
|
|
||||||
},
|
|
||||||
demoConfig
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`
|
<div class="center">
|
||||||
)}
|
${formatTime(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.am_pm,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
${formatTime(
|
||||||
|
this.date,
|
||||||
|
{
|
||||||
|
...defaultLocale,
|
||||||
|
language: key,
|
||||||
|
time_format: TimeFormat.twenty_four,
|
||||||
|
},
|
||||||
|
demoConfig
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@ const ENTITIES = [
|
|||||||
latitude: 32.877105,
|
latitude: 32.877105,
|
||||||
longitude: 117.232185,
|
longitude: 117.232185,
|
||||||
gps_accuracy: 91,
|
gps_accuracy: 91,
|
||||||
battery: 71,
|
battery: 25,
|
||||||
friendly_name: "Paulus",
|
friendly_name: "Paulus",
|
||||||
}),
|
}),
|
||||||
getEntity("device_tracker", "demo_anne_therese", "school", {
|
getEntity("device_tracker", "demo_anne_therese", "school", {
|
||||||
@@ -19,7 +19,7 @@ const ENTITIES = [
|
|||||||
latitude: 32.877105,
|
latitude: 32.877105,
|
||||||
longitude: 117.232185,
|
longitude: 117.232185,
|
||||||
gps_accuracy: 91,
|
gps_accuracy: 91,
|
||||||
battery: 71,
|
battery: 50,
|
||||||
friendly_name: "Anne Therese",
|
friendly_name: "Anne Therese",
|
||||||
}),
|
}),
|
||||||
getEntity("device_tracker", "demo_home_boy", "home", {
|
getEntity("device_tracker", "demo_home_boy", "home", {
|
||||||
@@ -27,7 +27,7 @@ const ENTITIES = [
|
|||||||
latitude: 32.877105,
|
latitude: 32.877105,
|
||||||
longitude: 117.232185,
|
longitude: 117.232185,
|
||||||
gps_accuracy: 91,
|
gps_accuracy: 91,
|
||||||
battery: 71,
|
battery: 75,
|
||||||
friendly_name: "Home Boy",
|
friendly_name: "Home Boy",
|
||||||
}),
|
}),
|
||||||
getEntity("light", "bed_light", "on", {
|
getEntity("light", "bed_light", "on", {
|
||||||
@@ -39,21 +39,53 @@ const ENTITIES = [
|
|||||||
getEntity("light", "ceiling_lights", "off", {
|
getEntity("light", "ceiling_lights", "off", {
|
||||||
friendly_name: "Ceiling Lights",
|
friendly_name: "Ceiling Lights",
|
||||||
}),
|
}),
|
||||||
|
getEntity("sensor", "battery_1", 20, {
|
||||||
|
device_class: "battery",
|
||||||
|
friendly_name: "Battery 1",
|
||||||
|
unit_of_measurement: "%",
|
||||||
|
}),
|
||||||
|
getEntity("sensor", "battery_2", 35, {
|
||||||
|
device_class: "battery",
|
||||||
|
friendly_name: "Battery 2",
|
||||||
|
unit_of_measurement: "%",
|
||||||
|
}),
|
||||||
|
getEntity("sensor", "battery_3", 40, {
|
||||||
|
device_class: "battery",
|
||||||
|
friendly_name: "Battery 3",
|
||||||
|
unit_of_measurement: "%",
|
||||||
|
}),
|
||||||
|
getEntity("sensor", "battery_4", 80, {
|
||||||
|
device_class: "battery",
|
||||||
|
friendly_name: "Battery 4",
|
||||||
|
unit_of_measurement: "%",
|
||||||
|
}),
|
||||||
|
getEntity("input_number", "min_battery_level", 30, {
|
||||||
|
mode: "slider",
|
||||||
|
step: 10,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
icon: "mdi:battery-alert-variant",
|
||||||
|
friendly_name: "Minimum Battery Level",
|
||||||
|
unit_of_measurement: "%",
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const CONFIGS = [
|
const CONFIGS = [
|
||||||
{
|
{
|
||||||
heading: "Unfiltered controller",
|
heading: "Unfiltered entities",
|
||||||
config: `
|
config: `
|
||||||
- type: entities
|
- type: entities
|
||||||
entities:
|
entities:
|
||||||
- light.bed_light
|
- device_tracker.demo_anne_therese
|
||||||
- light.ceiling_lights
|
- device_tracker.demo_home_boy
|
||||||
- light.kitchen_lights
|
- device_tracker.demo_paulus
|
||||||
|
- light.bed_light
|
||||||
|
- light.ceiling_lights
|
||||||
|
- light.kitchen_lights
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Filtered entities card",
|
heading: "On and home entities",
|
||||||
config: `
|
config: `
|
||||||
- type: entity-filter
|
- type: entity-filter
|
||||||
entities:
|
entities:
|
||||||
@@ -63,9 +95,28 @@ const CONFIGS = [
|
|||||||
- light.bed_light
|
- light.bed_light
|
||||||
- light.ceiling_lights
|
- light.ceiling_lights
|
||||||
- light.kitchen_lights
|
- light.kitchen_lights
|
||||||
state_filter:
|
conditions:
|
||||||
- "on"
|
- condition: state
|
||||||
- home
|
state:
|
||||||
|
- "on"
|
||||||
|
- home
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Same state as Bed Light",
|
||||||
|
config: `
|
||||||
|
- type: entity-filter
|
||||||
|
entities:
|
||||||
|
- device_tracker.demo_anne_therese
|
||||||
|
- device_tracker.demo_home_boy
|
||||||
|
- device_tracker.demo_paulus
|
||||||
|
- light.bed_light
|
||||||
|
- light.ceiling_lights
|
||||||
|
- light.kitchen_lights
|
||||||
|
conditions:
|
||||||
|
- condition: state
|
||||||
|
state:
|
||||||
|
- light.bed_light
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -79,9 +130,11 @@ const CONFIGS = [
|
|||||||
- light.bed_light
|
- light.bed_light
|
||||||
- light.ceiling_lights
|
- light.ceiling_lights
|
||||||
- light.kitchen_lights
|
- light.kitchen_lights
|
||||||
state_filter:
|
conditions:
|
||||||
- "on"
|
- condition: state
|
||||||
- not_home
|
state:
|
||||||
|
- "on"
|
||||||
|
- home
|
||||||
card:
|
card:
|
||||||
type: entities
|
type: entities
|
||||||
title: Custom Title
|
title: Custom Title
|
||||||
@@ -99,15 +152,101 @@ const CONFIGS = [
|
|||||||
- light.bed_light
|
- light.bed_light
|
||||||
- light.ceiling_lights
|
- light.ceiling_lights
|
||||||
- light.kitchen_lights
|
- light.kitchen_lights
|
||||||
state_filter:
|
conditions:
|
||||||
- "on"
|
- condition: state
|
||||||
- not_home
|
state:
|
||||||
|
- "on"
|
||||||
|
- home
|
||||||
card:
|
card:
|
||||||
type: glance
|
type: glance
|
||||||
show_state: true
|
show_state: true
|
||||||
title: Custom Title
|
title: Custom Title
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading:
|
||||||
|
"Filtered entities by battery attribute (< '30') using state filter",
|
||||||
|
config: `
|
||||||
|
- type: entity-filter
|
||||||
|
entities:
|
||||||
|
- device_tracker.demo_anne_therese
|
||||||
|
- device_tracker.demo_home_boy
|
||||||
|
- device_tracker.demo_paulus
|
||||||
|
state_filter:
|
||||||
|
- operator: <
|
||||||
|
attribute: battery
|
||||||
|
value: "30"
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Unfiltered number entities",
|
||||||
|
config: `
|
||||||
|
- type: entities
|
||||||
|
entities:
|
||||||
|
- input_number.min_battery_level
|
||||||
|
- sensor.battery_1
|
||||||
|
- sensor.battery_3
|
||||||
|
- sensor.battery_2
|
||||||
|
- sensor.battery_4
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Battery lower than 50%",
|
||||||
|
config: `
|
||||||
|
- type: entity-filter
|
||||||
|
entities:
|
||||||
|
- sensor.battery_1
|
||||||
|
- sensor.battery_3
|
||||||
|
- sensor.battery_2
|
||||||
|
- sensor.battery_4
|
||||||
|
conditions:
|
||||||
|
- condition: numeric_state
|
||||||
|
below: 50
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Battery lower than min battery level",
|
||||||
|
config: `
|
||||||
|
- type: entity-filter
|
||||||
|
entities:
|
||||||
|
- sensor.battery_1
|
||||||
|
- sensor.battery_3
|
||||||
|
- sensor.battery_2
|
||||||
|
- sensor.battery_4
|
||||||
|
conditions:
|
||||||
|
- condition: numeric_state
|
||||||
|
below: input_number.min_battery_level
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Battery between min battery level and 70%",
|
||||||
|
config: `
|
||||||
|
- type: entity-filter
|
||||||
|
entities:
|
||||||
|
- sensor.battery_1
|
||||||
|
- sensor.battery_3
|
||||||
|
- sensor.battery_2
|
||||||
|
- sensor.battery_4
|
||||||
|
conditions:
|
||||||
|
- condition: numeric_state
|
||||||
|
above: input_number.min_battery_level
|
||||||
|
below: 70
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Error: Entities must be specified",
|
||||||
|
config: `
|
||||||
|
- type: entity-filter
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Error: Incorrect filter config",
|
||||||
|
config: `
|
||||||
|
- type: entity-filter
|
||||||
|
entities:
|
||||||
|
- sensor.gas_station_lowest_price
|
||||||
|
`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-lovelace-entity-filter-card")
|
@customElement("demo-lovelace-entity-filter-card")
|
||||||
|
@@ -36,6 +36,45 @@ const ENTITIES = [
|
|||||||
friendly_name: "Nest",
|
friendly_name: "Nest",
|
||||||
supported_features: 43,
|
supported_features: 43,
|
||||||
}),
|
}),
|
||||||
|
getEntity("climate", "overkiz_radiator", "heat", {
|
||||||
|
current_temperature: 18,
|
||||||
|
min_temp: 7,
|
||||||
|
max_temp: 35,
|
||||||
|
temperature: 20,
|
||||||
|
hvac_modes: ["heat", "auto", "off"],
|
||||||
|
friendly_name: "Overkiz radiator",
|
||||||
|
supported_features: 17,
|
||||||
|
preset_mode: "comfort",
|
||||||
|
preset_modes: [
|
||||||
|
"none",
|
||||||
|
"frost_protection",
|
||||||
|
"eco",
|
||||||
|
"comfort",
|
||||||
|
"comfort-1",
|
||||||
|
"comfort-2",
|
||||||
|
"auto",
|
||||||
|
"boost",
|
||||||
|
"external",
|
||||||
|
"prog",
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
getEntity("climate", "overkiz_towel_dryer", "heat", {
|
||||||
|
current_temperature: null,
|
||||||
|
min_temp: 7,
|
||||||
|
max_temp: 35,
|
||||||
|
hvac_modes: ["heat", "off"],
|
||||||
|
friendly_name: "Overkiz towel dryer",
|
||||||
|
supported_features: 16,
|
||||||
|
preset_mode: "eco",
|
||||||
|
preset_modes: [
|
||||||
|
"none",
|
||||||
|
"frost_protection",
|
||||||
|
"eco",
|
||||||
|
"comfort",
|
||||||
|
"comfort-1",
|
||||||
|
"comfort-2",
|
||||||
|
],
|
||||||
|
}),
|
||||||
getEntity("climate", "sensibo", "fan_only", {
|
getEntity("climate", "sensibo", "fan_only", {
|
||||||
current_temperature: null,
|
current_temperature: null,
|
||||||
temperature: null,
|
temperature: null,
|
||||||
@@ -46,7 +85,9 @@ const ENTITIES = [
|
|||||||
friendly_name: "Sensibo purifier",
|
friendly_name: "Sensibo purifier",
|
||||||
fan_modes: ["low", "high"],
|
fan_modes: ["low", "high"],
|
||||||
fan_mode: "low",
|
fan_mode: "low",
|
||||||
supported_features: 9,
|
swing_modes: ["on", "off", "both", "vertical", "horizontal"],
|
||||||
|
swing_mode: "vertical",
|
||||||
|
supported_features: 41,
|
||||||
}),
|
}),
|
||||||
getEntity("climate", "unavailable", "unavailable", {
|
getEntity("climate", "unavailable", "unavailable", {
|
||||||
supported_features: 43,
|
supported_features: 43,
|
||||||
@@ -59,8 +100,6 @@ const CONFIGS = [
|
|||||||
config: `
|
config: `
|
||||||
- type: thermostat
|
- type: thermostat
|
||||||
entity: climate.ecobee
|
entity: climate.ecobee
|
||||||
- type: thermostat
|
|
||||||
entity: climate.nest
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -70,6 +109,66 @@ const CONFIGS = [
|
|||||||
entity: climate.nest
|
entity: climate.nest
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Feature example",
|
||||||
|
config: `
|
||||||
|
- type: thermostat
|
||||||
|
entity: climate.overkiz_radiator
|
||||||
|
features:
|
||||||
|
- type: climate-hvac-modes
|
||||||
|
hvac_modes:
|
||||||
|
- heat
|
||||||
|
- 'off'
|
||||||
|
- auto
|
||||||
|
- type: climate-preset-modes
|
||||||
|
style: icons
|
||||||
|
preset_modes:
|
||||||
|
- none
|
||||||
|
- frost_protection
|
||||||
|
- eco
|
||||||
|
- comfort
|
||||||
|
- comfort-1
|
||||||
|
- comfort-2
|
||||||
|
- auto
|
||||||
|
- boost
|
||||||
|
- external
|
||||||
|
- prog
|
||||||
|
- type: climate-preset-modes
|
||||||
|
style: dropdown
|
||||||
|
preset_modes:
|
||||||
|
- none
|
||||||
|
- frost_protection
|
||||||
|
- eco
|
||||||
|
- comfort
|
||||||
|
- comfort-1
|
||||||
|
- comfort-2
|
||||||
|
- auto
|
||||||
|
- boost
|
||||||
|
- external
|
||||||
|
- prog
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Preset only example",
|
||||||
|
config: `
|
||||||
|
- type: thermostat
|
||||||
|
entity: climate.overkiz_towel_dryer
|
||||||
|
features:
|
||||||
|
- type: climate-hvac-modes
|
||||||
|
hvac_modes:
|
||||||
|
- heat
|
||||||
|
- 'off'
|
||||||
|
- type: climate-preset-modes
|
||||||
|
style: icons
|
||||||
|
preset_modes:
|
||||||
|
- none
|
||||||
|
- frost_protection
|
||||||
|
- eco
|
||||||
|
- comfort
|
||||||
|
- comfort-1
|
||||||
|
- comfort-2
|
||||||
|
`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
heading: "Fan only example",
|
heading: "Fan only example",
|
||||||
config: `
|
config: `
|
||||||
@@ -85,6 +184,14 @@ const CONFIGS = [
|
|||||||
fan_modes:
|
fan_modes:
|
||||||
- low
|
- low
|
||||||
- high
|
- high
|
||||||
|
- type: climate-swing-modes
|
||||||
|
style: icons
|
||||||
|
swing_modes:
|
||||||
|
- 'on'
|
||||||
|
- 'off'
|
||||||
|
- 'both'
|
||||||
|
- 'vertical'
|
||||||
|
- 'horizontal'
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -2,6 +2,7 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
|||||||
import { customElement, query } from "lit/decorators";
|
import { customElement, query } from "lit/decorators";
|
||||||
import { CoverEntityFeature } from "../../../../src/data/cover";
|
import { CoverEntityFeature } from "../../../../src/data/cover";
|
||||||
import { LightColorMode } from "../../../../src/data/light";
|
import { LightColorMode } from "../../../../src/data/light";
|
||||||
|
import { LockEntityFeature } from "../../../../src/data/lock";
|
||||||
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
|
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
|
||||||
import { getEntity } from "../../../../src/fake_data/entity";
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
@@ -20,6 +21,11 @@ const ENTITIES = [
|
|||||||
getEntity("light", "unavailable", "unavailable", {
|
getEntity("light", "unavailable", "unavailable", {
|
||||||
friendly_name: "Unavailable entity",
|
friendly_name: "Unavailable entity",
|
||||||
}),
|
}),
|
||||||
|
getEntity("lock", "front_door", "locked", {
|
||||||
|
friendly_name: "Front Door Lock",
|
||||||
|
device_class: "lock",
|
||||||
|
supported_features: LockEntityFeature.OPEN,
|
||||||
|
}),
|
||||||
getEntity("climate", "thermostat", "heat", {
|
getEntity("climate", "thermostat", "heat", {
|
||||||
current_temperature: 73,
|
current_temperature: 73,
|
||||||
min_temp: 45,
|
min_temp: 45,
|
||||||
@@ -138,6 +144,24 @@ const CONFIGS = [
|
|||||||
- type: "color-temp"
|
- type: "color-temp"
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Lock commands feature",
|
||||||
|
config: `
|
||||||
|
- type: tile
|
||||||
|
entity: lock.front_door
|
||||||
|
features:
|
||||||
|
- type: "lock-commands"
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Lock open door feature",
|
||||||
|
config: `
|
||||||
|
- type: tile
|
||||||
|
entity: lock.front_door
|
||||||
|
features:
|
||||||
|
- type: "lock-open-door"
|
||||||
|
`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
heading: "Vacuum commands feature",
|
heading: "Vacuum commands feature",
|
||||||
config: `
|
config: `
|
||||||
|
@@ -368,6 +368,7 @@ export class DemoEntityState extends LitElement {
|
|||||||
hass.localize,
|
hass.localize,
|
||||||
entry.stateObj,
|
entry.stateObj,
|
||||||
hass.locale,
|
hass.locale,
|
||||||
|
[], // numericDeviceClasses
|
||||||
hass.config,
|
hass.config,
|
||||||
hass.entities
|
hass.entities
|
||||||
)}`,
|
)}`,
|
||||||
@@ -406,6 +407,7 @@ export class DemoEntityState extends LitElement {
|
|||||||
entity_id: "select.speed",
|
entity_id: "select.speed",
|
||||||
translation_key: "speed",
|
translation_key: "speed",
|
||||||
platform: "demo",
|
platform: "demo",
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@@ -31,10 +31,13 @@ const createConfigEntry = (
|
|||||||
supports_options: false,
|
supports_options: false,
|
||||||
supports_remove_device: false,
|
supports_remove_device: false,
|
||||||
supports_unload: true,
|
supports_unload: true,
|
||||||
|
supports_reconfigure: true,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
pref_disable_new_entities: false,
|
pref_disable_new_entities: false,
|
||||||
pref_disable_polling: false,
|
pref_disable_polling: false,
|
||||||
reason: null,
|
reason: null,
|
||||||
|
error_reason_translation_key: null,
|
||||||
|
error_reason_translation_placeholders: null,
|
||||||
...override,
|
...override,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -198,6 +201,8 @@ const createEntityRegistryEntries = (
|
|||||||
has_entity_name: false,
|
has_entity_name: false,
|
||||||
unique_id: "updater",
|
unique_id: "updater",
|
||||||
options: null,
|
options: null,
|
||||||
|
labels: [],
|
||||||
|
categories: {},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -221,6 +226,7 @@ const createDeviceRegistryEntries = (
|
|||||||
name_by_user: null,
|
name_by_user: null,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
configuration_url: null,
|
configuration_url: null,
|
||||||
|
labels: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -11,7 +11,7 @@ import "../../components/demo-more-infos";
|
|||||||
import { ClimateEntityFeature } from "../../../../src/data/climate";
|
import { ClimateEntityFeature } from "../../../../src/data/climate";
|
||||||
|
|
||||||
const ENTITIES = [
|
const ENTITIES = [
|
||||||
getEntity("climate", "thermostat", "heat", {
|
getEntity("climate", "radiator", "heat", {
|
||||||
friendly_name: "Basic heater",
|
friendly_name: "Basic heater",
|
||||||
hvac_modes: ["heat", "off"],
|
hvac_modes: ["heat", "off"],
|
||||||
hvac_mode: "heat",
|
hvac_mode: "heat",
|
||||||
@@ -80,6 +80,24 @@ const ENTITIES = [
|
|||||||
max_humidity: 100,
|
max_humidity: 100,
|
||||||
humidity: 50,
|
humidity: 50,
|
||||||
}),
|
}),
|
||||||
|
getEntity("climate", "towel_dryer", "heat", {
|
||||||
|
friendly_name: "Preset only heater",
|
||||||
|
hvac_modes: ["heat", "off"],
|
||||||
|
hvac_mode: "heat",
|
||||||
|
preset_modes: [
|
||||||
|
"none",
|
||||||
|
"frost_protection",
|
||||||
|
"eco",
|
||||||
|
"comfort",
|
||||||
|
"comfort-1",
|
||||||
|
"comfort-2",
|
||||||
|
],
|
||||||
|
preset_mode: "eco",
|
||||||
|
current_temperature: null,
|
||||||
|
min_temp: 7,
|
||||||
|
max_temp: 35,
|
||||||
|
supported_features: ClimateEntityFeature.PRESET_MODE,
|
||||||
|
}),
|
||||||
getEntity("climate", "unavailable", "unavailable", {
|
getEntity("climate", "unavailable", "unavailable", {
|
||||||
friendly_name: "Unavailable heater",
|
friendly_name: "Unavailable heater",
|
||||||
hvac_modes: ["heat", "off"],
|
hvac_modes: ["heat", "off"],
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
import { globIterate } from "glob";
|
import { globIterate } from "glob";
|
||||||
|
import { availableParallelism } from "node:os";
|
||||||
|
|
||||||
|
process.env.UV_THREADPOOL_SIZE = availableParallelism();
|
||||||
|
|
||||||
const gulpImports = [];
|
const gulpImports = [];
|
||||||
|
|
||||||
|
@@ -1,19 +1,19 @@
|
|||||||
import { mdiStorePlus, mdiUpdate } from "@mdi/js";
|
import { mdiRefresh, mdiStorePlus } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { atLeastVersion } from "../../../src/common/config/version";
|
import { atLeastVersion } from "../../../src/common/config/version";
|
||||||
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
import "../../../src/components/ha-fab";
|
import "../../../src/components/ha-fab";
|
||||||
|
import { reloadHassioAddons } from "../../../src/data/hassio/addon";
|
||||||
|
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||||
|
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||||
|
import "../../../src/layouts/hass-subpage";
|
||||||
import "../../../src/layouts/hass-tabs-subpage";
|
import "../../../src/layouts/hass-tabs-subpage";
|
||||||
import { haStyle } from "../../../src/resources/styles";
|
import { haStyle } from "../../../src/resources/styles";
|
||||||
import { HomeAssistant, Route } from "../../../src/types";
|
import { HomeAssistant, Route } from "../../../src/types";
|
||||||
import { supervisorTabs } from "../hassio-tabs";
|
import { supervisorTabs } from "../hassio-tabs";
|
||||||
import "./hassio-addons";
|
import "./hassio-addons";
|
||||||
import "../../../src/layouts/hass-subpage";
|
|
||||||
import { reloadHassioAddons } from "../../../src/data/hassio/addon";
|
|
||||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
|
||||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
|
||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
|
||||||
|
|
||||||
@customElement("hassio-dashboard")
|
@customElement("hassio-dashboard")
|
||||||
class HassioDashboard extends LitElement {
|
class HassioDashboard extends LitElement {
|
||||||
@@ -43,7 +43,7 @@ class HassioDashboard extends LitElement {
|
|||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
slot="toolbar-icon"
|
slot="toolbar-icon"
|
||||||
@click=${this._handleCheckUpdates}
|
@click=${this._handleCheckUpdates}
|
||||||
.path=${mdiUpdate}
|
.path=${mdiRefresh}
|
||||||
.label=${this.supervisor.localize("store.check_updates")}
|
.label=${this.supervisor.localize("store.check_updates")}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<hassio-addons
|
<hassio-addons
|
||||||
|
167
package.json
167
package.json
@@ -25,24 +25,24 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.23.9",
|
"@babel/runtime": "7.24.7",
|
||||||
"@braintree/sanitize-url": "7.0.0",
|
"@braintree/sanitize-url": "7.0.3",
|
||||||
"@codemirror/autocomplete": "6.12.0",
|
"@codemirror/autocomplete": "6.16.3",
|
||||||
"@codemirror/commands": "6.3.3",
|
"@codemirror/commands": "6.6.0",
|
||||||
"@codemirror/language": "6.10.1",
|
"@codemirror/language": "6.10.2",
|
||||||
"@codemirror/legacy-modes": "6.3.3",
|
"@codemirror/legacy-modes": "6.4.0",
|
||||||
"@codemirror/search": "6.5.6",
|
"@codemirror/search": "6.5.6",
|
||||||
"@codemirror/state": "6.4.1",
|
"@codemirror/state": "6.4.1",
|
||||||
"@codemirror/view": "6.24.1",
|
"@codemirror/view": "6.28.2",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.12.2",
|
"@formatjs/intl-datetimeformat": "6.12.5",
|
||||||
"@formatjs/intl-displaynames": "6.6.6",
|
"@formatjs/intl-displaynames": "6.6.8",
|
||||||
"@formatjs/intl-getcanonicallocales": "2.3.0",
|
"@formatjs/intl-getcanonicallocales": "2.3.0",
|
||||||
"@formatjs/intl-listformat": "7.5.5",
|
"@formatjs/intl-listformat": "7.5.7",
|
||||||
"@formatjs/intl-locale": "3.4.5",
|
"@formatjs/intl-locale": "4.0.0",
|
||||||
"@formatjs/intl-numberformat": "8.10.0",
|
"@formatjs/intl-numberformat": "8.10.3",
|
||||||
"@formatjs/intl-pluralrules": "5.2.12",
|
"@formatjs/intl-pluralrules": "5.2.14",
|
||||||
"@formatjs/intl-relativetimeformat": "11.2.12",
|
"@formatjs/intl-relativetimeformat": "11.2.14",
|
||||||
"@fullcalendar/core": "6.1.11",
|
"@fullcalendar/core": "6.1.11",
|
||||||
"@fullcalendar/daygrid": "6.1.11",
|
"@fullcalendar/daygrid": "6.1.11",
|
||||||
"@fullcalendar/interaction": "6.1.11",
|
"@fullcalendar/interaction": "6.1.11",
|
||||||
@@ -53,8 +53,8 @@
|
|||||||
"@lit-labs/context": "0.4.1",
|
"@lit-labs/context": "0.4.1",
|
||||||
"@lit-labs/motion": "1.0.7",
|
"@lit-labs/motion": "1.0.7",
|
||||||
"@lit-labs/observers": "2.0.2",
|
"@lit-labs/observers": "2.0.2",
|
||||||
"@lit-labs/virtualizer": "2.0.12",
|
"@lit-labs/virtualizer": "2.0.13",
|
||||||
"@lrnwebcomponents/simple-tooltip": "patch:@lrnwebcomponents/simple-tooltip@npm%3A8.0.0#~/.yarn/patches/@lrnwebcomponents-simple-tooltip-npm-8.0.0-77591f2e0c.patch",
|
"@lrnwebcomponents/simple-tooltip": "8.0.2",
|
||||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/mwc-base": "0.27.0",
|
"@material/mwc-base": "0.27.0",
|
||||||
@@ -70,8 +70,8 @@
|
|||||||
"@material/mwc-list": "0.27.0",
|
"@material/mwc-list": "0.27.0",
|
||||||
"@material/mwc-menu": "0.27.0",
|
"@material/mwc-menu": "0.27.0",
|
||||||
"@material/mwc-radio": "0.27.0",
|
"@material/mwc-radio": "0.27.0",
|
||||||
"@material/mwc-ripple": "0.27.0",
|
|
||||||
"@material/mwc-select": "0.27.0",
|
"@material/mwc-select": "0.27.0",
|
||||||
|
"@material/mwc-snackbar": "0.27.0",
|
||||||
"@material/mwc-switch": "0.27.0",
|
"@material/mwc-switch": "0.27.0",
|
||||||
"@material/mwc-tab": "0.27.0",
|
"@material/mwc-tab": "0.27.0",
|
||||||
"@material/mwc-tab-bar": "0.27.0",
|
"@material/mwc-tab-bar": "0.27.0",
|
||||||
@@ -80,17 +80,16 @@
|
|||||||
"@material/mwc-top-app-bar": "0.27.0",
|
"@material/mwc-top-app-bar": "0.27.0",
|
||||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/web": "=1.3.0",
|
"@material/web": "1.5.0",
|
||||||
"@mdi/js": "7.4.47",
|
"@mdi/js": "7.4.47",
|
||||||
"@mdi/svg": "7.4.47",
|
"@mdi/svg": "7.4.47",
|
||||||
"@polymer/paper-item": "3.0.1",
|
"@polymer/paper-item": "3.0.1",
|
||||||
"@polymer/paper-listbox": "3.0.1",
|
"@polymer/paper-listbox": "3.0.1",
|
||||||
"@polymer/paper-tabs": "3.1.0",
|
"@polymer/paper-tabs": "3.1.0",
|
||||||
"@polymer/paper-toast": "3.0.1",
|
|
||||||
"@polymer/polymer": "3.5.1",
|
"@polymer/polymer": "3.5.1",
|
||||||
"@thomasloven/round-slider": "0.6.0",
|
"@thomasloven/round-slider": "0.6.0",
|
||||||
"@vaadin/combo-box": "24.3.6",
|
"@vaadin/combo-box": "24.4.0",
|
||||||
"@vaadin/vaadin-themable-mixin": "24.3.6",
|
"@vaadin/vaadin-themable-mixin": "24.4.0",
|
||||||
"@vibrant/color": "3.2.1-alpha.1",
|
"@vibrant/color": "3.2.1-alpha.1",
|
||||||
"@vibrant/core": "3.2.1-alpha.1",
|
"@vibrant/core": "3.2.1-alpha.1",
|
||||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||||
@@ -98,28 +97,28 @@
|
|||||||
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
||||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||||
"app-datepicker": "5.1.1",
|
"app-datepicker": "5.1.1",
|
||||||
"chart.js": "4.4.1",
|
"chart.js": "4.4.3",
|
||||||
"color-name": "2.0.0",
|
"color-name": "2.0.0",
|
||||||
"comlink": "4.4.1",
|
"comlink": "4.4.1",
|
||||||
"core-js": "3.36.0",
|
"core-js": "3.37.1",
|
||||||
"cropperjs": "1.6.1",
|
"cropperjs": "1.6.2",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "3.6.0",
|
||||||
"date-fns-tz": "2.0.0",
|
"date-fns-tz": "3.1.3",
|
||||||
"deep-clone-simple": "1.1.1",
|
"deep-clone-simple": "1.1.1",
|
||||||
"deep-freeze": "0.0.1",
|
"deep-freeze": "0.0.1",
|
||||||
"element-internals-polyfill": "1.3.10",
|
"element-internals-polyfill": "1.3.11",
|
||||||
"fuse.js": "7.0.0",
|
"fuse.js": "7.0.0",
|
||||||
"google-timezones-json": "1.2.0",
|
"google-timezones-json": "1.2.0",
|
||||||
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
|
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
|
||||||
"home-assistant-js-websocket": "9.1.0",
|
"home-assistant-js-websocket": "9.4.0",
|
||||||
"idb-keyval": "6.2.1",
|
"idb-keyval": "6.2.1",
|
||||||
"intl-messageformat": "10.5.11",
|
"intl-messageformat": "10.5.14",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"leaflet": "1.9.4",
|
"leaflet": "1.9.4",
|
||||||
"leaflet-draw": "1.0.4",
|
"leaflet-draw": "1.0.4",
|
||||||
"lit": "2.8.0",
|
"lit": "2.8.0",
|
||||||
"luxon": "3.4.4",
|
"luxon": "3.4.4",
|
||||||
"marked": "12.0.0",
|
"marked": "12.0.2",
|
||||||
"memoize-one": "6.0.0",
|
"memoize-one": "6.0.0",
|
||||||
"node-vibrant": "3.2.1-alpha.1",
|
"node-vibrant": "3.2.1-alpha.1",
|
||||||
"proxy-polyfill": "0.3.2",
|
"proxy-polyfill": "0.3.2",
|
||||||
@@ -130,125 +129,122 @@
|
|||||||
"rrule": "2.8.1",
|
"rrule": "2.8.1",
|
||||||
"sortablejs": "1.15.2",
|
"sortablejs": "1.15.2",
|
||||||
"stacktrace-js": "2.0.2",
|
"stacktrace-js": "2.0.2",
|
||||||
"superstruct": "1.0.3",
|
"superstruct": "1.0.4",
|
||||||
"tinykeys": "2.1.0",
|
"tinykeys": "2.1.0",
|
||||||
"tsparticles-engine": "2.12.0",
|
"tsparticles-engine": "2.12.0",
|
||||||
"tsparticles-preset-links": "2.12.0",
|
"tsparticles-preset-links": "2.12.0",
|
||||||
"ua-parser-js": "1.0.37",
|
"ua-parser-js": "1.0.38",
|
||||||
"unfetch": "5.0.0",
|
"unfetch": "5.0.0",
|
||||||
"vis-data": "7.1.9",
|
"vis-data": "7.1.9",
|
||||||
"vis-network": "9.1.9",
|
"vis-network": "9.1.9",
|
||||||
"vue": "2.7.16",
|
"vue": "2.7.16",
|
||||||
"vue2-daterange-picker": "0.6.8",
|
"vue2-daterange-picker": "0.6.8",
|
||||||
"weekstart": "2.0.0",
|
"weekstart": "2.0.0",
|
||||||
"workbox-cacheable-response": "7.0.0",
|
"workbox-cacheable-response": "7.1.0",
|
||||||
"workbox-core": "7.0.0",
|
"workbox-core": "7.1.0",
|
||||||
"workbox-expiration": "7.0.0",
|
"workbox-expiration": "7.1.0",
|
||||||
"workbox-precaching": "7.0.0",
|
"workbox-precaching": "7.1.0",
|
||||||
"workbox-routing": "7.0.0",
|
"workbox-routing": "7.1.0",
|
||||||
"workbox-strategies": "7.0.0",
|
"workbox-strategies": "7.1.0",
|
||||||
"xss": "1.0.14"
|
"xss": "1.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.23.9",
|
"@babel/core": "7.24.7",
|
||||||
"@babel/helper-define-polyfill-provider": "0.5.0",
|
"@babel/helper-define-polyfill-provider": "0.6.2",
|
||||||
"@babel/plugin-proposal-decorators": "7.23.9",
|
"@babel/plugin-proposal-decorators": "7.24.7",
|
||||||
"@babel/plugin-transform-runtime": "7.23.9",
|
"@babel/plugin-transform-runtime": "7.24.7",
|
||||||
"@babel/preset-env": "7.23.9",
|
"@babel/preset-env": "7.24.7",
|
||||||
"@babel/preset-typescript": "7.23.3",
|
"@babel/preset-typescript": "7.24.7",
|
||||||
"@bundle-stats/plugin-webpack-filter": "4.10.1",
|
"@bundle-stats/plugin-webpack-filter": "4.13.2",
|
||||||
"@koa/cors": "5.0.0",
|
"@koa/cors": "5.0.0",
|
||||||
"@lokalise/node-api": "12.1.0",
|
"@lokalise/node-api": "12.5.0",
|
||||||
"@octokit/auth-oauth-device": "6.0.1",
|
"@octokit/auth-oauth-device": "7.1.1",
|
||||||
"@octokit/plugin-retry": "6.0.1",
|
"@octokit/plugin-retry": "7.1.1",
|
||||||
"@octokit/rest": "20.0.2",
|
"@octokit/rest": "21.0.0",
|
||||||
"@open-wc/dev-server-hmr": "0.1.4",
|
"@open-wc/dev-server-hmr": "0.1.4",
|
||||||
"@rollup/plugin-babel": "6.0.4",
|
"@rollup/plugin-babel": "6.0.4",
|
||||||
"@rollup/plugin-commonjs": "25.0.7",
|
"@rollup/plugin-commonjs": "26.0.1",
|
||||||
"@rollup/plugin-json": "6.1.0",
|
"@rollup/plugin-json": "6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "15.2.3",
|
"@rollup/plugin-node-resolve": "15.2.3",
|
||||||
"@rollup/plugin-replace": "5.0.5",
|
"@rollup/plugin-replace": "5.0.7",
|
||||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||||
"@types/chromecast-caf-receiver": "6.0.13",
|
"@types/chromecast-caf-receiver": "6.0.15",
|
||||||
"@types/chromecast-caf-sender": "1.0.8",
|
"@types/chromecast-caf-sender": "1.0.10",
|
||||||
"@types/color-name": "1.1.3",
|
"@types/color-name": "1.1.4",
|
||||||
"@types/glob": "8.1.0",
|
"@types/glob": "8.1.0",
|
||||||
"@types/html-minifier-terser": "7.0.2",
|
"@types/html-minifier-terser": "7.0.2",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/leaflet": "1.9.8",
|
"@types/leaflet": "1.9.12",
|
||||||
"@types/leaflet-draw": "1.0.11",
|
"@types/leaflet-draw": "1.0.11",
|
||||||
|
"@types/lodash.merge": "4.6.9",
|
||||||
"@types/luxon": "3.4.2",
|
"@types/luxon": "3.4.2",
|
||||||
"@types/mocha": "10.0.6",
|
"@types/mocha": "10.0.6",
|
||||||
"@types/qrcode": "1.5.5",
|
"@types/qrcode": "1.5.5",
|
||||||
"@types/serve-handler": "6.1.4",
|
"@types/serve-handler": "6.1.4",
|
||||||
"@types/sortablejs": "1.15.8",
|
"@types/sortablejs": "1.15.8",
|
||||||
"@types/tar": "6.1.11",
|
"@types/tar": "6.1.13",
|
||||||
"@types/ua-parser-js": "0.7.39",
|
"@types/ua-parser-js": "0.7.39",
|
||||||
"@types/webspeechapi": "0.0.29",
|
"@types/webspeechapi": "0.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "7.0.2",
|
"@typescript-eslint/eslint-plugin": "7.13.1",
|
||||||
"@typescript-eslint/parser": "7.0.2",
|
"@typescript-eslint/parser": "7.13.1",
|
||||||
"@web/dev-server": "0.1.38",
|
"@web/dev-server": "0.1.38",
|
||||||
"@web/dev-server-rollup": "0.4.1",
|
"@web/dev-server-rollup": "0.4.1",
|
||||||
"babel-loader": "9.1.3",
|
"babel-loader": "9.1.3",
|
||||||
"babel-plugin-template-html-minifier": "4.1.0",
|
"babel-plugin-template-html-minifier": "4.1.0",
|
||||||
"chai": "5.1.0",
|
"chai": "5.1.1",
|
||||||
"del": "7.1.0",
|
"del": "7.1.0",
|
||||||
"eslint": "8.57.0",
|
"eslint": "8.57.0",
|
||||||
"eslint-config-airbnb-base": "15.0.0",
|
"eslint-config-airbnb-base": "15.0.0",
|
||||||
"eslint-config-airbnb-typescript": "17.1.0",
|
"eslint-config-airbnb-typescript": "18.0.0",
|
||||||
"eslint-config-prettier": "9.1.0",
|
"eslint-config-prettier": "9.1.0",
|
||||||
"eslint-import-resolver-webpack": "0.13.8",
|
"eslint-import-resolver-webpack": "0.13.8",
|
||||||
"eslint-plugin-disable": "2.0.3",
|
|
||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"eslint-plugin-lit": "1.11.0",
|
"eslint-plugin-lit": "1.14.0",
|
||||||
"eslint-plugin-lit-a11y": "4.1.2",
|
"eslint-plugin-lit-a11y": "4.1.2",
|
||||||
"eslint-plugin-unused-imports": "3.1.0",
|
"eslint-plugin-unused-imports": "4.0.0",
|
||||||
"eslint-plugin-wc": "2.0.4",
|
"eslint-plugin-wc": "2.1.0",
|
||||||
"fancy-log": "2.0.0",
|
"fancy-log": "2.0.0",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"glob": "10.3.10",
|
"glob": "10.4.2",
|
||||||
"gulp": "4.0.2",
|
"gulp": "5.0.0",
|
||||||
"gulp-flatmap": "1.0.2",
|
|
||||||
"gulp-json-transform": "0.5.0",
|
"gulp-json-transform": "0.5.0",
|
||||||
"gulp-merge-json": "2.1.2",
|
|
||||||
"gulp-rename": "2.0.0",
|
"gulp-rename": "2.0.0",
|
||||||
"gulp-zopfli-green": "6.0.1",
|
"gulp-zopfli-green": "6.0.1",
|
||||||
"html-minifier-terser": "7.2.0",
|
"html-minifier-terser": "7.2.0",
|
||||||
"husky": "9.0.11",
|
"husky": "9.0.11",
|
||||||
"instant-mocha": "1.5.2",
|
"instant-mocha": "1.5.2",
|
||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lint-staged": "15.2.2",
|
"lint-staged": "15.2.7",
|
||||||
"lit-analyzer": "2.0.3",
|
"lit-analyzer": "2.0.3",
|
||||||
|
"lodash.merge": "4.6.2",
|
||||||
"lodash.template": "4.5.0",
|
"lodash.template": "4.5.0",
|
||||||
"magic-string": "0.30.7",
|
"magic-string": "0.30.10",
|
||||||
"map-stream": "0.0.7",
|
"map-stream": "0.0.7",
|
||||||
"mocha": "10.3.0",
|
"mocha": "10.4.0",
|
||||||
"object-hash": "3.0.0",
|
"object-hash": "3.0.0",
|
||||||
"open": "10.0.3",
|
"open": "10.1.0",
|
||||||
"pinst": "3.0.0",
|
"pinst": "3.0.0",
|
||||||
"prettier": "3.2.5",
|
"prettier": "3.3.2",
|
||||||
"rollup": "2.79.1",
|
"rollup": "2.79.1",
|
||||||
"rollup-plugin-string": "3.0.0",
|
"rollup-plugin-string": "3.0.0",
|
||||||
"rollup-plugin-terser": "7.0.2",
|
"rollup-plugin-terser": "7.0.2",
|
||||||
"rollup-plugin-visualizer": "5.12.0",
|
"rollup-plugin-visualizer": "5.12.0",
|
||||||
"serve-handler": "6.1.5",
|
"serve-handler": "6.1.5",
|
||||||
"sinon": "17.0.1",
|
"sinon": "18.0.0",
|
||||||
"source-map-url": "0.4.1",
|
"source-map-url": "0.4.1",
|
||||||
"systemjs": "6.14.3",
|
"systemjs": "6.15.1",
|
||||||
"tar": "6.2.0",
|
"tar": "7.4.0",
|
||||||
"terser-webpack-plugin": "5.3.10",
|
"terser-webpack-plugin": "5.3.10",
|
||||||
"transform-async-modules-webpack-plugin": "1.0.2",
|
"transform-async-modules-webpack-plugin": "1.1.1",
|
||||||
"ts-lit-plugin": "2.0.2",
|
"ts-lit-plugin": "2.0.2",
|
||||||
"typescript": "5.3.3",
|
"typescript": "5.4.5",
|
||||||
"vinyl-buffer": "1.0.1",
|
"webpack": "5.92.1",
|
||||||
"vinyl-source-stream": "2.0.0",
|
|
||||||
"webpack": "5.90.3",
|
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "5.0.2",
|
"webpack-dev-server": "5.0.4",
|
||||||
"webpack-manifest-plugin": "5.0.0",
|
"webpack-manifest-plugin": "5.0.0",
|
||||||
"webpack-stats-plugin": "1.1.3",
|
"webpack-stats-plugin": "1.1.3",
|
||||||
"webpackbar": "6.0.1",
|
"webpackbar": "6.0.1",
|
||||||
"workbox-build": "7.0.0"
|
"workbox-build": "7.1.1"
|
||||||
},
|
},
|
||||||
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
@@ -257,8 +253,9 @@
|
|||||||
"lit": "2.8.0",
|
"lit": "2.8.0",
|
||||||
"clean-css": "5.3.3",
|
"clean-css": "5.3.3",
|
||||||
"@lit/reactive-element": "1.6.3",
|
"@lit/reactive-element": "1.6.3",
|
||||||
|
"@fullcalendar/daygrid": "6.1.11",
|
||||||
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
|
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
|
||||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.1.0"
|
"packageManager": "yarn@4.3.1"
|
||||||
}
|
}
|
||||||
|
1
public/static/images/appstore.svg
Normal file
1
public/static/images/appstore.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.1 KiB |
BIN
public/static/images/logo_apple_home.png
Normal file
BIN
public/static/images/logo_apple_home.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
BIN
public/static/images/logo_google_home.png
Normal file
BIN
public/static/images/logo_google_home.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
1
public/static/images/playstore.svg
Normal file
1
public/static/images/playstore.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 6.2 KiB |
1
public/static/images/qr-appstore.svg
Normal file
1
public/static/images/qr-appstore.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 52 KiB |
1
public/static/images/qr-playstore.svg
Normal file
1
public/static/images/qr-playstore.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 69 KiB |
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20240307.0"
|
version = "20240610.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
{name = "The Home Assistant Authors", email = "hello@home-assistant.io"}
|
{name = "The Home Assistant Authors", email = "hello@home-assistant.io"}
|
||||||
]
|
]
|
||||||
requires-python = ">=3.10.0"
|
requires-python = ">=3.11.0"
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
"Homepage" = "https://github.com/home-assistant/frontend"
|
"Homepage" = "https://github.com/home-assistant/frontend"
|
||||||
|
@@ -40,6 +40,11 @@
|
|||||||
"matchPackageNames": ["tsparticles-engine"],
|
"matchPackageNames": ["tsparticles-engine"],
|
||||||
"matchPackagePrefixes": ["tsparticles-preset-"]
|
"matchPackagePrefixes": ["tsparticles-preset-"]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Group date-fns with dependent timezone package",
|
||||||
|
"groupName": "date-fns",
|
||||||
|
"matchPackageNames": ["date-fns", "date-fns-tz"]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Group and temporarily disable WDS packages",
|
"description": "Group and temporarily disable WDS packages",
|
||||||
"groupName": "Web Dev Server",
|
"groupName": "Web Dev Server",
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
import { theme2hex } from "./convert-color";
|
||||||
|
|
||||||
export const COLORS = [
|
export const COLORS = [
|
||||||
"#44739e",
|
"#44739e",
|
||||||
"#984ea3",
|
"#984ea3",
|
||||||
@@ -65,10 +67,10 @@ export function getColorByIndex(index: number) {
|
|||||||
export function getGraphColorByIndex(
|
export function getGraphColorByIndex(
|
||||||
index: number,
|
index: number,
|
||||||
style: CSSStyleDeclaration
|
style: CSSStyleDeclaration
|
||||||
) {
|
): string {
|
||||||
// The CSS vars for the colors use range 1..n, so we need to adjust the index from the internal 0..n color index range.
|
// The CSS vars for the colors use range 1..n, so we need to adjust the index from the internal 0..n color index range.
|
||||||
return (
|
const themeColor =
|
||||||
style.getPropertyValue(`--graph-color-${index + 1}`) ||
|
style.getPropertyValue(`--graph-color-${index + 1}`) ||
|
||||||
getColorByIndex(index)
|
getColorByIndex(index);
|
||||||
);
|
return theme2hex(themeColor);
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import colors from "color-name";
|
||||||
import { expandHex } from "./hex";
|
import { expandHex } from "./hex";
|
||||||
|
|
||||||
const rgb_hex = (component: number): string => {
|
const rgb_hex = (component: number): string => {
|
||||||
@@ -126,3 +127,18 @@ export const rgb2hs = (rgb: [number, number, number]): [number, number] =>
|
|||||||
|
|
||||||
export const hs2rgb = (hs: [number, number]): [number, number, number] =>
|
export const hs2rgb = (hs: [number, number]): [number, number, number] =>
|
||||||
hsv2rgb([hs[0], hs[1], 255]);
|
hsv2rgb([hs[0], hs[1], 255]);
|
||||||
|
|
||||||
|
export function theme2hex(themeColor: string): string {
|
||||||
|
if (themeColor.startsWith("#")) {
|
||||||
|
return themeColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rgbFromColorName = colors[themeColor];
|
||||||
|
if (!rgbFromColorName) {
|
||||||
|
// We have a named color, and there's nothing in the table,
|
||||||
|
// so nothing further we can do with it.
|
||||||
|
// Compare/border/background color will all be the same.
|
||||||
|
return themeColor;
|
||||||
|
}
|
||||||
|
return rgb2hex(rgbFromColorName);
|
||||||
|
}
|
||||||
|
@@ -31,6 +31,7 @@ import {
|
|||||||
mdiFormatListBulleted,
|
mdiFormatListBulleted,
|
||||||
mdiFormatListCheckbox,
|
mdiFormatListCheckbox,
|
||||||
mdiFormTextbox,
|
mdiFormTextbox,
|
||||||
|
mdiForumOutline,
|
||||||
mdiGauge,
|
mdiGauge,
|
||||||
mdiGoogleAssistant,
|
mdiGoogleAssistant,
|
||||||
mdiGoogleCirclesCommunities,
|
mdiGoogleCirclesCommunities,
|
||||||
@@ -98,7 +99,7 @@ export const FIXED_DOMAIN_ICONS = {
|
|||||||
calendar: mdiCalendar,
|
calendar: mdiCalendar,
|
||||||
climate: mdiThermostat,
|
climate: mdiThermostat,
|
||||||
configurator: mdiCog,
|
configurator: mdiCog,
|
||||||
conversation: mdiMicrophoneMessage,
|
conversation: mdiForumOutline,
|
||||||
counter: mdiCounter,
|
counter: mdiCounter,
|
||||||
date: mdiCalendar,
|
date: mdiCalendar,
|
||||||
datetime: mdiCalendarClock,
|
datetime: mdiCalendarClock,
|
||||||
@@ -231,9 +232,12 @@ export const SENSOR_ENTITIES = [
|
|||||||
"calendar",
|
"calendar",
|
||||||
"camera",
|
"camera",
|
||||||
"device_tracker",
|
"device_tracker",
|
||||||
|
"image",
|
||||||
"weather",
|
"weather",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const ASSIST_ENTITIES = ["conversation", "stt", "tts"];
|
||||||
|
|
||||||
/** Domains that render an input element instead of a text value when displayed in a row.
|
/** Domains that render an input element instead of a text value when displayed in a row.
|
||||||
* Those rows should then not show a cursor pointer when hovered (which would normally
|
* Those rows should then not show a cursor pointer when hovered (which would normally
|
||||||
* be the default) unless the element itself enforces it (e.g. a button). Also those elements
|
* be the default) unless the element itself enforces it (e.g. a button). Also those elements
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { utcToZonedTime, zonedTimeToUtc } from "date-fns-tz";
|
import { toZonedTime, fromZonedTime } from "date-fns-tz";
|
||||||
import { HassConfig } from "home-assistant-js-websocket";
|
import { HassConfig } from "home-assistant-js-websocket";
|
||||||
import { FrontendLocaleData, TimeZone } from "../../data/translation";
|
import { FrontendLocaleData, TimeZone } from "../../data/translation";
|
||||||
|
|
||||||
@@ -8,10 +8,10 @@ const calcZonedDate = (
|
|||||||
fn: (date: Date, options?: any) => Date | number | boolean,
|
fn: (date: Date, options?: any) => Date | number | boolean,
|
||||||
options?
|
options?
|
||||||
) => {
|
) => {
|
||||||
const inputZoned = utcToZonedTime(date, tz);
|
const inputZoned = toZonedTime(date, tz);
|
||||||
const fnZoned = fn(inputZoned, options);
|
const fnZoned = fn(inputZoned, options);
|
||||||
if (fnZoned instanceof Date) {
|
if (fnZoned instanceof Date) {
|
||||||
return zonedTimeToUtc(fnZoned, tz) as Date;
|
return fromZonedTime(fnZoned, tz) as Date;
|
||||||
}
|
}
|
||||||
return fnZoned;
|
return fnZoned;
|
||||||
};
|
};
|
||||||
@@ -37,3 +37,20 @@ export const calcDateProperty = (
|
|||||||
locale.time_zone === TimeZone.server
|
locale.time_zone === TimeZone.server
|
||||||
? (calcZonedDate(date, config.time_zone, fn, options) as number | boolean)
|
? (calcZonedDate(date, config.time_zone, fn, options) as number | boolean)
|
||||||
: fn(date, options);
|
: fn(date, options);
|
||||||
|
|
||||||
|
export const calcDateDifferenceProperty = (
|
||||||
|
endDate: Date,
|
||||||
|
startDate: Date,
|
||||||
|
fn: (date: Date, options?: any) => boolean | number,
|
||||||
|
locale: FrontendLocaleData,
|
||||||
|
config: HassConfig
|
||||||
|
) =>
|
||||||
|
calcDateProperty(
|
||||||
|
endDate,
|
||||||
|
fn,
|
||||||
|
locale,
|
||||||
|
config,
|
||||||
|
locale.time_zone === TimeZone.server
|
||||||
|
? toZonedTime(startDate, config.time_zone)
|
||||||
|
: startDate
|
||||||
|
);
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
import { getWeekStartByLocale } from "weekstart";
|
import { getWeekStartByLocale } from "weekstart";
|
||||||
import { FrontendLocaleData, FirstWeekday } from "../../data/translation";
|
import { FrontendLocaleData, FirstWeekday } from "../../data/translation";
|
||||||
|
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
|
|
||||||
export const weekdays = [
|
export const weekdays = [
|
||||||
"sunday",
|
"sunday",
|
||||||
"monday",
|
"monday",
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { HassConfig } from "home-assistant-js-websocket";
|
import { HassConfig } from "home-assistant-js-websocket";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { DateFormat, FrontendLocaleData } from "../../data/translation";
|
import { DateFormat, FrontendLocaleData } from "../../data/translation";
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
import { resolveTimeZone } from "./resolve-time-zone";
|
import { resolveTimeZone } from "./resolve-time-zone";
|
||||||
|
|
||||||
// Tuesday, August 10
|
// Tuesday, August 10
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { HassConfig } from "home-assistant-js-websocket";
|
import { HassConfig } from "home-assistant-js-websocket";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
import { formatDateNumeric } from "./format_date";
|
import { formatDateNumeric } from "./format_date";
|
||||||
import { formatTime } from "./format_time";
|
import { formatTime } from "./format_time";
|
||||||
import { resolveTimeZone } from "./resolve-time-zone";
|
import { resolveTimeZone } from "./resolve-time-zone";
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { HaDurationData } from "../../components/ha-duration-input";
|
import { HaDurationData } from "../../components/ha-duration-input";
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
|
|
||||||
const leftPad = (num: number) => (num < 10 ? `0${num}` : num);
|
const leftPad = (num: number) => (num < 10 ? `0${num}` : num);
|
||||||
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { HassConfig } from "home-assistant-js-websocket";
|
import { HassConfig } from "home-assistant-js-websocket";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
import { resolveTimeZone } from "./resolve-time-zone";
|
import { resolveTimeZone } from "./resolve-time-zone";
|
||||||
import { useAmPm } from "./use_am_pm";
|
import { useAmPm } from "./use_am_pm";
|
||||||
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
|
|
||||||
export const localizeWeekdays = memoizeOne(
|
export const localizeWeekdays = memoizeOne(
|
||||||
(language: string, short: boolean): string[] => {
|
(language: string, short: boolean): string[] => {
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
import { selectUnit } from "../util/select-unit";
|
import { selectUnit } from "../util/select-unit";
|
||||||
|
|
||||||
const formatRelTimeMem = memoizeOne(
|
const formatRelTimeMem = memoizeOne(
|
||||||
|
@@ -108,6 +108,8 @@ export const storage =
|
|||||||
subscribe?: boolean;
|
subscribe?: boolean;
|
||||||
state?: boolean;
|
state?: boolean;
|
||||||
stateOptions?: InternalPropertyDeclaration;
|
stateOptions?: InternalPropertyDeclaration;
|
||||||
|
serializer?: (value: any) => any;
|
||||||
|
deserializer?: (value: any) => any;
|
||||||
}): any =>
|
}): any =>
|
||||||
(clsElement: ClassElement) => {
|
(clsElement: ClassElement) => {
|
||||||
const storageName = options.storage || "localStorage";
|
const storageName = options.storage || "localStorage";
|
||||||
@@ -141,7 +143,9 @@ export const storage =
|
|||||||
|
|
||||||
const getValue = (): any =>
|
const getValue = (): any =>
|
||||||
storageInstance.hasKey(storageKey!)
|
storageInstance.hasKey(storageKey!)
|
||||||
? storageInstance.getValue(storageKey!)
|
? options.deserializer
|
||||||
|
? options.deserializer(storageInstance.getValue(storageKey!))
|
||||||
|
: storageInstance.getValue(storageKey!)
|
||||||
: initVal;
|
: initVal;
|
||||||
|
|
||||||
const setValue = (el: ReactiveElement, value: any) => {
|
const setValue = (el: ReactiveElement, value: any) => {
|
||||||
@@ -149,7 +153,10 @@ export const storage =
|
|||||||
if (options.state) {
|
if (options.state) {
|
||||||
oldValue = getValue();
|
oldValue = getValue();
|
||||||
}
|
}
|
||||||
storageInstance.setValue(storageKey!, value);
|
storageInstance.setValue(
|
||||||
|
storageKey!,
|
||||||
|
options.serializer ? options.serializer(value) : value
|
||||||
|
);
|
||||||
if (options.state) {
|
if (options.state) {
|
||||||
el.requestUpdate(clsElement.key, oldValue);
|
el.requestUpdate(clsElement.key, oldValue);
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
export type MediaQueriesListener = () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach a media query. Listener is called right away and when it matches.
|
* Attach a media query. Listener is called right away and when it matches.
|
||||||
* @param mediaQuery media query to match.
|
* @param mediaQuery media query to match.
|
||||||
@@ -7,7 +9,7 @@
|
|||||||
export const listenMediaQuery = (
|
export const listenMediaQuery = (
|
||||||
mediaQuery: string,
|
mediaQuery: string,
|
||||||
matchesChanged: (matches: boolean) => void
|
matchesChanged: (matches: boolean) => void
|
||||||
) => {
|
): MediaQueriesListener => {
|
||||||
const mql = matchMedia(mediaQuery);
|
const mql = matchMedia(mediaQuery);
|
||||||
const listener = (e) => matchesChanged(e.matches);
|
const listener = (e) => matchesChanged(e.matches);
|
||||||
mql.addListener(listener);
|
mql.addListener(listener);
|
||||||
|
1
src/common/dom/prevent_default.ts
Normal file
1
src/common/dom/prevent_default.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const preventDefault = (ev) => ev.preventDefault();
|
@@ -19,28 +19,11 @@ import { blankBeforeUnit } from "../translations/blank_before_unit";
|
|||||||
import { LocalizeFunc } from "../translations/localize";
|
import { LocalizeFunc } from "../translations/localize";
|
||||||
import { computeDomain } from "./compute_domain";
|
import { computeDomain } from "./compute_domain";
|
||||||
|
|
||||||
export const computeStateDisplaySingleEntity = (
|
|
||||||
localize: LocalizeFunc,
|
|
||||||
stateObj: HassEntity,
|
|
||||||
locale: FrontendLocaleData,
|
|
||||||
config: HassConfig,
|
|
||||||
entity: EntityRegistryDisplayEntry | undefined,
|
|
||||||
state?: string
|
|
||||||
): string =>
|
|
||||||
computeStateDisplayFromEntityAttributes(
|
|
||||||
localize,
|
|
||||||
locale,
|
|
||||||
config,
|
|
||||||
entity,
|
|
||||||
stateObj.entity_id,
|
|
||||||
stateObj.attributes,
|
|
||||||
state !== undefined ? state : stateObj.state
|
|
||||||
);
|
|
||||||
|
|
||||||
export const computeStateDisplay = (
|
export const computeStateDisplay = (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
stateObj: HassEntity,
|
stateObj: HassEntity,
|
||||||
locale: FrontendLocaleData,
|
locale: FrontendLocaleData,
|
||||||
|
sensorNumericDeviceClasses: string[],
|
||||||
config: HassConfig,
|
config: HassConfig,
|
||||||
entities: HomeAssistant["entities"],
|
entities: HomeAssistant["entities"],
|
||||||
state?: string
|
state?: string
|
||||||
@@ -52,6 +35,7 @@ export const computeStateDisplay = (
|
|||||||
return computeStateDisplayFromEntityAttributes(
|
return computeStateDisplayFromEntityAttributes(
|
||||||
localize,
|
localize,
|
||||||
locale,
|
locale,
|
||||||
|
sensorNumericDeviceClasses,
|
||||||
config,
|
config,
|
||||||
entity,
|
entity,
|
||||||
stateObj.entity_id,
|
stateObj.entity_id,
|
||||||
@@ -63,6 +47,7 @@ export const computeStateDisplay = (
|
|||||||
export const computeStateDisplayFromEntityAttributes = (
|
export const computeStateDisplayFromEntityAttributes = (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
locale: FrontendLocaleData,
|
locale: FrontendLocaleData,
|
||||||
|
sensorNumericDeviceClasses: string[],
|
||||||
config: HassConfig,
|
config: HassConfig,
|
||||||
entity: EntityRegistryDisplayEntry | undefined,
|
entity: EntityRegistryDisplayEntry | undefined,
|
||||||
entityId: string,
|
entityId: string,
|
||||||
@@ -73,8 +58,15 @@ export const computeStateDisplayFromEntityAttributes = (
|
|||||||
return localize(`state.default.${state}`);
|
return localize(`state.default.${state}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const domain = computeDomain(entityId);
|
||||||
|
|
||||||
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
|
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
|
||||||
if (isNumericFromAttributes(attributes)) {
|
if (
|
||||||
|
isNumericFromAttributes(
|
||||||
|
attributes,
|
||||||
|
domain === "sensor" ? sensorNumericDeviceClasses : []
|
||||||
|
)
|
||||||
|
) {
|
||||||
// state is duration
|
// state is duration
|
||||||
if (
|
if (
|
||||||
attributes.device_class === "duration" &&
|
attributes.device_class === "duration" &&
|
||||||
@@ -120,8 +112,6 @@ export const computeStateDisplayFromEntityAttributes = (
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const domain = computeDomain(entityId);
|
|
||||||
|
|
||||||
if (domain === "datetime") {
|
if (domain === "datetime") {
|
||||||
const time = new Date(state);
|
const time = new Date(state);
|
||||||
return formatDateTime(time, locale, config);
|
return formatDateTime(time, locale, config);
|
||||||
@@ -187,11 +177,14 @@ export const computeStateDisplayFromEntityAttributes = (
|
|||||||
if (
|
if (
|
||||||
[
|
[
|
||||||
"button",
|
"button",
|
||||||
|
"conversation",
|
||||||
"event",
|
"event",
|
||||||
"image",
|
"image",
|
||||||
"input_button",
|
"input_button",
|
||||||
|
"notify",
|
||||||
"scene",
|
"scene",
|
||||||
"stt",
|
"stt",
|
||||||
|
"tag",
|
||||||
"tts",
|
"tts",
|
||||||
"wake_word",
|
"wake_word",
|
||||||
].includes(domain) ||
|
].includes(domain) ||
|
||||||
|
@@ -28,7 +28,15 @@ export const FIXED_DOMAIN_STATES = {
|
|||||||
input_button: [],
|
input_button: [],
|
||||||
lawn_mower: ["error", "paused", "mowing", "docked"],
|
lawn_mower: ["error", "paused", "mowing", "docked"],
|
||||||
light: ["on", "off"],
|
light: ["on", "off"],
|
||||||
lock: ["jammed", "locked", "locking", "unlocked", "unlocking"],
|
lock: [
|
||||||
|
"jammed",
|
||||||
|
"locked",
|
||||||
|
"locking",
|
||||||
|
"unlocked",
|
||||||
|
"unlocking",
|
||||||
|
"opening",
|
||||||
|
"open",
|
||||||
|
],
|
||||||
media_player: [
|
media_player: [
|
||||||
"off",
|
"off",
|
||||||
"on",
|
"on",
|
||||||
|
@@ -12,11 +12,10 @@ export const formatLanguageCode = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatLanguageCodeMem = memoizeOne((locale: FrontendLocaleData) =>
|
const formatLanguageCodeMem = memoizeOne(
|
||||||
Intl && "DisplayNames" in Intl
|
(locale: FrontendLocaleData) =>
|
||||||
? new Intl.DisplayNames(locale.language, {
|
new Intl.DisplayNames(locale.language, {
|
||||||
type: "language",
|
type: "language",
|
||||||
fallback: "code",
|
fallback: "code",
|
||||||
})
|
})
|
||||||
: undefined
|
|
||||||
);
|
);
|
||||||
|
@@ -11,6 +11,7 @@ declare global {
|
|||||||
|
|
||||||
export interface NavigateOptions {
|
export interface NavigateOptions {
|
||||||
replace?: boolean;
|
replace?: boolean;
|
||||||
|
data?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const navigate = (path: string, options?: NavigateOptions) => {
|
export const navigate = (path: string, options?: NavigateOptions) => {
|
||||||
@@ -24,7 +25,7 @@ export const navigate = (path: string, options?: NavigateOptions) => {
|
|||||||
if (__DEMO__) {
|
if (__DEMO__) {
|
||||||
if (replace) {
|
if (replace) {
|
||||||
mainWindow.history.replaceState(
|
mainWindow.history.replaceState(
|
||||||
mainWindow.history.state?.root ? { root: true } : null,
|
mainWindow.history.state?.root ? { root: true } : options?.data ?? null,
|
||||||
"",
|
"",
|
||||||
`${mainWindow.location.pathname}#${path}`
|
`${mainWindow.location.pathname}#${path}`
|
||||||
);
|
);
|
||||||
@@ -33,12 +34,12 @@ export const navigate = (path: string, options?: NavigateOptions) => {
|
|||||||
}
|
}
|
||||||
} else if (replace) {
|
} else if (replace) {
|
||||||
mainWindow.history.replaceState(
|
mainWindow.history.replaceState(
|
||||||
mainWindow.history.state?.root ? { root: true } : null,
|
mainWindow.history.state?.root ? { root: true } : options?.data ?? null,
|
||||||
"",
|
"",
|
||||||
path
|
path
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
mainWindow.history.pushState(null, "", path);
|
mainWindow.history.pushState(options?.data ?? null, "", path);
|
||||||
}
|
}
|
||||||
fireEvent(mainWindow, "location-changed", {
|
fireEvent(mainWindow, "location-changed", {
|
||||||
replace,
|
replace,
|
||||||
|
@@ -14,8 +14,12 @@ export const isNumericState = (stateObj: HassEntity): boolean =>
|
|||||||
isNumericFromAttributes(stateObj.attributes);
|
isNumericFromAttributes(stateObj.attributes);
|
||||||
|
|
||||||
export const isNumericFromAttributes = (
|
export const isNumericFromAttributes = (
|
||||||
attributes: HassEntityAttributeBase
|
attributes: HassEntityAttributeBase,
|
||||||
): boolean => !!attributes.unit_of_measurement || !!attributes.state_class;
|
numericDeviceClasses?: string[]
|
||||||
|
): boolean =>
|
||||||
|
!!attributes.unit_of_measurement ||
|
||||||
|
!!attributes.state_class ||
|
||||||
|
(numericDeviceClasses || []).includes(attributes.device_class || "");
|
||||||
|
|
||||||
export const numberFormatToLocale = (
|
export const numberFormatToLocale = (
|
||||||
localeOptions: FrontendLocaleData
|
localeOptions: FrontendLocaleData
|
||||||
@@ -59,30 +63,18 @@ export const formatNumber = (
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
localeOptions?.number_format !== NumberFormat.none &&
|
localeOptions?.number_format !== NumberFormat.none &&
|
||||||
!Number.isNaN(Number(num)) &&
|
!Number.isNaN(Number(num))
|
||||||
Intl
|
|
||||||
) {
|
) {
|
||||||
try {
|
return new Intl.NumberFormat(
|
||||||
return new Intl.NumberFormat(
|
locale,
|
||||||
locale,
|
getDefaultFormatOptions(num, options)
|
||||||
getDefaultFormatOptions(num, options)
|
).format(Number(num));
|
||||||
).format(Number(num));
|
|
||||||
} catch (err: any) {
|
|
||||||
// Don't fail when using "TEST" language
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error(err);
|
|
||||||
return new Intl.NumberFormat(
|
|
||||||
undefined,
|
|
||||||
getDefaultFormatOptions(num, options)
|
|
||||||
).format(Number(num));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!Number.isNaN(Number(num)) &&
|
!Number.isNaN(Number(num)) &&
|
||||||
num !== "" &&
|
num !== "" &&
|
||||||
localeOptions?.number_format === NumberFormat.none &&
|
localeOptions?.number_format === NumberFormat.none
|
||||||
Intl
|
|
||||||
) {
|
) {
|
||||||
// If NumberFormat is none, use en-US format without grouping.
|
// If NumberFormat is none, use en-US format without grouping.
|
||||||
return new Intl.NumberFormat(
|
return new Intl.NumberFormat(
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import "../../resources/intl-polyfill";
|
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
|
|
||||||
export const formatListWithAnds = (
|
export const formatListWithAnds = (
|
||||||
|
@@ -21,7 +21,8 @@ export const computeFormatFunctions = async (
|
|||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
locale: FrontendLocaleData,
|
locale: FrontendLocaleData,
|
||||||
config: HassConfig,
|
config: HassConfig,
|
||||||
entities: HomeAssistant["entities"]
|
entities: HomeAssistant["entities"],
|
||||||
|
sensorNumericDeviceClasses: string[]
|
||||||
): Promise<{
|
): Promise<{
|
||||||
formatEntityState: FormatEntityStateFunc;
|
formatEntityState: FormatEntityStateFunc;
|
||||||
formatEntityAttributeValue: FormatEntityAttributeValueFunc;
|
formatEntityAttributeValue: FormatEntityAttributeValueFunc;
|
||||||
@@ -35,7 +36,15 @@ export const computeFormatFunctions = async (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
formatEntityState: (stateObj, state) =>
|
formatEntityState: (stateObj, state) =>
|
||||||
computeStateDisplay(localize, stateObj, locale, config, entities, state),
|
computeStateDisplay(
|
||||||
|
localize,
|
||||||
|
stateObj,
|
||||||
|
locale,
|
||||||
|
sensorNumericDeviceClasses,
|
||||||
|
config,
|
||||||
|
entities,
|
||||||
|
state
|
||||||
|
),
|
||||||
formatEntityAttributeValue: (stateObj, attribute, value) =>
|
formatEntityAttributeValue: (stateObj, attribute, value) =>
|
||||||
computeAttributeValueDisplay(
|
computeAttributeValueDisplay(
|
||||||
localize,
|
localize,
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
import IntlMessageFormat from "intl-messageformat";
|
import type { IntlMessageFormat } from "intl-messageformat";
|
||||||
import type { HTMLTemplateResult } from "lit";
|
import type { HTMLTemplateResult } from "lit";
|
||||||
import { polyfillLocaleData } from "../../resources/locale-data-polyfill";
|
import { polyfillLocaleData } from "../../resources/polyfills/locale-data-polyfill";
|
||||||
import { Resources, TranslationDict } from "../../types";
|
import { Resources, TranslationDict } from "../../types";
|
||||||
|
import { fireEvent } from "../dom/fire_event";
|
||||||
|
|
||||||
// Exclude some patterns from key type checking for now
|
// Exclude some patterns from key type checking for now
|
||||||
// These are intended to be removed as errors are fixed
|
// These are intended to be removed as errors are fixed
|
||||||
@@ -81,14 +82,15 @@ export interface FormatsType {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const computeLocalize = async <Keys extends string = LocalizeKeys>(
|
export const computeLocalize = async <Keys extends string = LocalizeKeys>(
|
||||||
cache: any,
|
cache: HTMLElement & {
|
||||||
|
_localizationCache?: Record<string, IntlMessageFormat>;
|
||||||
|
},
|
||||||
language: string,
|
language: string,
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
formats?: FormatsType
|
formats?: FormatsType
|
||||||
): Promise<LocalizeFunc<Keys>> => {
|
): Promise<LocalizeFunc<Keys>> => {
|
||||||
await import("../../resources/intl-polyfill").then(() =>
|
const { IntlMessageFormat } = await import("intl-messageformat");
|
||||||
polyfillLocaleData(language)
|
await polyfillLocaleData(language);
|
||||||
);
|
|
||||||
|
|
||||||
// Every time any of the parameters change, invalidate the strings cache.
|
// Every time any of the parameters change, invalidate the strings cache.
|
||||||
cache._localizationCache = {};
|
cache._localizationCache = {};
|
||||||
@@ -107,7 +109,7 @@ export const computeLocalize = async <Keys extends string = LocalizeKeys>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const messageKey = key + translatedValue;
|
const messageKey = key + translatedValue;
|
||||||
let translatedMessage = cache._localizationCache[messageKey] as
|
let translatedMessage = cache._localizationCache![messageKey] as
|
||||||
| IntlMessageFormat
|
| IntlMessageFormat
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
@@ -121,7 +123,7 @@ export const computeLocalize = async <Keys extends string = LocalizeKeys>(
|
|||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return "Translation error: " + err.message;
|
return "Translation error: " + err.message;
|
||||||
}
|
}
|
||||||
cache._localizationCache[messageKey] = translatedMessage;
|
cache._localizationCache![messageKey] = translatedMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
let argObject = {};
|
let argObject = {};
|
||||||
@@ -137,6 +139,12 @@ export const computeLocalize = async <Keys extends string = LocalizeKeys>(
|
|||||||
try {
|
try {
|
||||||
return translatedMessage.format<string>(argObject) as string;
|
return translatedMessage.format<string>(argObject) as string;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error("Translation error", key, language, err);
|
||||||
|
fireEvent(cache, "write_log", {
|
||||||
|
level: "error",
|
||||||
|
message: `Failed to format translation for key '${key}' in language '${language}'. ${err}`,
|
||||||
|
});
|
||||||
return "Translation " + err;
|
return "Translation " + err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
9
src/common/util/promise-all-settled-results.ts
Normal file
9
src/common/util/promise-all-settled-results.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export const hasRejectedItems = <T = any>(results: PromiseSettledResult<T>[]) =>
|
||||||
|
results.some((result) => result.status === "rejected");
|
||||||
|
|
||||||
|
export const rejectedItems = <T = any>(
|
||||||
|
results: PromiseSettledResult<T>[]
|
||||||
|
): PromiseRejectedResult[] =>
|
||||||
|
results.filter(
|
||||||
|
(result) => result.status === "rejected"
|
||||||
|
) as PromiseRejectedResult[];
|
@@ -1,4 +1,4 @@
|
|||||||
import { differenceInDays, differenceInWeeks, startOfWeek } from "date-fns/esm";
|
import { differenceInDays, differenceInWeeks, startOfWeek } from "date-fns";
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
import { firstWeekdayIndex } from "../datetime/first_weekday";
|
import { firstWeekdayIndex } from "../datetime/first_weekday";
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user