Compare commits
642 Commits
20250811.0
...
row-remove
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a208b83e3a | ||
![]() |
8c4a67315b | ||
![]() |
c18de97b32 | ||
![]() |
23a3ca3ed7 | ||
![]() |
69457b4e85 | ||
![]() |
2e096c23e0 | ||
![]() |
552691e200 | ||
![]() |
91258c86c1 | ||
![]() |
3750a378cd | ||
![]() |
12d3304c72 | ||
![]() |
246100809d | ||
![]() |
6efca93186 | ||
![]() |
6280647b9a | ||
![]() |
2ff52c6c29 | ||
![]() |
d038e11170 | ||
![]() |
8925b39fe5 | ||
![]() |
beeef65506 | ||
![]() |
994c1b5751 | ||
![]() |
6823c647b6 | ||
![]() |
866b478dc0 | ||
![]() |
d746dc5752 | ||
![]() |
5f53e1e71c | ||
![]() |
3da82df093 | ||
![]() |
4cedfffb71 | ||
![]() |
1e1514e7da | ||
![]() |
60e07075bc | ||
![]() |
c998086474 | ||
![]() |
53be0a3fa2 | ||
![]() |
d69c46c80c | ||
![]() |
0c2a7bfed0 | ||
![]() |
afdd232e38 | ||
![]() |
179751a135 | ||
![]() |
52f6024306 | ||
![]() |
7c7a4e61f2 | ||
![]() |
facce7b016 | ||
![]() |
e546cb3374 | ||
![]() |
a0d2e7312b | ||
![]() |
c0a9dadcbe | ||
![]() |
e1edf7fb98 | ||
![]() |
6d5c165bd2 | ||
![]() |
54177a16e9 | ||
![]() |
c814b8e888 | ||
![]() |
33a0b32cc5 | ||
![]() |
7dae13bf57 | ||
![]() |
0a3fe6e0fb | ||
![]() |
e0348e4da7 | ||
![]() |
d53f3ec898 | ||
![]() |
e422547d93 | ||
![]() |
d91a3fbe85 | ||
![]() |
01d7130f22 | ||
![]() |
c57851e4df | ||
![]() |
6f1f13acb0 | ||
![]() |
a8abd00809 | ||
![]() |
e053978dbe | ||
![]() |
6e57f726a3 | ||
![]() |
b7cabadbe1 | ||
![]() |
d920217374 | ||
![]() |
1630263276 | ||
![]() |
5680c742be | ||
![]() |
2aeb9cf0ef | ||
![]() |
c9931b3a3c | ||
![]() |
fbf7ebdfe4 | ||
![]() |
52ccb03de5 | ||
![]() |
900236ac07 | ||
![]() |
28940c930d | ||
![]() |
e278b463fd | ||
![]() |
db2acd4e39 | ||
![]() |
6dcc52cd44 | ||
![]() |
981db50826 | ||
![]() |
09683863a7 | ||
![]() |
8c78f931dc | ||
![]() |
40ce3c1e31 | ||
![]() |
e430a1b1be | ||
![]() |
a2c6116417 | ||
![]() |
3239273f3e | ||
![]() |
e42c5a3254 | ||
![]() |
df7a6297b0 | ||
![]() |
e4ca478d01 | ||
![]() |
7be2c59295 | ||
![]() |
99d9c67492 | ||
![]() |
8f781e53e3 | ||
![]() |
3c92826e71 | ||
![]() |
151a879e0a | ||
![]() |
f3a8529ed7 | ||
![]() |
d2cc7856d1 | ||
![]() |
d5cb815bbd | ||
![]() |
7f88d863e9 | ||
![]() |
88ac56ac0b | ||
![]() |
3d173ad03e | ||
![]() |
3889d71768 | ||
![]() |
8872adf2ed | ||
![]() |
969e655fff | ||
![]() |
cdc913d878 | ||
![]() |
4ac1215def | ||
![]() |
b2376fba56 | ||
![]() |
f14d9198ac | ||
![]() |
f4e583b302 | ||
![]() |
2c602aecee | ||
![]() |
cbf96898fe | ||
![]() |
6760f4a2ae | ||
![]() |
3481f7e8be | ||
![]() |
95a0fe335f | ||
![]() |
1e2d144d26 | ||
![]() |
6aa89cb532 | ||
![]() |
1b0ed7017f | ||
![]() |
1cc7e387da | ||
![]() |
41bf935f6e | ||
![]() |
b08ea36a1e | ||
![]() |
4f52a46725 | ||
![]() |
f8a82563b0 | ||
![]() |
a1672ccdfb | ||
![]() |
bde851e5a4 | ||
![]() |
a6d3041d59 | ||
![]() |
f64edfa305 | ||
![]() |
067b321d84 | ||
![]() |
33efe395c8 | ||
![]() |
db26b1041f | ||
![]() |
6e9b4637bb | ||
![]() |
0e30e5e0f4 | ||
![]() |
283da74e2d | ||
![]() |
034afd1375 | ||
![]() |
912d710ae4 | ||
![]() |
86b99d931a | ||
![]() |
35cfa9aa0d | ||
![]() |
6a23dbf204 | ||
![]() |
cef8fc1d38 | ||
![]() |
7c06e33b50 | ||
![]() |
cb365d4635 | ||
![]() |
525102678b | ||
![]() |
dfc4b0bba2 | ||
![]() |
846692bc8a | ||
![]() |
3b90b5fcb1 | ||
![]() |
cac978344f | ||
![]() |
6a40631e6d | ||
![]() |
48f5b6dfd3 | ||
![]() |
04b01d2cd9 | ||
![]() |
0e8e054db1 | ||
![]() |
477a893193 | ||
![]() |
bd0822f09f | ||
![]() |
07c3ffb55d | ||
![]() |
fbfb4709d2 | ||
![]() |
0a5b31e328 | ||
![]() |
8cf0d8d2c3 | ||
![]() |
61c16ce020 | ||
![]() |
6bede4ddca | ||
![]() |
bd88b91071 | ||
![]() |
29b02a3c99 | ||
![]() |
ac87e2280d | ||
![]() |
98c4e34a23 | ||
![]() |
3d005c8316 | ||
![]() |
af31b5add3 | ||
![]() |
9d02a1d391 | ||
![]() |
98e6f32fe8 | ||
![]() |
2726c6a849 | ||
![]() |
c09ec54c76 | ||
![]() |
9f045538a2 | ||
![]() |
c6c4f91b0e | ||
![]() |
f71d8f4367 | ||
![]() |
68c1a38231 | ||
![]() |
a9796e4216 | ||
![]() |
bf6eefb692 | ||
![]() |
7ec3b08444 | ||
![]() |
f3355671d1 | ||
![]() |
c0e240a3bf | ||
![]() |
00fd4753e4 | ||
![]() |
08ac873e3b | ||
![]() |
d12b8d1b1b | ||
![]() |
977207dde4 | ||
![]() |
87a5f1a315 | ||
![]() |
acab2d5ead | ||
![]() |
046fc00f73 | ||
![]() |
05775c411b | ||
![]() |
d64acca598 | ||
![]() |
59571d03a6 | ||
![]() |
28c515bbac | ||
![]() |
27db5b3b02 | ||
![]() |
1922db0474 | ||
![]() |
c8c74a9744 | ||
![]() |
2c676baa99 | ||
![]() |
3e41474faa | ||
![]() |
5f9c69ac21 | ||
![]() |
8b45ccaaba | ||
![]() |
455925f637 | ||
![]() |
9fba7427f8 | ||
![]() |
21aae02652 | ||
![]() |
24e3fbf622 | ||
![]() |
dcbc8b627f | ||
![]() |
0d8d18617c | ||
![]() |
7eb87c78cc | ||
![]() |
0eaf9ead9e | ||
![]() |
7082646fe5 | ||
![]() |
96d364b3bd | ||
![]() |
e726eb7370 | ||
![]() |
e6f587da78 | ||
![]() |
c595392abe | ||
![]() |
5bcffd0dbe | ||
![]() |
df801833fc | ||
![]() |
5ba5c00c70 | ||
![]() |
dcea227f4a | ||
![]() |
1abedcd5f0 | ||
![]() |
9e29693293 | ||
![]() |
3bfafc794f | ||
![]() |
89c43b2b33 | ||
![]() |
466115d916 | ||
![]() |
a34ca3c085 | ||
![]() |
9a8ca36047 | ||
![]() |
b454e89613 | ||
![]() |
0b76109272 | ||
![]() |
942d264693 | ||
![]() |
b10fdf8438 | ||
![]() |
bee8980192 | ||
![]() |
61487565db | ||
![]() |
cc70eb46c9 | ||
![]() |
dec9d304da | ||
![]() |
7f8e856102 | ||
![]() |
4bd60a1366 | ||
![]() |
e9ca1758a0 | ||
![]() |
dff3b82f0d | ||
![]() |
1b630e7b66 | ||
![]() |
f4238bf291 | ||
![]() |
ef8cb8b393 | ||
![]() |
bed161d485 | ||
![]() |
22e0ef4308 | ||
![]() |
eb355d110d | ||
![]() |
c041c295d5 | ||
![]() |
c582896574 | ||
![]() |
3e6b59fe1e | ||
![]() |
62714b2b68 | ||
![]() |
07fdd5b7af | ||
![]() |
720f435987 | ||
![]() |
52061d6c1a | ||
![]() |
ae35164a57 | ||
![]() |
d1c814bd6b | ||
![]() |
bb50512c89 | ||
![]() |
0fae45edc9 | ||
![]() |
0a8d3cc8fa | ||
![]() |
db09947a67 | ||
![]() |
5eb600726f | ||
![]() |
17a2e6e1f6 | ||
![]() |
53e7959d54 | ||
![]() |
5f140c5fc4 | ||
![]() |
688b8e5229 | ||
![]() |
34b50b45a3 | ||
![]() |
c17c9c6cc9 | ||
![]() |
c9d72b5253 | ||
![]() |
f5dbb28fb2 | ||
![]() |
9acfe5c1cc | ||
![]() |
701cbcfbad | ||
![]() |
38685127d2 | ||
![]() |
4275f6c6b6 | ||
![]() |
bfc186b612 | ||
![]() |
2cbcf1a689 | ||
![]() |
1c1c0d70c5 | ||
![]() |
a66f5fb573 | ||
![]() |
9affeab755 | ||
![]() |
2bfaf77908 | ||
![]() |
bc4caae796 | ||
![]() |
8746acd329 | ||
![]() |
96ecf16da2 | ||
![]() |
1e95a0f3ef | ||
![]() |
a164d793b1 | ||
![]() |
cb4d92ccf4 | ||
![]() |
1dc7256fb5 | ||
![]() |
012e710e45 | ||
![]() |
5abb7d0286 | ||
![]() |
ce74946706 | ||
![]() |
bf351d67e9 | ||
![]() |
b75fa013d2 | ||
![]() |
2601b0d89c | ||
![]() |
ef8410e121 | ||
![]() |
37610703c8 | ||
![]() |
4efd9bba8a | ||
![]() |
e1fe7976d8 | ||
![]() |
53b96107d9 | ||
![]() |
510fc71b40 | ||
![]() |
2a6a3edb77 | ||
![]() |
c7a8796a47 | ||
![]() |
dcbad9e798 | ||
![]() |
26b3212c7e | ||
![]() |
9d40fa5f2b | ||
![]() |
8f2a023775 | ||
![]() |
989b0b34fe | ||
![]() |
f3f4bcfe45 | ||
![]() |
cf94e71215 | ||
![]() |
49896f3fa6 | ||
![]() |
7cfa9de75f | ||
![]() |
fc4b7674b1 | ||
![]() |
04c9f32539 | ||
![]() |
21e3fc9bb9 | ||
![]() |
4b78eb7656 | ||
![]() |
b66dc8894d | ||
![]() |
14a7813ab0 | ||
![]() |
70a2ca281f | ||
![]() |
d982f042fc | ||
![]() |
e60f9e326b | ||
![]() |
e6f91aef8e | ||
![]() |
8f99f86c8b | ||
![]() |
b7eff547c7 | ||
![]() |
ba39d189e7 | ||
![]() |
78867b2cd9 | ||
![]() |
ceb6b64152 | ||
![]() |
d253041376 | ||
![]() |
cb0aa81f89 | ||
![]() |
42061b2f8c | ||
![]() |
69bfb89a65 | ||
![]() |
e0307f9688 | ||
![]() |
1cf353461f | ||
![]() |
1786235c86 | ||
![]() |
645ba3f9c1 | ||
![]() |
b65f6f46e1 | ||
![]() |
84ad521b3d | ||
![]() |
dfb9c662e7 | ||
![]() |
5ac42e17b0 | ||
![]() |
be2f19637e | ||
![]() |
1dff42dc00 | ||
![]() |
0c9b3a0765 | ||
![]() |
5a109c0ba8 | ||
![]() |
f3b214c30a | ||
![]() |
c49d2a0be6 | ||
![]() |
c6c3170c1b | ||
![]() |
0abb958aea | ||
![]() |
9d55843629 | ||
![]() |
b70d309297 | ||
![]() |
5961b71562 | ||
![]() |
6942626f60 | ||
![]() |
069c0acdff | ||
![]() |
1f0d83190d | ||
![]() |
b7a6ee3792 | ||
![]() |
1fb2f0c989 | ||
![]() |
7c6c92c856 | ||
![]() |
b4ad411e6f | ||
![]() |
5d76a92f73 | ||
![]() |
beee09491a | ||
![]() |
ee5aabdddf | ||
![]() |
ec80f6a6f1 | ||
![]() |
9845f0b47c | ||
![]() |
cd294ba619 | ||
![]() |
61e27cb1ea | ||
![]() |
8d6295e8e8 | ||
![]() |
b0e95699f7 | ||
![]() |
c8e1e7b8a8 | ||
![]() |
d2cea159af | ||
![]() |
eb5d1c79c8 | ||
![]() |
65ab6848ab | ||
![]() |
7a1d934e8d | ||
![]() |
cbacde12fa | ||
![]() |
eff352cde1 | ||
![]() |
62a75c188c | ||
![]() |
4ffa6b6186 | ||
![]() |
25173cf605 | ||
![]() |
3277d8e80b | ||
![]() |
55864fdc82 | ||
![]() |
d4d662ba46 | ||
![]() |
3ea5f508bb | ||
![]() |
902a5dd678 | ||
![]() |
4a3ed62583 | ||
![]() |
b4223e9e92 | ||
![]() |
99955d7818 | ||
![]() |
f66768726c | ||
![]() |
0e4be02b2c | ||
![]() |
6daea23b3c | ||
![]() |
e21ddcb1e5 | ||
![]() |
ded85d9f27 | ||
![]() |
eea43494da | ||
![]() |
9cf9ef927d | ||
![]() |
779ec4f583 | ||
![]() |
c541831cd2 | ||
![]() |
fd20c2a554 | ||
![]() |
14fd29808c | ||
![]() |
7132ee157f | ||
![]() |
1596b313d5 | ||
![]() |
4c33618e05 | ||
![]() |
3837b3e630 | ||
![]() |
7c15633f6d | ||
![]() |
f7ec8650eb | ||
![]() |
7674eee0fb | ||
![]() |
f494a6453a | ||
![]() |
37f3682ffa | ||
![]() |
8055286a1f | ||
![]() |
70cd68ded7 | ||
![]() |
cc91a6185e | ||
![]() |
1fd7c84583 | ||
![]() |
0269540ee9 | ||
![]() |
98390b3843 | ||
![]() |
269628929c | ||
![]() |
21fcc84afd | ||
![]() |
b86c1db83d | ||
![]() |
a376670478 | ||
![]() |
72c62079aa | ||
![]() |
9baf875585 | ||
![]() |
175915218f | ||
![]() |
0bdd213761 | ||
![]() |
810b43760e | ||
![]() |
424d71c55a | ||
![]() |
176924241c | ||
![]() |
da08aa7fb0 | ||
![]() |
6047227648 | ||
![]() |
fc71fd6bc3 | ||
![]() |
90a1b135e1 | ||
![]() |
e19413b6ca | ||
![]() |
0dfc10af5f | ||
![]() |
bbbc419bea | ||
![]() |
50ad5e376f | ||
![]() |
a9f2254bbc | ||
![]() |
a8836404d4 | ||
![]() |
25f25243bd | ||
![]() |
cf8d36b1f3 | ||
![]() |
e3a9d754df | ||
![]() |
7b303a699b | ||
![]() |
ee45eb00f7 | ||
![]() |
24a6aa2669 | ||
![]() |
66d011cfb9 | ||
![]() |
35895735cc | ||
![]() |
e71df0b71a | ||
![]() |
2a9846c598 | ||
![]() |
b243d56bee | ||
![]() |
6a372a165e | ||
![]() |
a5dad9bc22 | ||
![]() |
954e0a5f63 | ||
![]() |
4dbd4eebaa | ||
![]() |
09b01df366 | ||
![]() |
a76539c732 | ||
![]() |
c7babe884c | ||
![]() |
ce83feec93 | ||
![]() |
150ee3fb12 | ||
![]() |
8fd3fcd323 | ||
![]() |
6e3b3a53e4 | ||
![]() |
22966485c7 | ||
![]() |
673ca8ba4b | ||
![]() |
c8be25dfc2 | ||
![]() |
edaaa00038 | ||
![]() |
2de605d97a | ||
![]() |
0b11302b1d | ||
![]() |
ddb224e145 | ||
![]() |
317149e51e | ||
![]() |
51840b88b3 | ||
![]() |
319a1ad8c6 | ||
![]() |
d75c84750d | ||
![]() |
492a73e345 | ||
![]() |
64bf101c95 | ||
![]() |
876ced25f1 | ||
![]() |
72e3b72854 | ||
![]() |
6ccd3d3b95 | ||
![]() |
5709cb6aa4 | ||
![]() |
1fe7282b0e | ||
![]() |
6d29063b35 | ||
![]() |
d3e0b94d27 | ||
![]() |
f4f1f98433 | ||
![]() |
69e3d8e13d | ||
![]() |
3ab6e389d3 | ||
![]() |
9cc85bc928 | ||
![]() |
4f4343d6c8 | ||
![]() |
7ab27d620a | ||
![]() |
fa4ee71803 | ||
![]() |
82026250c5 | ||
![]() |
bc7533bb42 | ||
![]() |
7110c0381f | ||
![]() |
87fa05accc | ||
![]() |
56ee3f82fb | ||
![]() |
11872b076b | ||
![]() |
620db4238d | ||
![]() |
ef78bec48d | ||
![]() |
4dcf2287ce | ||
![]() |
a9766ed66b | ||
![]() |
26a83feeb0 | ||
![]() |
8c5dd7cdba | ||
![]() |
0d025a2355 | ||
![]() |
8216778d0c | ||
![]() |
fe16b689a8 | ||
![]() |
2653f6c874 | ||
![]() |
8093f7f4cb | ||
![]() |
b26c914ff9 | ||
![]() |
b2fa97b6dc | ||
![]() |
8a8bba422a | ||
![]() |
76ca66b1b5 | ||
![]() |
280dbfc958 | ||
![]() |
0b10ad3e78 | ||
![]() |
c172f0c486 | ||
![]() |
1244ed73a2 | ||
![]() |
e2aef205cc | ||
![]() |
7434c9345a | ||
![]() |
287ff17107 | ||
![]() |
21309944e5 | ||
![]() |
c0e39ffd67 | ||
![]() |
3da2cb3123 | ||
![]() |
7957bd1f25 | ||
![]() |
04d0aa2f22 | ||
![]() |
4b901101da | ||
![]() |
4960284e2d | ||
![]() |
11d32300e9 | ||
![]() |
f131e93e63 | ||
![]() |
5a540dd889 | ||
![]() |
3a70310f78 | ||
![]() |
feed58c33e | ||
![]() |
e5585e13fe | ||
![]() |
edd49e1511 | ||
![]() |
057fad55e8 | ||
![]() |
d1a8165de2 | ||
![]() |
31c555445d | ||
![]() |
61bb5d33e5 | ||
![]() |
20a3bab5bc | ||
![]() |
e8d916acd7 | ||
![]() |
8b73d664b4 | ||
![]() |
10dcc08068 | ||
![]() |
fc5adc3753 | ||
![]() |
d1db8f456f | ||
![]() |
0dd07a395a | ||
![]() |
589771df5c | ||
![]() |
92812048dc | ||
![]() |
9fc14d6627 | ||
![]() |
3da6b85593 | ||
![]() |
d2a4f481be | ||
![]() |
e37f67c548 | ||
![]() |
e775a6770b | ||
![]() |
4ba5ef6c37 | ||
![]() |
d528ab06d9 | ||
![]() |
03a628cfe2 | ||
![]() |
973851b332 | ||
![]() |
4c5795c276 | ||
![]() |
41d016d96a | ||
![]() |
22e647cad4 | ||
![]() |
b63dd9dbbf | ||
![]() |
3b3b9e269d | ||
![]() |
d2c3b9ee83 | ||
![]() |
5b50a8692b | ||
![]() |
8a8bbee8e0 | ||
![]() |
28e28d1417 | ||
![]() |
ea77a0f3d6 | ||
![]() |
10e09b238a | ||
![]() |
f9cd2b66cb | ||
![]() |
b1c0fba8cf | ||
![]() |
fdae6257b3 | ||
![]() |
6a48aea128 | ||
![]() |
a90b173671 | ||
![]() |
d9971bfaa9 | ||
![]() |
369d56a809 | ||
![]() |
939a3cdf63 | ||
![]() |
208fd0662c | ||
![]() |
f133f246cb | ||
![]() |
b9b8997d68 | ||
![]() |
46c4a19a13 | ||
![]() |
8d63654211 | ||
![]() |
3bf25f125b | ||
![]() |
8c65876413 | ||
![]() |
2ab6d49553 | ||
![]() |
67b0cf0952 | ||
![]() |
5138276f8a | ||
![]() |
30e6777529 | ||
![]() |
1686ab4b9d | ||
![]() |
b7102c0d7d | ||
![]() |
d41d524850 | ||
![]() |
4f05f6305a | ||
![]() |
ba0b1239be | ||
![]() |
708b68f35d | ||
![]() |
3108e98b97 | ||
![]() |
ba7609cc2c | ||
![]() |
506fd7d480 | ||
![]() |
9767ebe1fb | ||
![]() |
539e89e7b5 | ||
![]() |
a7eef81272 | ||
![]() |
7986be103f | ||
![]() |
055e65c45e | ||
![]() |
fe762e9ae4 | ||
![]() |
5267c6fdfc | ||
![]() |
8eff913845 | ||
![]() |
1c845d0052 | ||
![]() |
60a1d25e1e | ||
![]() |
3439d1d663 | ||
![]() |
bf120d9cb2 | ||
![]() |
b5a024c879 | ||
![]() |
602d754e5e | ||
![]() |
b7c4f4029d | ||
![]() |
7fdb5d4862 | ||
![]() |
bc52ab410c | ||
![]() |
3b0220fa92 | ||
![]() |
a60c9f788d | ||
![]() |
d9c297c06a | ||
![]() |
3789bebb2b | ||
![]() |
bbecf5f368 | ||
![]() |
e580b30219 | ||
![]() |
ed8c8ad3e3 | ||
![]() |
4f61d5689b | ||
![]() |
60a18185d7 | ||
![]() |
e0246b8488 | ||
![]() |
1cd0fae84a | ||
![]() |
e8a1ebbff4 | ||
![]() |
c5010b8502 | ||
![]() |
a7db401b62 | ||
![]() |
49c7dad6eb | ||
![]() |
521c3d40b7 | ||
![]() |
709a1d2ef0 | ||
![]() |
3c5d7b97d1 | ||
![]() |
9165c8bc57 | ||
![]() |
0b3e4eab23 | ||
![]() |
39d14c943c | ||
![]() |
09469be93f | ||
![]() |
6e215870ef | ||
![]() |
d5985dcaaf | ||
![]() |
bbd9d8887d | ||
![]() |
9588987e30 | ||
![]() |
52c05a4426 | ||
![]() |
e8224df4e5 | ||
![]() |
83a6df1621 | ||
![]() |
c46ebc8d3e | ||
![]() |
fca530411f | ||
![]() |
c2c64b9923 | ||
![]() |
9968c27a8e | ||
![]() |
96796ac5da | ||
![]() |
37def6d3e4 | ||
![]() |
013d603ba0 | ||
![]() |
b76407d28d | ||
![]() |
4e969ccf09 | ||
![]() |
cdfd6431c3 | ||
![]() |
c363995718 | ||
![]() |
53497aa632 | ||
![]() |
8d89b0e57f | ||
![]() |
92cf8b5579 | ||
![]() |
6068c32176 | ||
![]() |
38893324af | ||
![]() |
a39ab3c174 | ||
![]() |
797d2be5bf | ||
![]() |
99a91e1019 | ||
![]() |
5de8d07ce0 | ||
![]() |
3a31a4a721 | ||
![]() |
05f4419a92 | ||
![]() |
5ea8feb86b | ||
![]() |
8fd70b3ae6 | ||
![]() |
343aa40bc8 | ||
![]() |
6022f9a77e | ||
![]() |
bd9de0680e | ||
![]() |
b8000d5bc1 | ||
![]() |
c6efa1127f | ||
![]() |
688a3d91d3 | ||
![]() |
68151a2a70 | ||
![]() |
c2ca556151 | ||
![]() |
df86b27af4 | ||
![]() |
eba1f401cc | ||
![]() |
19c2f9c9e8 | ||
![]() |
4250447d14 | ||
![]() |
4666197f28 | ||
![]() |
a5ca36c93f | ||
![]() |
a88950e16c |
12
.github/workflows/cast_deployment.yaml
vendored
@@ -21,12 +21,12 @@ 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.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
- name: Deploy to Netlify
|
- name: Deploy to Netlify
|
||||||
id: deploy
|
id: deploy
|
||||||
run: |
|
run: |
|
||||||
npx -y netlify-cli deploy --dir=cast/dist --alias dev
|
npx -y netlify-cli@23.7.3 deploy --dir=cast/dist --alias dev
|
||||||
env:
|
env:
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
|
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
|
||||||
@@ -56,12 +56,12 @@ 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.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
- name: Deploy to Netlify
|
- name: Deploy to Netlify
|
||||||
id: deploy
|
id: deploy
|
||||||
run: |
|
run: |
|
||||||
npx -y netlify-cli deploy --dir=cast/dist --prod
|
npx -y netlify-cli@23.7.3 deploy --dir=cast/dist --prod
|
||||||
env:
|
env:
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
|
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
|
||||||
|
22
.github/workflows/ci.yaml
vendored
@@ -24,9 +24,9 @@ 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.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -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.2.3
|
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
node_modules/.cache/prettier
|
node_modules/.cache/prettier
|
||||||
@@ -58,9 +58,9 @@ 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.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -76,9 +76,9 @@ 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.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -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.6.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: frontend-bundle-stats
|
name: frontend-bundle-stats
|
||||||
path: build/stats/*.json
|
path: build/stats/*.json
|
||||||
@@ -100,9 +100,9 @@ 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.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -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.6.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: supervisor-bundle-stats
|
name: supervisor-bundle-stats
|
||||||
path: build/stats/*.json
|
path: build/stats/*.json
|
||||||
|
8
.github/workflows/codeql-analysis.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
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.
|
||||||
@@ -36,14 +36,14 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v3
|
uses: github/codeql-action/autobuild@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@@ -57,4 +57,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
|
||||||
|
12
.github/workflows/demo_deployment.yaml
vendored
@@ -22,12 +22,12 @@ 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.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
- name: Deploy to Netlify
|
- name: Deploy to Netlify
|
||||||
id: deploy
|
id: deploy
|
||||||
run: |
|
run: |
|
||||||
npx -y netlify-cli deploy --dir=demo/dist --prod
|
npx -y netlify-cli@23.7.3 deploy --dir=demo/dist --prod
|
||||||
env:
|
env:
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }}
|
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }}
|
||||||
@@ -57,12 +57,12 @@ 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.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -78,7 +78,7 @@ jobs:
|
|||||||
- name: Deploy to Netlify
|
- name: Deploy to Netlify
|
||||||
id: deploy
|
id: deploy
|
||||||
run: |
|
run: |
|
||||||
npx -y netlify-cli deploy --dir=demo/dist --prod
|
npx -y netlify-cli@23.7.3 deploy --dir=demo/dist --prod
|
||||||
env:
|
env:
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_SITE_ID }}
|
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_SITE_ID }}
|
||||||
|
6
.github/workflows/design_deployment.yaml
vendored
@@ -16,10 +16,10 @@ 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.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: Deploy to Netlify
|
- name: Deploy to Netlify
|
||||||
id: deploy
|
id: deploy
|
||||||
run: |
|
run: |
|
||||||
npx -y netlify-cli deploy --dir=gallery/dist --prod
|
npx -y netlify-cli@23.7.3 deploy --dir=gallery/dist --prod
|
||||||
env:
|
env:
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }}
|
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }}
|
||||||
|
6
.github/workflows/design_preview.yaml
vendored
@@ -21,10 +21,10 @@ 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.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
- name: Deploy preview to Netlify
|
- name: Deploy preview to Netlify
|
||||||
id: deploy
|
id: deploy
|
||||||
run: |
|
run: |
|
||||||
npx -y netlify-cli deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}" \
|
npx -y netlify-cli@23.7.3 deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}" \
|
||||||
--json > deploy_output.json
|
--json > deploy_output.json
|
||||||
env:
|
env:
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
|
2
.github/workflows/labeler.yaml
vendored
@@ -10,6 +10,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Apply labels
|
- name: Apply labels
|
||||||
uses: actions/labeler@v5.0.0
|
uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
||||||
with:
|
with:
|
||||||
sync-labels: true
|
sync-labels: true
|
||||||
|
2
.github/workflows/lock.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
lock:
|
lock:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v5.0.1
|
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
process-only: "issues, prs"
|
process-only: "issues, prs"
|
||||||
|
10
.github/workflows/nightly.yaml
vendored
@@ -20,15 +20,15 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -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.6.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
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.6.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: translations
|
name: translations
|
||||||
path: translations.tar.gz
|
path: translations.tar.gz
|
||||||
|
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@v3.0.1
|
uses: relative-ci/agent-action@1707825cbfcc7452b2913d273414705415ae64d4 # v3.0.1
|
||||||
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 }}
|
||||||
|
2
.github/workflows/release-drafter.yaml
vendored
@@ -18,6 +18,6 @@ jobs:
|
|||||||
pull-requests: read
|
pull-requests: read
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: release-drafter/release-drafter@v6.1.0
|
- uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
23
.github/workflows/release.yaml
vendored
@@ -23,10 +23,10 @@ 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.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
uses: home-assistant/actions/helpers/verify-version@master
|
uses: home-assistant/actions/helpers/verify-version@master
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
script/release
|
script/release
|
||||||
|
|
||||||
- name: Upload release assets
|
- name: Upload release assets
|
||||||
uses: softprops/action-gh-release@v2.3.2
|
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
dist/*.whl
|
dist/*.whl
|
||||||
@@ -73,8 +73,9 @@ jobs:
|
|||||||
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
|
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
|
||||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||||
|
|
||||||
|
# home-assistant/wheels doesn't support SHA pinning
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: home-assistant/wheels@2025.03.0
|
uses: home-assistant/wheels@2025.09.1
|
||||||
with:
|
with:
|
||||||
abi: cp313
|
abi: cp313
|
||||||
tag: musllinux_1_2
|
tag: musllinux_1_2
|
||||||
@@ -90,9 +91,9 @@ 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.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -107,7 +108,7 @@ jobs:
|
|||||||
- name: Tar folder
|
- name: Tar folder
|
||||||
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
|
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
|
||||||
- name: Upload release asset
|
- name: Upload release asset
|
||||||
uses: softprops/action-gh-release@v2.3.2
|
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
|
||||||
with:
|
with:
|
||||||
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
|
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
|
||||||
@@ -119,9 +120,9 @@ 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.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.4.0
|
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -136,6 +137,6 @@ jobs:
|
|||||||
- name: Tar folder
|
- name: Tar folder
|
||||||
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
|
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
|
||||||
- name: Upload release asset
|
- name: Upload release asset
|
||||||
uses: softprops/action-gh-release@v2.3.2
|
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
|
||||||
with:
|
with:
|
||||||
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz
|
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
4
.github/workflows/restrict-task-creation.yml
vendored
@@ -9,10 +9,10 @@ jobs:
|
|||||||
check-authorization:
|
check-authorization:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# Only run if this is a Task issue type (from the issue form)
|
# Only run if this is a Task issue type (from the issue form)
|
||||||
if: github.event.issue.issue_type == 'Task'
|
if: github.event.issue.type.name == 'Task'
|
||||||
steps:
|
steps:
|
||||||
- name: Check if user is authorized
|
- name: Check if user is authorized
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const issueAuthor = context.payload.issue.user.login;
|
const issueAuthor = context.payload.issue.user.login;
|
||||||
|
2
.github/workflows/stale.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 90 days stale policy
|
- name: 90 days stale policy
|
||||||
uses: actions/stale@v9.1.0
|
uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-stale: 90
|
days-before-stale: 90
|
||||||
|
2
.github/workflows/translations.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- name: Upload Translations
|
- name: Upload Translations
|
||||||
run: |
|
run: |
|
||||||
|
@@ -6,4 +6,4 @@ enableGlobalCache: false
|
|||||||
|
|
||||||
nodeLinker: node-modules
|
nodeLinker: node-modules
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-4.9.2.cjs
|
yarnPath: .yarn/releases/yarn-4.10.3.cjs
|
||||||
|
8
CODEOWNERS
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# People marked here will be automatically requested for a review
|
||||||
|
# when the code that they own is touched.
|
||||||
|
# https://github.com/blog/2392-introducing-code-owners
|
||||||
|
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||||
|
|
||||||
|
# Part of the frontend that mobile developper should review
|
||||||
|
src/external_app/ @bgoncal @TimoPtr
|
||||||
|
test/external_app/ @bgoncal @TimoPtr
|
@@ -183,7 +183,6 @@ module.exports.babelOptions = ({
|
|||||||
include: /\/node_modules\//,
|
include: /\/node_modules\//,
|
||||||
exclude: [
|
exclude: [
|
||||||
"element-internals-polyfill",
|
"element-internals-polyfill",
|
||||||
"@shoelace-style",
|
|
||||||
"@?lit(?:-labs|-element|-html)?",
|
"@?lit(?:-labs|-element|-html)?",
|
||||||
].map((p) => new RegExp(`/node_modules/${p}/`)),
|
].map((p) => new RegExp(`/node_modules/${p}/`)),
|
||||||
},
|
},
|
||||||
|
@@ -14,5 +14,5 @@
|
|||||||
"name": "Home Assistant Cast",
|
"name": "Home Assistant Cast",
|
||||||
"short_name": "HA Cast",
|
"short_name": "HA Cast",
|
||||||
"start_url": "/?homescreen=1",
|
"start_url": "/?homescreen=1",
|
||||||
"theme_color": "#03A9F4"
|
"theme_color": "#009ac7"
|
||||||
}
|
}
|
||||||
|
@@ -75,7 +75,7 @@ export const castDemoEntities: () => Entity[] = () =>
|
|||||||
longitude: 4.8903147,
|
longitude: 4.8903147,
|
||||||
radius: 100,
|
radius: 100,
|
||||||
friendly_name: "Home",
|
friendly_name: "Home",
|
||||||
icon: "hass:home",
|
icon: "mdi:home",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"input_number.harmonyvolume": {
|
"input_number.harmonyvolume": {
|
||||||
@@ -88,7 +88,7 @@ export const castDemoEntities: () => Entity[] = () =>
|
|||||||
step: 1,
|
step: 1,
|
||||||
mode: "slider",
|
mode: "slider",
|
||||||
friendly_name: "Volume",
|
friendly_name: "Volume",
|
||||||
icon: "hass:volume-high",
|
icon: "mdi:volume-high",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"climate.upstairs": {
|
"climate.upstairs": {
|
||||||
|
@@ -56,7 +56,7 @@ export const castDemoLovelace: () => LovelaceConfig = () => {
|
|||||||
type: "weblink",
|
type: "weblink",
|
||||||
url: "/lovelace/climate",
|
url: "/lovelace/climate",
|
||||||
name: "Climate controls",
|
name: "Climate controls",
|
||||||
icon: "hass:arrow-right",
|
icon: "mdi:arrow-right",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -76,7 +76,7 @@ export const castDemoLovelace: () => LovelaceConfig = () => {
|
|||||||
type: "weblink",
|
type: "weblink",
|
||||||
url: "/lovelace/overview",
|
url: "/lovelace/overview",
|
||||||
name: "Back",
|
name: "Back",
|
||||||
icon: "hass:arrow-left",
|
icon: "mdi:arrow-left",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@@ -75,5 +75,5 @@
|
|||||||
"name": "Home Assistant Demo",
|
"name": "Home Assistant Demo",
|
||||||
"short_name": "HA Demo",
|
"short_name": "HA Demo",
|
||||||
"start_url": "/?homescreen=1",
|
"start_url": "/?homescreen=1",
|
||||||
"theme_color": "#03A9F4"
|
"theme_color": "#009ac7"
|
||||||
}
|
}
|
||||||
|
@@ -143,7 +143,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
|
|||||||
state: "on",
|
state: "on",
|
||||||
attributes: {
|
attributes: {
|
||||||
friendly_name: "Home Automation",
|
friendly_name: "Home Automation",
|
||||||
icon: "hass:home-automation",
|
icon: "mdi:home-automation",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"input_boolean.tvtime": {
|
"input_boolean.tvtime": {
|
||||||
|
@@ -4,7 +4,7 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
|
|||||||
title: "Home Assistant",
|
title: "Home Assistant",
|
||||||
views: [
|
views: [
|
||||||
{
|
{
|
||||||
icon: "hass:home-assistant",
|
icon: "mdi:home-assistant",
|
||||||
id: "home",
|
id: "home",
|
||||||
title: "Home",
|
title: "Home",
|
||||||
cards: [
|
cards: [
|
||||||
|
@@ -1236,7 +1236,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
path: "security",
|
path: "security",
|
||||||
icon: "hass:shield-home",
|
icon: "mdi:shield-home",
|
||||||
name: "Security",
|
name: "Security",
|
||||||
background:
|
background:
|
||||||
'center / cover no-repeat url("/assets/jimpower/background-15.jpg") fixed',
|
'center / cover no-repeat url("/assets/jimpower/background-15.jpg") fixed',
|
||||||
|
@@ -68,7 +68,7 @@
|
|||||||
}
|
}
|
||||||
#ha-launch-screen .ha-launch-screen-spacer-top {
|
#ha-launch-screen .ha-launch-screen-spacer-top {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-top: calc( 2 * max(var(--safe-area-inset-bottom), 48px) + 46px );
|
margin-top: calc( 2 * max(var(--safe-area-inset-top, 0px), 48px) + 46px );
|
||||||
padding-top: 48px;
|
padding-top: 48px;
|
||||||
}
|
}
|
||||||
#ha-launch-screen .ha-launch-screen-spacer-bottom {
|
#ha-launch-screen .ha-launch-screen-spacer-bottom {
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
padding-top: 48px;
|
padding-top: 48px;
|
||||||
}
|
}
|
||||||
.ohf-logo {
|
.ohf-logo {
|
||||||
margin: max(var(--safe-area-inset-bottom), 48px) 0;
|
margin: max(var(--safe-area-inset-bottom, 0px), 48px) 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import { LitElement, css, html } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
import "../../../src/dialogs/more-info/more-info-content";
|
import "../../../src/dialogs/more-info/more-info-content";
|
||||||
import "../../../src/state-summary/state-card-content";
|
import "../../../src/state-summary/state-card-content";
|
||||||
import "../ha-demo-options";
|
import "../ha-demo-options";
|
||||||
import type { HomeAssistant } from "../../../src/types";
|
import type { HomeAssistant } from "../../../src/types";
|
||||||
|
import { computeShowNewMoreInfo } from "../../../src/dialogs/more-info/const";
|
||||||
|
|
||||||
@customElement("demo-more-info")
|
@customElement("demo-more-info")
|
||||||
class DemoMoreInfo extends LitElement {
|
class DemoMoreInfo extends LitElement {
|
||||||
@@ -21,11 +22,13 @@ class DemoMoreInfo extends LitElement {
|
|||||||
<div class="root">
|
<div class="root">
|
||||||
<div id="card">
|
<div id="card">
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<state-card-content
|
${!computeShowNewMoreInfo(state)
|
||||||
.stateObj=${state}
|
? html`<state-card-content
|
||||||
.hass=${this.hass}
|
.stateObj=${state}
|
||||||
in-dialog
|
.hass=${this.hass}
|
||||||
></state-card-content>
|
in-dialog
|
||||||
|
></state-card-content>`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
<more-info-content
|
<more-info-content
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
@@ -1106,7 +1106,7 @@ export default {
|
|||||||
friendly_name: "Philips Hue",
|
friendly_name: "Philips Hue",
|
||||||
entity_picture: null,
|
entity_picture: null,
|
||||||
description:
|
description:
|
||||||
"Press the button on the bridge to register Philips Hue with Home Assistant.\n\n",
|
"Press the button on the bridge to register Philips Hue with Home Assistant.",
|
||||||
submit_caption: "I have pressed the button",
|
submit_caption: "I have pressed the button",
|
||||||
},
|
},
|
||||||
last_changed: "2018-07-19T10:44:46.515160+00:00",
|
last_changed: "2018-07-19T10:44:46.515160+00:00",
|
||||||
|
@@ -17,6 +17,10 @@ export const createMediaPlayerEntities = () => [
|
|||||||
new Date().getTime() - 23000
|
new Date().getTime() - 23000
|
||||||
).toISOString(),
|
).toISOString(),
|
||||||
volume_level: 0.5,
|
volume_level: 0.5,
|
||||||
|
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
|
||||||
|
source: "AirPlay",
|
||||||
|
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
|
||||||
|
sound_mode: "Music",
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "music_playing", "playing", {
|
getEntity("media_player", "music_playing", "playing", {
|
||||||
friendly_name: "Playing The Music",
|
friendly_name: "Playing The Music",
|
||||||
@@ -24,8 +28,8 @@ export const createMediaPlayerEntities = () => [
|
|||||||
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
||||||
media_artist: "Technohead",
|
media_artist: "Technohead",
|
||||||
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
|
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
|
||||||
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media
|
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media + Grouping
|
||||||
supported_features: 195135,
|
supported_features: 784959,
|
||||||
entity_picture: "/images/album_cover.jpg",
|
entity_picture: "/images/album_cover.jpg",
|
||||||
media_duration: 300,
|
media_duration: 300,
|
||||||
media_position: 0,
|
media_position: 0,
|
||||||
@@ -34,6 +38,9 @@ export const createMediaPlayerEntities = () => [
|
|||||||
new Date().getTime() - 23000
|
new Date().getTime() - 23000
|
||||||
).toISOString(),
|
).toISOString(),
|
||||||
volume_level: 0.5,
|
volume_level: 0.5,
|
||||||
|
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
|
||||||
|
sound_mode: "Music",
|
||||||
|
group_members: ["media_player.playing", "media_player.stream_playing"],
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "stream_playing", "playing", {
|
getEntity("media_player", "stream_playing", "playing", {
|
||||||
friendly_name: "Playing the Stream",
|
friendly_name: "Playing the Stream",
|
||||||
@@ -149,15 +156,18 @@ export const createMediaPlayerEntities = () => [
|
|||||||
}),
|
}),
|
||||||
getEntity("media_player", "receiver_on", "on", {
|
getEntity("media_player", "receiver_on", "on", {
|
||||||
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
|
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
|
||||||
|
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
|
||||||
volume_level: 0.63,
|
volume_level: 0.63,
|
||||||
is_volume_muted: false,
|
is_volume_muted: false,
|
||||||
source: "TV",
|
source: "TV",
|
||||||
|
sound_mode: "Movie",
|
||||||
friendly_name: "Receiver (selectable sources)",
|
friendly_name: "Receiver (selectable sources)",
|
||||||
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
|
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
|
||||||
supported_features: 84364,
|
supported_features: 84364,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "receiver_off", "off", {
|
getEntity("media_player", "receiver_off", "off", {
|
||||||
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
|
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
|
||||||
|
sound_mode_list: ["Movie", "Music", "Game", "Pure Audio"],
|
||||||
friendly_name: "Receiver (selectable sources)",
|
friendly_name: "Receiver (selectable sources)",
|
||||||
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
|
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
|
||||||
supported_features: 84364,
|
supported_features: 84364,
|
||||||
|
@@ -18,7 +18,6 @@ import { HaDeviceAction } from "../../../../src/panels/config/automation/action/
|
|||||||
import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event";
|
import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event";
|
||||||
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 { 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 { HaPlayMediaAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-play_media";
|
|
||||||
import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
|
import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
|
||||||
import { HaSequenceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-sequence";
|
import { HaSequenceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-sequence";
|
||||||
import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service";
|
import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service";
|
||||||
@@ -32,7 +31,6 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [
|
|||||||
{ name: "Service", actions: [HaServiceAction.defaultConfig] },
|
{ name: "Service", actions: [HaServiceAction.defaultConfig] },
|
||||||
{ name: "Condition", actions: [HaConditionAction.defaultConfig] },
|
{ name: "Condition", actions: [HaConditionAction.defaultConfig] },
|
||||||
{ name: "Delay", actions: [HaDelayAction.defaultConfig] },
|
{ name: "Delay", actions: [HaDelayAction.defaultConfig] },
|
||||||
{ name: "Play media", actions: [HaPlayMediaAction.defaultConfig] },
|
|
||||||
{ name: "Wait", actions: [HaWaitAction.defaultConfig] },
|
{ name: "Wait", actions: [HaWaitAction.defaultConfig] },
|
||||||
{ name: "WaitForTrigger", actions: [HaWaitForTriggerAction.defaultConfig] },
|
{ name: "WaitForTrigger", actions: [HaWaitForTriggerAction.defaultConfig] },
|
||||||
{ name: "Repeat", actions: [HaRepeatAction.defaultConfig] },
|
{ name: "Repeat", actions: [HaRepeatAction.defaultConfig] },
|
||||||
|
@@ -117,7 +117,7 @@ export class DemoHaBadge extends LitElement {
|
|||||||
}
|
}
|
||||||
.card-content {
|
.card-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 24px;
|
gap: var(--ha-space-6);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -155,11 +155,11 @@ export class DemoHaButton extends LitElement {
|
|||||||
.card-content {
|
.card-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 24px;
|
gap: var(--ha-space-6);
|
||||||
}
|
}
|
||||||
.card-content div {
|
.card-content div {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: var(--ha-space-2);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
37
gallery/src/pages/components/ha-marquee-text.markdown
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
title: Marquee Text
|
||||||
|
---
|
||||||
|
|
||||||
|
# Marquee Text `<ha-marquee-text>`
|
||||||
|
|
||||||
|
Marquee text component scrolls text horizontally if it overflows its container. It supports pausing on hover and customizable speed and pause duration.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### Example Usage
|
||||||
|
|
||||||
|
<ha-marquee-text style="width: 200px;">
|
||||||
|
This is a long text that will scroll horizontally if it overflows the container.
|
||||||
|
</ha-marquee-text>
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ha-marquee-text style="width: 200px;">
|
||||||
|
This is a long text that will scroll horizontally if it overflows the
|
||||||
|
container.
|
||||||
|
</ha-marquee-text>
|
||||||
|
```
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
**Slots**
|
||||||
|
|
||||||
|
- default slot: The text content to be displayed and scrolled.
|
||||||
|
- no default
|
||||||
|
|
||||||
|
**Properties/Attributes**
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| -------------- | ------- | ------- | ---------------------------------------------------------------------------- |
|
||||||
|
| speed | number | `15` | The speed of the scrolling animation. Higher values result in faster scroll. |
|
||||||
|
| pause-on-hover | boolean | `true` | Whether to pause the scrolling animation when |
|
||||||
|
| pause-duration | number | `1000` | The delay in milliseconds before the scrolling animation starts/restarts. |
|
25
gallery/src/pages/components/ha-marquee-text.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { css, LitElement } from "lit";
|
||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
import "../../../../src/components/ha-card";
|
||||||
|
import "../../../../src/components/ha-marquee-text";
|
||||||
|
|
||||||
|
@customElement("demo-components-ha-marquee-text")
|
||||||
|
export class DemoHaMarqueeText extends LitElement {
|
||||||
|
static styles = css`
|
||||||
|
ha-card {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 24px auto;
|
||||||
|
}
|
||||||
|
.card-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-components-ha-marquee-text": DemoHaMarqueeText;
|
||||||
|
}
|
||||||
|
}
|
@@ -123,11 +123,11 @@ export class DemoHaProgressButton extends LitElement {
|
|||||||
.card-content {
|
.card-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 24px;
|
gap: var(--ha-space-6);
|
||||||
}
|
}
|
||||||
.card-content div {
|
.card-content div {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: var(--ha-space-2);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
36
gallery/src/pages/components/ha-slider.markdown
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
title: Slider
|
||||||
|
subtitle: A slider component for selecting a value from a range.
|
||||||
|
---
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
# Slider `<ha-slider>`
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### Example Usage
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
<ha-slider size="small" with-markers min="0" max="8" value="4"></ha-slider>
|
||||||
|
<ha-slider size="medium"></ha-slider>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```html
|
||||||
|
<ha-slider size="small" with-markers min="0" max="8" value="4"></ha-slider>
|
||||||
|
<ha-slider size="medium"></ha-slider>
|
||||||
|
```
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
This component is based on the webawesome slider component.
|
||||||
|
Check the [webawesome documentation](https://webawesome.com/docs/components/slider/) for more details.
|
||||||
|
|
||||||
|
**CSS Custom Properties**
|
||||||
|
|
||||||
|
- `--ha-slider-track-size` - Height of the slider track. Defaults to `4px`.
|
100
gallery/src/pages/components/ha-slider.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import type { TemplateResult } from "lit";
|
||||||
|
import { css, html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element";
|
||||||
|
import "../../../../src/components/ha-bar";
|
||||||
|
import "../../../../src/components/ha-card";
|
||||||
|
import "../../../../src/components/ha-spinner";
|
||||||
|
import "../../../../src/components/ha-slider";
|
||||||
|
import type { HomeAssistant } from "../../../../src/types";
|
||||||
|
|
||||||
|
@customElement("demo-components-ha-slider")
|
||||||
|
export class DemoHaSlider extends LitElement {
|
||||||
|
@property({ attribute: false }) hass!: HomeAssistant;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
${["light", "dark"].map(
|
||||||
|
(mode) => html`
|
||||||
|
<div class=${mode}>
|
||||||
|
<ha-card header="ha-slider ${mode} demo">
|
||||||
|
<div class="card-content">
|
||||||
|
<span>Default (disabled)</span>
|
||||||
|
<ha-slider
|
||||||
|
disabled
|
||||||
|
min="0"
|
||||||
|
max="8"
|
||||||
|
value="4"
|
||||||
|
with-markers
|
||||||
|
></ha-slider>
|
||||||
|
<span>Small</span>
|
||||||
|
<ha-slider
|
||||||
|
size="small"
|
||||||
|
min="0"
|
||||||
|
max="8"
|
||||||
|
value="4"
|
||||||
|
with-markers
|
||||||
|
></ha-slider>
|
||||||
|
<span>Medium</span>
|
||||||
|
<ha-slider
|
||||||
|
size="medium"
|
||||||
|
min="0"
|
||||||
|
max="8"
|
||||||
|
value="4"
|
||||||
|
with-markers
|
||||||
|
></ha-slider>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated(changedProps) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
applyThemesOnElement(
|
||||||
|
this.shadowRoot!.querySelector(".dark"),
|
||||||
|
{
|
||||||
|
default_theme: "default",
|
||||||
|
default_dark_theme: "default",
|
||||||
|
themes: {},
|
||||||
|
darkMode: true,
|
||||||
|
theme: "default",
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.dark,
|
||||||
|
.light {
|
||||||
|
display: block;
|
||||||
|
background-color: var(--primary-background-color);
|
||||||
|
padding: 0 50px;
|
||||||
|
margin: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
ha-card {
|
||||||
|
margin: 24px auto;
|
||||||
|
}
|
||||||
|
.card-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--ha-space-6);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-components-ha-slider": DemoHaSlider;
|
||||||
|
}
|
||||||
|
}
|
@@ -70,7 +70,7 @@ export class DemoHaSpinner extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 24px;
|
gap: var(--ha-space-6);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -6,21 +6,23 @@ A tooltip's target is its _first child element_, so you should only wrap one ele
|
|||||||
|
|
||||||
Tooltips use `display: contents` so they won't interfere with how elements are positioned in a flex or grid layout.
|
Tooltips use `display: contents` so they won't interfere with how elements are positioned in a flex or grid layout.
|
||||||
|
|
||||||
<ha-tooltip content="This is a tooltip">
|
<ha-button id="hover">Hover Me</ha-button>
|
||||||
<ha-button>Hover Me</ha-button>
|
<ha-tooltip for="hover">
|
||||||
|
This is a tooltip
|
||||||
</ha-tooltip>
|
</ha-tooltip>
|
||||||
|
|
||||||
```
|
```
|
||||||
<ha-tooltip content="This is a tooltip">
|
<ha-button id="hover">Hover Me</ha-button>
|
||||||
<ha-button>Hover Me</ha-button>
|
<ha-tooltip for="hover">
|
||||||
|
This is a tooltip
|
||||||
</ha-tooltip>
|
</ha-tooltip>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
This element is based on shoelace `sl-tooltip` it only sets some css tokens and has a custom show/hide animation.
|
This element is based on webawesome `wa-tooltip` it only sets some css tokens and has a custom show/hide animation.
|
||||||
|
|
||||||
<a href="https://shoelace.style/components/tooltip" target="_blank" rel="noopener noreferrer">Shoelace documentation</a>
|
<a href="https://webawesome.com/docs/components/tooltip/" target="_blank" rel="noopener noreferrer">Webawesome documentation</a>
|
||||||
|
|
||||||
### HA style tokens
|
### HA style tokens
|
||||||
|
|
||||||
@@ -28,7 +30,7 @@ In your theme settings use this without the prefixed `--`.
|
|||||||
|
|
||||||
- `--ha-tooltip-border-radius` (Default: 4px)
|
- `--ha-tooltip-border-radius` (Default: 4px)
|
||||||
- `--ha-tooltip-arrow-size` (Default: 8px)
|
- `--ha-tooltip-arrow-size` (Default: 8px)
|
||||||
- `--sl-tooltip-font-family` (Default: `var(--ha-font-family-body)`)
|
- `--wa-tooltip-font-family` (Default: `var(--ha-font-family-body)`)
|
||||||
- `--ha-tooltip-font-size` (Default: `var(--ha-font-size-s)`)
|
- `--ha-tooltip-font-size` (Default: `var(--ha-font-size-s)`)
|
||||||
- `--sl-tooltip-font-weight` (Default: `var(--ha-font-weight-normal)`)
|
- `--wa-tooltip-font-weight` (Default: `var(--ha-font-weight-normal)`)
|
||||||
- `--sl-tooltip-line-height` (Default: `var(--ha-line-height-condensed)`)
|
- `--wa-tooltip-line-height` (Default: `var(--ha-line-height-condensed)`)
|
||||||
|
@@ -11,6 +11,7 @@ import { provideHass } from "../../../../src/fake_data/provide_hass";
|
|||||||
import "../../components/demo-cards";
|
import "../../components/demo-cards";
|
||||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||||
import { ClimateEntityFeature } from "../../../../src/data/climate";
|
import { ClimateEntityFeature } from "../../../../src/data/climate";
|
||||||
|
import { FanEntityFeature } from "../../../../src/data/fan";
|
||||||
|
|
||||||
const ENTITIES = [
|
const ENTITIES = [
|
||||||
getEntity("switch", "tv_outlet", "on", {
|
getEntity("switch", "tv_outlet", "on", {
|
||||||
@@ -100,6 +101,15 @@ const ENTITIES = [
|
|||||||
ClimateEntityFeature.FAN_MODE +
|
ClimateEntityFeature.FAN_MODE +
|
||||||
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
|
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
|
||||||
}),
|
}),
|
||||||
|
getEntity("fan", "fan_demo", "on", {
|
||||||
|
friendly_name: "Ceiling fan",
|
||||||
|
device_class: "fan",
|
||||||
|
direction: "reverse",
|
||||||
|
supported_features:
|
||||||
|
FanEntityFeature.DIRECTION +
|
||||||
|
FanEntityFeature.SET_SPEED +
|
||||||
|
FanEntityFeature.OSCILLATE,
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const CONFIGS = [
|
const CONFIGS = [
|
||||||
@@ -261,6 +271,33 @@ const CONFIGS = [
|
|||||||
- type: target-temperature
|
- type: target-temperature
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Fan direction feature",
|
||||||
|
config: `
|
||||||
|
- type: tile
|
||||||
|
entity: fan.fan_demo
|
||||||
|
features:
|
||||||
|
- type: fan-direction
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Fan speed feature",
|
||||||
|
config: `
|
||||||
|
- type: tile
|
||||||
|
entity: fan.fan_demo
|
||||||
|
features:
|
||||||
|
- type: fan-speed
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Fan oscillate feature",
|
||||||
|
config: `
|
||||||
|
- type: tile
|
||||||
|
entity: fan.fan_demo
|
||||||
|
features:
|
||||||
|
- type: fan-oscillate
|
||||||
|
`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-lovelace-tile-card")
|
@customElement("demo-lovelace-tile-card")
|
||||||
|
3
gallery/src/pages/more-info/fan.markdown
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Fan
|
||||||
|
---
|
50
gallery/src/pages/more-info/fan.ts
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
import type { PropertyValues, TemplateResult } from "lit";
|
||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property, query } from "lit/decorators";
|
||||||
|
import "../../../../src/components/ha-card";
|
||||||
|
import "../../../../src/dialogs/more-info/more-info-content";
|
||||||
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
|
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
|
||||||
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
|
import "../../components/demo-more-infos";
|
||||||
|
import { FanEntityFeature } from "../../../../src/data/fan";
|
||||||
|
|
||||||
|
const ENTITIES = [
|
||||||
|
getEntity("fan", "fan", "on", {
|
||||||
|
friendly_name: "Fan",
|
||||||
|
device_class: "fan",
|
||||||
|
supported_features:
|
||||||
|
FanEntityFeature.OSCILLATE +
|
||||||
|
FanEntityFeature.DIRECTION +
|
||||||
|
FanEntityFeature.SET_SPEED,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("demo-more-info-fan")
|
||||||
|
class DemoMoreInfoFan extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: MockHomeAssistant;
|
||||||
|
|
||||||
|
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<demo-more-infos
|
||||||
|
.hass=${this.hass}
|
||||||
|
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
||||||
|
></demo-more-infos>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProperties: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProperties);
|
||||||
|
const hass = provideHass(this._demoRoot);
|
||||||
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.addEntities(ENTITIES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-more-info-fan": DemoMoreInfoFan;
|
||||||
|
}
|
||||||
|
}
|
@@ -11,7 +11,10 @@ import "../../../../src/components/ha-alert";
|
|||||||
import "../../../../src/components/ha-button-menu";
|
import "../../../../src/components/ha-button-menu";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import "../../../../src/components/ha-form/ha-form";
|
import "../../../../src/components/ha-form/ha-form";
|
||||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
import type {
|
||||||
|
HaFormSchema,
|
||||||
|
HaFormDataContainer,
|
||||||
|
} from "../../../../src/components/ha-form/types";
|
||||||
import "../../../../src/components/ha-formfield";
|
import "../../../../src/components/ha-formfield";
|
||||||
import "../../../../src/components/ha-icon-button";
|
import "../../../../src/components/ha-icon-button";
|
||||||
import "../../../../src/components/ha-list-item";
|
import "../../../../src/components/ha-list-item";
|
||||||
@@ -33,6 +36,7 @@ import { haStyle } from "../../../../src/resources/styles";
|
|||||||
import type { HomeAssistant } from "../../../../src/types";
|
import type { HomeAssistant } from "../../../../src/types";
|
||||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||||
import { hassioStyle } from "../../resources/hassio-style";
|
import { hassioStyle } from "../../resources/hassio-style";
|
||||||
|
import type { ObjectSelector, Selector } from "../../../../src/data/selector";
|
||||||
|
|
||||||
const SUPPORTED_UI_TYPES = [
|
const SUPPORTED_UI_TYPES = [
|
||||||
"string",
|
"string",
|
||||||
@@ -78,78 +82,125 @@ class HassioAddonConfig extends LitElement {
|
|||||||
|
|
||||||
@query("ha-yaml-editor") private _editor?: HaYamlEditor;
|
@query("ha-yaml-editor") private _editor?: HaYamlEditor;
|
||||||
|
|
||||||
public computeLabel = (entry: HaFormSchema): string =>
|
private _getTranslationEntry(
|
||||||
this.addon.translations[this.hass.language]?.configuration?.[entry.name]
|
language: string,
|
||||||
?.name ||
|
entry: HaFormSchema,
|
||||||
this.addon.translations.en?.configuration?.[entry.name]?.name ||
|
options?: { path?: string[] }
|
||||||
|
) {
|
||||||
|
let parent = this.addon.translations[language]?.configuration;
|
||||||
|
if (!parent) return undefined;
|
||||||
|
if (options?.path) {
|
||||||
|
for (const key of options.path) {
|
||||||
|
parent = parent[key]?.fields;
|
||||||
|
if (!parent) return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parent[entry.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
public computeLabel = (
|
||||||
|
entry: HaFormSchema,
|
||||||
|
_data: HaFormDataContainer,
|
||||||
|
options?: { path?: string[] }
|
||||||
|
): string =>
|
||||||
|
this._getTranslationEntry(this.hass.language, entry, options)?.name ||
|
||||||
|
this._getTranslationEntry("en", entry, options)?.name ||
|
||||||
entry.name;
|
entry.name;
|
||||||
|
|
||||||
public computeHelper = (entry: HaFormSchema): string =>
|
public computeHelper = (
|
||||||
this.addon.translations[this.hass.language]?.configuration?.[entry.name]
|
entry: HaFormSchema,
|
||||||
|
options?: { path?: string[] }
|
||||||
|
): string =>
|
||||||
|
this._getTranslationEntry(this.hass.language, entry, options)
|
||||||
?.description ||
|
?.description ||
|
||||||
this.addon.translations.en?.configuration?.[entry.name]?.description ||
|
this._getTranslationEntry("en", entry, options)?.description ||
|
||||||
"";
|
"";
|
||||||
|
|
||||||
private _convertSchema = memoizeOne(
|
private _convertSchema = memoizeOne(
|
||||||
// Convert supervisor schema to selectors
|
// Convert supervisor schema to selectors
|
||||||
(schema: Record<string, any>): HaFormSchema[] =>
|
(schema: readonly HaFormSchema[]): HaFormSchema[] =>
|
||||||
schema.map((entry) =>
|
this._convertSchemaElements(schema)
|
||||||
entry.type === "select"
|
|
||||||
? {
|
|
||||||
name: entry.name,
|
|
||||||
required: entry.required,
|
|
||||||
selector: { select: { options: entry.options } },
|
|
||||||
}
|
|
||||||
: entry.type === "string"
|
|
||||||
? entry.multiple
|
|
||||||
? {
|
|
||||||
name: entry.name,
|
|
||||||
required: entry.required,
|
|
||||||
selector: {
|
|
||||||
select: { options: [], multiple: true, custom_value: true },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
name: entry.name,
|
|
||||||
required: entry.required,
|
|
||||||
selector: {
|
|
||||||
text: {
|
|
||||||
type: entry.format
|
|
||||||
? entry.format
|
|
||||||
: MASKED_FIELDS.includes(entry.name)
|
|
||||||
? "password"
|
|
||||||
: "text",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: entry.type === "boolean"
|
|
||||||
? {
|
|
||||||
name: entry.name,
|
|
||||||
required: entry.required,
|
|
||||||
selector: { boolean: {} },
|
|
||||||
}
|
|
||||||
: entry.type === "schema"
|
|
||||||
? {
|
|
||||||
name: entry.name,
|
|
||||||
required: entry.required,
|
|
||||||
selector: { object: {} },
|
|
||||||
}
|
|
||||||
: entry.type === "float" || entry.type === "integer"
|
|
||||||
? {
|
|
||||||
name: entry.name,
|
|
||||||
required: entry.required,
|
|
||||||
selector: {
|
|
||||||
number: {
|
|
||||||
mode: "box",
|
|
||||||
step: entry.type === "float" ? "any" : undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: entry
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
private _filteredShchema = memoizeOne(
|
private _convertSchemaElements(
|
||||||
|
schema: readonly HaFormSchema[]
|
||||||
|
): HaFormSchema[] {
|
||||||
|
return schema.map((entry) => this._convertSchemaElement(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _convertSchemaElement(entry: any): HaFormSchema {
|
||||||
|
if (entry.type === "schema" && !entry.multiple) {
|
||||||
|
return {
|
||||||
|
name: entry.name,
|
||||||
|
type: "expandable",
|
||||||
|
required: entry.required,
|
||||||
|
schema: this._convertSchemaElements(entry.schema),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const selector = this._convertSchemaElementToSelector(entry, false);
|
||||||
|
if (selector) {
|
||||||
|
return {
|
||||||
|
name: entry.name,
|
||||||
|
required: entry.required,
|
||||||
|
selector,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _convertSchemaElementToSelector(
|
||||||
|
entry: any,
|
||||||
|
force: boolean
|
||||||
|
): Selector | null {
|
||||||
|
if (entry.type === "select") {
|
||||||
|
return { select: { options: entry.options } };
|
||||||
|
}
|
||||||
|
if (entry.type === "string") {
|
||||||
|
return entry.multiple
|
||||||
|
? { select: { options: [], multiple: true, custom_value: true } }
|
||||||
|
: {
|
||||||
|
text: {
|
||||||
|
type: entry.format
|
||||||
|
? entry.format
|
||||||
|
: MASKED_FIELDS.includes(entry.name)
|
||||||
|
? "password"
|
||||||
|
: "text",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (entry.type === "boolean") {
|
||||||
|
return { boolean: {} };
|
||||||
|
}
|
||||||
|
if (entry.type === "schema") {
|
||||||
|
const fields: NonNullable<ObjectSelector["object"]>["fields"] = {};
|
||||||
|
for (const child_entry of entry.schema) {
|
||||||
|
fields[child_entry.name] = {
|
||||||
|
required: child_entry.required,
|
||||||
|
selector: this._convertSchemaElementToSelector(child_entry, true)!,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
object: {
|
||||||
|
multiple: entry.multiple,
|
||||||
|
fields,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (entry.type === "float" || entry.type === "integer") {
|
||||||
|
return {
|
||||||
|
number: {
|
||||||
|
mode: "box",
|
||||||
|
step: entry.type === "float" ? "any" : undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (force) {
|
||||||
|
return { object: {} };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _filteredSchema = memoizeOne(
|
||||||
(options: Record<string, unknown>, schema: HaFormSchema[]) =>
|
(options: Record<string, unknown>, schema: HaFormSchema[]) =>
|
||||||
schema.filter((entry) => entry.name in options || entry.required)
|
schema.filter((entry) => entry.name in options || entry.required)
|
||||||
);
|
);
|
||||||
@@ -161,7 +212,7 @@ class HassioAddonConfig extends LitElement {
|
|||||||
showForm &&
|
showForm &&
|
||||||
JSON.stringify(this.addon.schema) !==
|
JSON.stringify(this.addon.schema) !==
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
this._filteredShchema(this.addon.options, this.addon.schema!)
|
this._filteredSchema(this.addon.options, this.addon.schema!)
|
||||||
);
|
);
|
||||||
return html`
|
return html`
|
||||||
<h1>${this.addon.name}</h1>
|
<h1>${this.addon.name}</h1>
|
||||||
@@ -199,6 +250,7 @@ class HassioAddonConfig extends LitElement {
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
${showForm
|
${showForm
|
||||||
? html`<ha-form
|
? html`<ha-form
|
||||||
|
.hass=${this.hass}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.data=${this._options!}
|
.data=${this._options!}
|
||||||
@value-changed=${this._configChanged}
|
@value-changed=${this._configChanged}
|
||||||
@@ -207,7 +259,7 @@ class HassioAddonConfig extends LitElement {
|
|||||||
.schema=${this._convertSchema(
|
.schema=${this._convertSchema(
|
||||||
this._showOptional
|
this._showOptional
|
||||||
? this.addon.schema!
|
? this.addon.schema!
|
||||||
: this._filteredShchema(
|
: this._filteredSchema(
|
||||||
this.addon.options,
|
this.addon.options,
|
||||||
this.addon.schema!
|
this.addon.schema!
|
||||||
)
|
)
|
||||||
|
@@ -781,7 +781,7 @@ class HassioAddonInfo extends LitElement {
|
|||||||
|
|
||||||
${this.addon.long_description
|
${this.addon.long_description
|
||||||
? html`
|
? html`
|
||||||
<ha-card outlined>
|
<ha-card class="long-description" outlined>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<ha-markdown
|
<ha-markdown
|
||||||
.content=${this.addon.long_description}
|
.content=${this.addon.long_description}
|
||||||
@@ -1333,6 +1333,9 @@ class HassioAddonInfo extends LitElement {
|
|||||||
.description a {
|
.description a {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
.long-description {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
ha-assist-chip {
|
ha-assist-chip {
|
||||||
--md-sys-color-primary: var(--text-primary-color);
|
--md-sys-color-primary: var(--text-primary-color);
|
||||||
--md-sys-color-on-surface: var(--text-primary-color);
|
--md-sys-color-on-surface: var(--text-primary-color);
|
||||||
|
@@ -15,6 +15,8 @@ import "../../../../src/components/ha-list";
|
|||||||
import "../../../../src/components/ha-list-item";
|
import "../../../../src/components/ha-list-item";
|
||||||
import "../../../../src/components/ha-password-field";
|
import "../../../../src/components/ha-password-field";
|
||||||
import "../../../../src/components/ha-radio";
|
import "../../../../src/components/ha-radio";
|
||||||
|
import "../../../../src/components/ha-tab-group";
|
||||||
|
import "../../../../src/components/ha-tab-group-tab";
|
||||||
import "../../../../src/components/ha-textfield";
|
import "../../../../src/components/ha-textfield";
|
||||||
import type { HaTextField } from "../../../../src/components/ha-textfield";
|
import type { HaTextField } from "../../../../src/components/ha-textfield";
|
||||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||||
@@ -36,7 +38,6 @@ import type { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
|||||||
import { haStyleDialog } from "../../../../src/resources/styles";
|
import { haStyleDialog } from "../../../../src/resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../src/types";
|
import type { HomeAssistant } from "../../../../src/types";
|
||||||
import type { HassioNetworkDialogParams } from "./show-dialog-network";
|
import type { HassioNetworkDialogParams } from "./show-dialog-network";
|
||||||
import "../../../../src/components/sl-tab-group";
|
|
||||||
|
|
||||||
const IP_VERSIONS = ["ipv4", "ipv6"];
|
const IP_VERSIONS = ["ipv4", "ipv6"];
|
||||||
|
|
||||||
@@ -114,19 +115,19 @@ export class DialogHassioNetwork
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
</ha-header-bar>
|
</ha-header-bar>
|
||||||
${this._interfaces.length > 1
|
${this._interfaces.length > 1
|
||||||
? html`<sl-tab-group @sl-tab-show=${this._handleTabActivated}
|
? html`<ha-tab-group @wa-tab-show=${this._handleTabActivated}
|
||||||
>${this._interfaces.map(
|
>${this._interfaces.map(
|
||||||
(device, index) =>
|
(device, index) =>
|
||||||
html`<sl-tab
|
html`<ha-tab-group-tab
|
||||||
slot="nav"
|
slot="nav"
|
||||||
.id=${device.interface}
|
.id=${device.interface}
|
||||||
.panel=${index.toString()}
|
.panel=${index.toString()}
|
||||||
.active=${this._curTabIndex === index}
|
.active=${this._curTabIndex === index}
|
||||||
>
|
>
|
||||||
${device.interface}
|
${device.interface}
|
||||||
</sl-tab>`
|
</ha-tab-group-tab>`
|
||||||
)}
|
)}
|
||||||
</sl-tab-group>`
|
</ha-tab-group>`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
${cache(this._renderTab())}
|
${cache(this._renderTab())}
|
||||||
@@ -627,10 +628,10 @@ export class DialogHassioNetwork
|
|||||||
--mdc-list-side-padding: 10px;
|
--mdc-list-side-padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
sl-tab {
|
ha-tab-group-tab {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
sl-tab::part(base) {
|
ha-tab-group-tab::part(base) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
@@ -119,26 +119,27 @@ class HassioRepositoriesDialog extends LitElement {
|
|||||||
<div>${repo.url}</div>
|
<div>${repo.url}</div>
|
||||||
</div>
|
</div>
|
||||||
<ha-tooltip
|
<ha-tooltip
|
||||||
|
.for="icon-button-${repo.slug}"
|
||||||
class="delete"
|
class="delete"
|
||||||
slot="end"
|
slot="end"
|
||||||
.content=${this._dialogParams!.supervisor.localize(
|
>
|
||||||
|
${this._dialogParams!.supervisor.localize(
|
||||||
usedRepositories.includes(repo.slug)
|
usedRepositories.includes(repo.slug)
|
||||||
? "dialog.repositories.used"
|
? "dialog.repositories.used"
|
||||||
: "dialog.repositories.remove"
|
: "dialog.repositories.remove"
|
||||||
)}
|
)}
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<ha-icon-button
|
|
||||||
.disabled=${usedRepositories.includes(repo.slug)}
|
|
||||||
.slug=${repo.slug}
|
|
||||||
.path=${usedRepositories.includes(repo.slug)
|
|
||||||
? mdiDeleteOff
|
|
||||||
: mdiDelete}
|
|
||||||
@click=${this._removeRepository}
|
|
||||||
>
|
|
||||||
</ha-icon-button>
|
|
||||||
</div>
|
|
||||||
</ha-tooltip>
|
</ha-tooltip>
|
||||||
|
<div .id="icon-button-${repo.slug}">
|
||||||
|
<ha-icon-button
|
||||||
|
.disabled=${usedRepositories.includes(repo.slug)}
|
||||||
|
.slug=${repo.slug}
|
||||||
|
.path=${usedRepositories.includes(repo.slug)
|
||||||
|
? mdiDeleteOff
|
||||||
|
: mdiDelete}
|
||||||
|
@click=${this._removeRepository}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
|
</div>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
@@ -159,7 +159,7 @@ class HassioSystemManagedDialog extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 16px;
|
gap: var(--ha-space-4);
|
||||||
--mdc-icon-size: 48px;
|
--mdc-icon-size: 48px;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ import type { PropertyValues, TemplateResult } from "lit";
|
|||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
import { navigate } from "../../../src/common/navigate";
|
import { goBack, navigate } from "../../../src/common/navigate";
|
||||||
import { extractSearchParam } from "../../../src/common/url/search-params";
|
import { extractSearchParam } from "../../../src/common/url/search-params";
|
||||||
import { nextRender } from "../../../src/common/util/render-status";
|
import { nextRender } from "../../../src/common/util/render-status";
|
||||||
import "../../../src/components/ha-icon-button";
|
import "../../../src/components/ha-icon-button";
|
||||||
@@ -193,7 +193,7 @@ class HassioIngressView extends LitElement {
|
|||||||
title: addon.name,
|
title: addon.name,
|
||||||
});
|
});
|
||||||
await nextRender();
|
await nextRender();
|
||||||
history.back();
|
goBack();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +275,7 @@ class HassioIngressView extends LitElement {
|
|||||||
title: addon.name,
|
title: addon.name,
|
||||||
});
|
});
|
||||||
await nextRender();
|
await nextRender();
|
||||||
history.back();
|
goBack();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -31,7 +31,7 @@ export const hassioStyle = css`
|
|||||||
.card-group {
|
.card-group {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
grid-gap: 8px;
|
grid-gap: var(--ha-space-2);
|
||||||
}
|
}
|
||||||
@media screen and (min-width: 640px) {
|
@media screen and (min-width: 640px) {
|
||||||
.card-group {
|
.card-group {
|
||||||
|
@@ -2,6 +2,7 @@ import type { TemplateResult } from "lit";
|
|||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import type { Supervisor } from "../../../src/data/supervisor/supervisor";
|
import type { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||||
|
import { goBack } from "../../../src/common/navigate";
|
||||||
import "../../../src/layouts/hass-subpage";
|
import "../../../src/layouts/hass-subpage";
|
||||||
import type { HomeAssistant, Route } from "../../../src/types";
|
import type { HomeAssistant, Route } from "../../../src/types";
|
||||||
import "./update-available-card";
|
import "./update-available-card";
|
||||||
@@ -35,7 +36,7 @@ class UpdateAvailableDashboard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _updateComplete() {
|
private _updateComplete() {
|
||||||
history.back();
|
goBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
|
@@ -213,7 +213,7 @@ class HaLandingPage extends LandingPageBaseElement {
|
|||||||
ha-card .card-content {
|
ha-card .card-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: var(--ha-space-4);
|
||||||
}
|
}
|
||||||
ha-alert p {
|
ha-alert p {
|
||||||
text-align: unset;
|
text-align: unset;
|
||||||
|
@@ -19,8 +19,9 @@
|
|||||||
height: auto;
|
height: auto;
|
||||||
padding: 32px 0;
|
padding: 32px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
max-width: 560px;
|
max-width: min(560px, calc(100vw - var(--safe-area-inset-right, 0px) - var(--safe-area-inset-left, 0px)));
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
export default {
|
export default {
|
||||||
"*.?(c|m){js,ts}": [
|
"*.?(c|m){js,ts}": [
|
||||||
"eslint --flag unstable_config_lookup_from_file --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --fix",
|
"eslint --flag v10_config_lookup_from_file --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --fix",
|
||||||
"prettier --cache --write",
|
"prettier --cache --write",
|
||||||
"lit-analyzer --quiet",
|
"lit-analyzer --quiet",
|
||||||
],
|
],
|
||||||
|
107
package.json
@@ -8,8 +8,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "script/build_frontend",
|
"build": "script/build_frontend",
|
||||||
"lint:eslint": "eslint --flag unstable_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore --max-warnings=0",
|
"lint:eslint": "eslint --flag v10_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore --max-warnings=0",
|
||||||
"format:eslint": "eslint --flag unstable_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore --fix",
|
"format:eslint": "eslint --flag v10_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore --fix",
|
||||||
"lint:prettier": "prettier . --cache --check",
|
"lint:prettier": "prettier . --cache --check",
|
||||||
"format:prettier": "prettier . --cache --write",
|
"format:prettier": "prettier . --cache --write",
|
||||||
"lint:types": "tsc",
|
"lint:types": "tsc",
|
||||||
@@ -26,16 +26,16 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@awesome.me/webawesome": "3.0.0-beta.3",
|
"@babel/runtime": "7.28.4",
|
||||||
"@babel/runtime": "7.28.2",
|
|
||||||
"@braintree/sanitize-url": "7.1.1",
|
"@braintree/sanitize-url": "7.1.1",
|
||||||
"@codemirror/autocomplete": "6.18.6",
|
"@codemirror/autocomplete": "6.19.0",
|
||||||
"@codemirror/commands": "6.8.1",
|
"@codemirror/commands": "6.8.1",
|
||||||
"@codemirror/language": "6.11.2",
|
"@codemirror/language": "6.11.3",
|
||||||
"@codemirror/legacy-modes": "6.5.1",
|
"@codemirror/legacy-modes": "6.5.1",
|
||||||
"@codemirror/search": "6.5.11",
|
"@codemirror/search": "6.5.11",
|
||||||
"@codemirror/state": "6.5.2",
|
"@codemirror/state": "6.5.2",
|
||||||
"@codemirror/view": "6.38.1",
|
"@codemirror/view": "6.38.4",
|
||||||
|
"@date-fns/tz": "1.4.1",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.18.0",
|
"@formatjs/intl-datetimeformat": "6.18.0",
|
||||||
"@formatjs/intl-displaynames": "6.8.11",
|
"@formatjs/intl-displaynames": "6.8.11",
|
||||||
@@ -46,12 +46,13 @@
|
|||||||
"@formatjs/intl-numberformat": "8.15.4",
|
"@formatjs/intl-numberformat": "8.15.4",
|
||||||
"@formatjs/intl-pluralrules": "5.4.4",
|
"@formatjs/intl-pluralrules": "5.4.4",
|
||||||
"@formatjs/intl-relativetimeformat": "11.4.11",
|
"@formatjs/intl-relativetimeformat": "11.4.11",
|
||||||
"@fullcalendar/core": "6.1.18",
|
"@fullcalendar/core": "6.1.19",
|
||||||
"@fullcalendar/daygrid": "6.1.18",
|
"@fullcalendar/daygrid": "6.1.19",
|
||||||
"@fullcalendar/interaction": "6.1.18",
|
"@fullcalendar/interaction": "6.1.19",
|
||||||
"@fullcalendar/list": "6.1.18",
|
"@fullcalendar/list": "6.1.19",
|
||||||
"@fullcalendar/luxon3": "6.1.18",
|
"@fullcalendar/luxon3": "6.1.19",
|
||||||
"@fullcalendar/timegrid": "6.1.18",
|
"@fullcalendar/timegrid": "6.1.19",
|
||||||
|
"@home-assistant/webawesome": "3.0.0-beta.6.ha.0",
|
||||||
"@lezer/highlight": "1.2.1",
|
"@lezer/highlight": "1.2.1",
|
||||||
"@lit-labs/motion": "1.0.9",
|
"@lit-labs/motion": "1.0.9",
|
||||||
"@lit-labs/observers": "2.0.6",
|
"@lit-labs/observers": "2.0.6",
|
||||||
@@ -61,7 +62,6 @@
|
|||||||
"@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",
|
||||||
"@material/mwc-button": "0.27.0",
|
|
||||||
"@material/mwc-checkbox": "0.27.0",
|
"@material/mwc-checkbox": "0.27.0",
|
||||||
"@material/mwc-dialog": "0.27.0",
|
"@material/mwc-dialog": "0.27.0",
|
||||||
"@material/mwc-drawer": "0.27.0",
|
"@material/mwc-drawer": "0.27.0",
|
||||||
@@ -81,39 +81,37 @@
|
|||||||
"@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": "2.3.0",
|
"@material/web": "2.4.0",
|
||||||
"@mdi/js": "7.4.47",
|
"@mdi/js": "7.4.47",
|
||||||
"@mdi/svg": "7.4.47",
|
"@mdi/svg": "7.4.47",
|
||||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||||
"@shoelace-style/shoelace": "2.20.1",
|
|
||||||
"@swc/helpers": "0.5.17",
|
"@swc/helpers": "0.5.17",
|
||||||
"@thomasloven/round-slider": "0.6.0",
|
"@thomasloven/round-slider": "0.6.0",
|
||||||
"@tsparticles/engine": "3.8.1",
|
"@tsparticles/engine": "3.9.1",
|
||||||
"@tsparticles/preset-links": "3.2.0",
|
"@tsparticles/preset-links": "3.2.0",
|
||||||
"@vaadin/combo-box": "24.7.9",
|
"@vaadin/combo-box": "24.9.1",
|
||||||
"@vaadin/vaadin-themable-mixin": "24.7.9",
|
"@vaadin/vaadin-themable-mixin": "24.9.1",
|
||||||
"@vibrant/color": "4.0.0",
|
"@vibrant/color": "4.0.0",
|
||||||
"@vue/web-component-wrapper": "1.3.0",
|
"@vue/web-component-wrapper": "1.3.0",
|
||||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||||
"app-datepicker": "5.1.1",
|
"app-datepicker": "5.1.1",
|
||||||
"barcode-detector": "3.0.5",
|
"barcode-detector": "3.0.5",
|
||||||
"color-name": "2.0.0",
|
"color-name": "2.0.2",
|
||||||
"comlink": "4.4.2",
|
"comlink": "4.4.2",
|
||||||
"core-js": "3.44.0",
|
"core-js": "3.45.1",
|
||||||
"cropperjs": "1.6.2",
|
"cropperjs": "1.6.2",
|
||||||
"culori": "4.0.2",
|
"culori": "4.0.2",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
"date-fns-tz": "3.2.0",
|
|
||||||
"deep-clone-simple": "1.1.1",
|
"deep-clone-simple": "1.1.1",
|
||||||
"deep-freeze": "0.0.1",
|
"deep-freeze": "0.0.1",
|
||||||
"dialog-polyfill": "0.5.6",
|
"dialog-polyfill": "0.5.6",
|
||||||
"echarts": "5.6.0",
|
"echarts": "6.0.0",
|
||||||
"element-internals-polyfill": "3.0.2",
|
"element-internals-polyfill": "3.0.2",
|
||||||
"fuse.js": "7.1.0",
|
"fuse.js": "7.1.0",
|
||||||
"google-timezones-json": "1.2.0",
|
"google-timezones-json": "1.2.0",
|
||||||
"gulp-zopfli-green": "6.0.2",
|
"gulp-zopfli-green": "6.0.2",
|
||||||
"hls.js": "1.6.7",
|
"hls.js": "1.6.13",
|
||||||
"home-assistant-js-websocket": "9.5.0",
|
"home-assistant-js-websocket": "9.5.0",
|
||||||
"idb-keyval": "6.2.2",
|
"idb-keyval": "6.2.2",
|
||||||
"intl-messageformat": "10.7.16",
|
"intl-messageformat": "10.7.16",
|
||||||
@@ -123,8 +121,8 @@
|
|||||||
"leaflet.markercluster": "1.5.3",
|
"leaflet.markercluster": "1.5.3",
|
||||||
"lit": "3.3.1",
|
"lit": "3.3.1",
|
||||||
"lit-html": "3.3.1",
|
"lit-html": "3.3.1",
|
||||||
"luxon": "3.7.1",
|
"luxon": "3.7.2",
|
||||||
"marked": "16.1.1",
|
"marked": "16.3.0",
|
||||||
"memoize-one": "6.0.0",
|
"memoize-one": "6.0.0",
|
||||||
"node-vibrant": "4.0.3",
|
"node-vibrant": "4.0.3",
|
||||||
"object-hash": "3.0.0",
|
"object-hash": "3.0.0",
|
||||||
@@ -137,7 +135,7 @@
|
|||||||
"stacktrace-js": "2.0.2",
|
"stacktrace-js": "2.0.2",
|
||||||
"superstruct": "2.0.2",
|
"superstruct": "2.0.2",
|
||||||
"tinykeys": "3.0.0",
|
"tinykeys": "3.0.0",
|
||||||
"ua-parser-js": "2.0.4",
|
"ua-parser-js": "2.0.5",
|
||||||
"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",
|
||||||
@@ -150,30 +148,30 @@
|
|||||||
"xss": "1.0.15"
|
"xss": "1.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.28.0",
|
"@babel/core": "7.28.4",
|
||||||
"@babel/helper-define-polyfill-provider": "0.6.5",
|
"@babel/helper-define-polyfill-provider": "0.6.5",
|
||||||
"@babel/plugin-transform-runtime": "7.28.0",
|
"@babel/plugin-transform-runtime": "7.28.3",
|
||||||
"@babel/preset-env": "7.28.0",
|
"@babel/preset-env": "7.28.3",
|
||||||
"@bundle-stats/plugin-webpack-filter": "4.21.1",
|
"@bundle-stats/plugin-webpack-filter": "4.21.3",
|
||||||
"@lokalise/node-api": "15.0.0",
|
"@lokalise/node-api": "15.2.1",
|
||||||
"@octokit/auth-oauth-device": "8.0.1",
|
"@octokit/auth-oauth-device": "8.0.1",
|
||||||
"@octokit/plugin-retry": "8.0.1",
|
"@octokit/plugin-retry": "8.0.1",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@rsdoctor/rspack-plugin": "1.1.10",
|
"@rsdoctor/rspack-plugin": "1.3.1",
|
||||||
"@rspack/cli": "1.4.10",
|
"@rspack/core": "1.5.8",
|
||||||
"@rspack/core": "1.4.10",
|
"@rspack/dev-server": "1.1.4",
|
||||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||||
"@types/chromecast-caf-receiver": "6.0.22",
|
"@types/chromecast-caf-receiver": "6.0.22",
|
||||||
"@types/chromecast-caf-sender": "1.0.11",
|
"@types/chromecast-caf-sender": "1.0.11",
|
||||||
"@types/color-name": "2.0.0",
|
"@types/color-name": "2.0.0",
|
||||||
"@types/culori": "4.0.0",
|
"@types/culori": "4.0.1",
|
||||||
"@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.20",
|
"@types/leaflet": "1.9.20",
|
||||||
"@types/leaflet-draw": "1.0.12",
|
"@types/leaflet-draw": "1.0.13",
|
||||||
"@types/leaflet.markercluster": "1.5.5",
|
"@types/leaflet.markercluster": "1.5.6",
|
||||||
"@types/lodash.merge": "4.6.9",
|
"@types/lodash.merge": "4.6.9",
|
||||||
"@types/luxon": "3.6.2",
|
"@types/luxon": "3.7.1",
|
||||||
"@types/mocha": "10.0.10",
|
"@types/mocha": "10.0.10",
|
||||||
"@types/qrcode": "1.5.5",
|
"@types/qrcode": "1.5.5",
|
||||||
"@types/sortablejs": "1.15.8",
|
"@types/sortablejs": "1.15.8",
|
||||||
@@ -184,18 +182,18 @@
|
|||||||
"babel-loader": "10.0.0",
|
"babel-loader": "10.0.0",
|
||||||
"babel-plugin-template-html-minifier": "4.1.0",
|
"babel-plugin-template-html-minifier": "4.1.0",
|
||||||
"browserslist-useragent-regexp": "4.1.3",
|
"browserslist-useragent-regexp": "4.1.3",
|
||||||
"del": "8.0.0",
|
"del": "8.0.1",
|
||||||
"eslint": "9.32.0",
|
"eslint": "9.36.0",
|
||||||
"eslint-config-airbnb-base": "15.0.0",
|
"eslint-config-airbnb-base": "15.0.0",
|
||||||
"eslint-config-prettier": "10.1.8",
|
"eslint-config-prettier": "10.1.8",
|
||||||
"eslint-import-resolver-webpack": "0.13.10",
|
"eslint-import-resolver-webpack": "0.13.10",
|
||||||
"eslint-plugin-import": "2.32.0",
|
"eslint-plugin-import": "2.32.0",
|
||||||
"eslint-plugin-lit": "2.1.1",
|
"eslint-plugin-lit": "2.1.1",
|
||||||
"eslint-plugin-lit-a11y": "5.1.1",
|
"eslint-plugin-lit-a11y": "5.1.1",
|
||||||
"eslint-plugin-unused-imports": "4.1.4",
|
"eslint-plugin-unused-imports": "4.2.0",
|
||||||
"eslint-plugin-wc": "3.0.1",
|
"eslint-plugin-wc": "3.0.1",
|
||||||
"fancy-log": "2.0.0",
|
"fancy-log": "2.0.0",
|
||||||
"fs-extra": "11.3.0",
|
"fs-extra": "11.3.2",
|
||||||
"glob": "11.0.3",
|
"glob": "11.0.3",
|
||||||
"gulp": "5.0.1",
|
"gulp": "5.0.1",
|
||||||
"gulp-brotli": "3.0.0",
|
"gulp-brotli": "3.0.0",
|
||||||
@@ -203,23 +201,23 @@
|
|||||||
"gulp-rename": "2.1.0",
|
"gulp-rename": "2.1.0",
|
||||||
"html-minifier-terser": "7.2.0",
|
"html-minifier-terser": "7.2.0",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"jsdom": "26.1.0",
|
"jsdom": "27.0.0",
|
||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lint-staged": "16.1.2",
|
"lint-staged": "16.2.3",
|
||||||
"lit-analyzer": "2.0.3",
|
"lit-analyzer": "2.0.3",
|
||||||
"lodash.merge": "4.6.2",
|
"lodash.merge": "4.6.2",
|
||||||
"lodash.template": "4.5.0",
|
"lodash.template": "4.5.0",
|
||||||
"map-stream": "0.0.7",
|
"map-stream": "0.0.7",
|
||||||
"pinst": "3.0.0",
|
"pinst": "3.0.0",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"rspack-manifest-plugin": "5.0.3",
|
"rspack-manifest-plugin": "5.1.0",
|
||||||
"serve": "14.2.4",
|
"serve": "14.2.5",
|
||||||
"sinon": "21.0.0",
|
"sinon": "21.0.0",
|
||||||
"tar": "7.4.3",
|
"tar": "7.5.1",
|
||||||
"terser-webpack-plugin": "5.3.14",
|
"terser-webpack-plugin": "5.3.14",
|
||||||
"ts-lit-plugin": "2.0.2",
|
"ts-lit-plugin": "2.0.2",
|
||||||
"typescript": "5.8.3",
|
"typescript": "5.9.2",
|
||||||
"typescript-eslint": "8.38.0",
|
"typescript-eslint": "8.44.1",
|
||||||
"vite-tsconfig-paths": "5.1.4",
|
"vite-tsconfig-paths": "5.1.4",
|
||||||
"vitest": "3.2.4",
|
"vitest": "3.2.4",
|
||||||
"webpack-stats-plugin": "1.1.3",
|
"webpack-stats-plugin": "1.1.3",
|
||||||
@@ -232,11 +230,10 @@
|
|||||||
"lit-html": "3.3.1",
|
"lit-html": "3.3.1",
|
||||||
"clean-css": "5.3.3",
|
"clean-css": "5.3.3",
|
||||||
"@lit/reactive-element": "2.1.1",
|
"@lit/reactive-element": "2.1.1",
|
||||||
"@fullcalendar/daygrid": "6.1.18",
|
"@fullcalendar/daygrid": "6.1.19",
|
||||||
"globals": "16.3.0",
|
"globals": "16.4.0",
|
||||||
"tslib": "2.8.1",
|
"tslib": "2.8.1",
|
||||||
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
|
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch"
|
||||||
"@vaadin/vaadin-themable-mixin": "24.7.9"
|
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.9.2"
|
"packageManager": "yarn@4.10.3"
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,11 @@
|
|||||||
export default {
|
export default {
|
||||||
trailingComma: "es5",
|
trailingComma: "es5",
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: "*.globals.ts",
|
||||||
|
options: {
|
||||||
|
printWidth: 9999, // Effectively disables line wrapping for these files
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
Before Width: | Height: | Size: 5.4 KiB |
@@ -0,0 +1,76 @@
|
|||||||
|
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_4744_40067)">
|
||||||
|
<path d="M0 6C0 2.68629 2.68629 0 6 0H28C31.3137 0 34 2.68629 34 6C34 9.31371 31.3137 12 28 12H6C2.68629 12 0 9.31371 0 6Z" fill="white" fill-opacity="0.48"/>
|
||||||
|
<path d="M0 28C0 23.5817 3.58172 20 8 20H42.6667C47.0849 20 50.6667 23.5817 50.6667 28V36C50.6667 40.4183 47.0849 44 42.6667 44H8.00001C3.58173 44 0 40.4183 0 36V28Z" fill="#1C1C1C"/>
|
||||||
|
<path d="M8 20.5H42.667C46.809 20.5002 50.167 23.858 50.167 28V36C50.167 40.142 46.809 43.4998 42.667 43.5H8C3.85787 43.5 0.5 40.1421 0.5 36V28C0.5 23.8579 3.85786 20.5 8 20.5Z" stroke="white" stroke-opacity="0.24"/>
|
||||||
|
<path d="M6 32C6 28.6863 8.68629 26 12 26C15.3137 26 18 28.6863 18 32C18 35.3137 15.3137 38 12 38C8.68629 38 6 35.3137 6 32Z" fill="white" fill-opacity="0.24"/>
|
||||||
|
<path d="M24 31C24 29.3431 25.3431 28 27 28H39.6667C41.3235 28 42.6667 29.3431 42.6667 31V33C42.6667 34.6569 41.3235 36 39.6667 36H27C25.3431 36 24 34.6569 24 33V31Z" fill="white" fill-opacity="0.24"/>
|
||||||
|
<path d="M54.6666 28C54.6666 23.5817 58.2483 20 62.6666 20H97.3333C101.752 20 105.333 23.5817 105.333 28V36C105.333 40.4183 101.752 44 97.3333 44H62.6666C58.2484 44 54.6666 40.4183 54.6666 36V28Z" fill="#1C1C1C"/>
|
||||||
|
<path d="M62.6666 20.5H97.3336C101.476 20.5002 104.834 23.858 104.834 28V36C104.834 40.142 101.476 43.4998 97.3336 43.5H62.6666C58.5245 43.5 55.1666 40.1421 55.1666 36V28C55.1666 23.8579 58.5245 20.5 62.6666 20.5Z" stroke="white" stroke-opacity="0.24"/>
|
||||||
|
<path d="M60.6666 32C60.6666 28.6863 63.3529 26 66.6666 26C69.9803 26 72.6666 28.6863 72.6666 32C72.6666 35.3137 69.9803 38 66.6666 38C63.3529 38 60.6666 35.3137 60.6666 32Z" fill="white" fill-opacity="0.24"/>
|
||||||
|
<path d="M78.6666 31C78.6666 29.3431 80.0098 28 81.6666 28H94.3333C95.9901 28 97.3333 29.3431 97.3333 31V33C97.3333 34.6569 95.9901 36 94.3333 36H81.6666C80.0098 36 78.6666 34.6569 78.6666 33V31Z" fill="white" fill-opacity="0.24"/>
|
||||||
|
<path d="M109.333 28C109.333 23.5817 112.915 20 117.333 20H152C156.418 20 160 23.5817 160 28V36C160 40.4183 156.418 44 152 44H117.333C112.915 44 109.333 40.4183 109.333 36V28Z" fill="#1C1C1C"/>
|
||||||
|
<path d="M117.333 20.5H152C156.142 20.5002 159.5 23.858 159.5 28V36C159.5 40.142 156.142 43.4998 152 43.5H117.333C113.191 43.5 109.833 40.1421 109.833 36V28C109.833 23.8579 113.191 20.5 117.333 20.5Z" stroke="white" stroke-opacity="0.24"/>
|
||||||
|
<path d="M115.333 32C115.333 28.6863 118.02 26 121.333 26C124.647 26 127.333 28.6863 127.333 32C127.333 35.3137 124.647 38 121.333 38C118.02 38 115.333 35.3137 115.333 32Z" fill="white" fill-opacity="0.24"/>
|
||||||
|
<path d="M133.333 31C133.333 29.3431 134.677 28 136.333 28H149C150.657 28 152 29.3431 152 31V33C152 34.6569 150.657 36 149 36H136.333C134.677 36 133.333 34.6569 133.333 33V31Z" fill="white" fill-opacity="0.24"/>
|
||||||
|
<path d="M0 56C0 53.7909 1.79086 52 4 52H29C31.2091 52 33 53.7909 33 56C33 58.2091 31.2091 60 29 60H4C1.79086 60 0 58.2091 0 56Z" fill="white" fill-opacity="0.48"/>
|
||||||
|
<path d="M0 72C0 67.5817 3.58172 64 8 64H29C33.4183 64 37 67.5817 37 72V96C37 100.418 33.4183 104 29 104H8C3.58172 104 0 100.418 0 96V72Z" fill="#1C1C1C"/>
|
||||||
|
<path d="M8 64.5H29C33.1421 64.5 36.5 67.8579 36.5 72V96C36.5 100.142 33.1421 103.5 29 103.5H8C3.85786 103.5 0.5 100.142 0.5 96V72C0.5 67.8579 3.85786 64.5 8 64.5Z" stroke="white" stroke-opacity="0.24"/>
|
||||||
|
<mask id="mask0_4744_40067" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="6" y="72" width="25" height="24">
|
||||||
|
<path d="M18.5 74C16.6435 74 14.863 74.7375 13.5503 76.0503C12.2375 77.363 11.5 79.1435 11.5 81C11.5 83.38 12.69 85.47 14.5 86.74V89C14.5 89.2652 14.6054 89.5196 14.7929 89.7071C14.9804 89.8946 15.2348 90 15.5 90H21.5C21.7652 90 22.0196 89.8946 22.2071 89.7071C22.3946 89.5196 22.5 89.2652 22.5 89V86.74C24.31 85.47 25.5 83.38 25.5 81C25.5 79.1435 24.7625 77.363 23.4497 76.0503C22.137 74.7375 20.3565 74 18.5 74ZM15.5 93C15.5 93.2652 15.6054 93.5196 15.7929 93.7071C15.9804 93.8946 16.2348 94 16.5 94H20.5C20.7652 94 21.0196 93.8946 21.2071 93.7071C21.3946 93.5196 21.5 93.2652 21.5 93V92H15.5V93Z" fill="black"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_4744_40067)">
|
||||||
|
<rect x="6.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
||||||
|
</g>
|
||||||
|
<path d="M41 72C41 67.5817 44.5817 64 49 64H70C74.4183 64 78 67.5817 78 72V96C78 100.418 74.4183 104 70 104H49C44.5817 104 41 100.418 41 96V72Z" fill="#1C1C1C"/>
|
||||||
|
<path d="M49 64.5H70C74.1421 64.5 77.5 67.8579 77.5 72V96C77.5 100.142 74.1421 103.5 70 103.5H49C44.8579 103.5 41.5 100.142 41.5 96V72C41.5 67.8579 44.8579 64.5 49 64.5Z" stroke="white" stroke-opacity="0.24"/>
|
||||||
|
<mask id="mask1_4744_40067" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="47" y="72" width="25" height="24">
|
||||||
|
<path d="M66.5 80C67.61 80 68.5 80.9 68.5 82V88.76C69.11 89.31 69.5 90.11 69.5 91C69.5 92.66 68.16 94 66.5 94C64.84 94 63.5 92.66 63.5 91C63.5 90.11 63.89 89.31 64.5 88.76V82C64.5 80.9 65.4 80 66.5 80ZM66.5 81C65.95 81 65.5 81.45 65.5 82V83H67.5V82C67.5 81.45 67.05 81 66.5 81ZM52.5 92V84H49.5L59.5 75L63.9 78.96C63.04 79.69 62.5 80.78 62.5 82V88C61.87 88.83 61.5 89.87 61.5 91L61.6 92H52.5Z" fill="black"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask1_4744_40067)">
|
||||||
|
<rect x="47.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
||||||
|
</g>
|
||||||
|
<path d="M82 72C82 67.5817 85.5817 64 90 64H111C115.418 64 119 67.5817 119 72V96C119 100.418 115.418 104 111 104H90C85.5817 104 82 100.418 82 96V72Z" fill="#1C1C1C"/>
|
||||||
|
<path d="M90 64.5H111C115.142 64.5 118.5 67.8579 118.5 72V96C118.5 100.142 115.142 103.5 111 103.5H90C85.8579 103.5 82.5 100.142 82.5 96V72C82.5 67.8579 85.8579 64.5 90 64.5Z" stroke="white" stroke-opacity="0.24"/>
|
||||||
|
<mask id="mask2_4744_40067" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="88" y="72" width="25" height="24">
|
||||||
|
<path d="M100.5 84H107.5C106.97 88.11 104.22 91.78 100.5 92.92V84H93.5V78.3L100.5 75.19M100.5 73L91.5 77V83C91.5 88.55 95.34 93.73 100.5 95C105.66 93.73 109.5 88.55 109.5 83V77L100.5 73Z" fill="black"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask2_4744_40067)">
|
||||||
|
<rect x="88.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
||||||
|
</g>
|
||||||
|
<path d="M123 72C123 67.5817 126.582 64 131 64H152C156.418 64 160 67.5817 160 72V96C160 100.418 156.418 104 152 104H131C126.582 104 123 100.418 123 96V72Z" fill="#1C1C1C"/>
|
||||||
|
<path d="M131 64.5H152C156.142 64.5 159.5 67.8579 159.5 72V96C159.5 100.142 156.142 103.5 152 103.5H131C126.858 103.5 123.5 100.142 123.5 96V72C123.5 67.8579 126.858 64.5 131 64.5Z" stroke="white" stroke-opacity="0.24"/>
|
||||||
|
<mask id="mask3_4744_40067" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="129" y="72" width="25" height="24">
|
||||||
|
<path d="M145.5 84C145.5 83.4696 145.711 82.9609 146.086 82.5858C146.461 82.2107 146.97 82 147.5 82C148.03 82 148.539 82.2107 148.914 82.5858C149.289 82.9609 149.5 83.4696 149.5 84C149.5 84.5304 149.289 85.0391 148.914 85.4142C148.539 85.7893 148.03 86 147.5 86C146.97 86 146.461 85.7893 146.086 85.4142C145.711 85.0391 145.5 84.5304 145.5 84ZM139.5 84C139.5 83.4696 139.711 82.9609 140.086 82.5858C140.461 82.2107 140.97 82 141.5 82C142.03 82 142.539 82.2107 142.914 82.5858C143.289 82.9609 143.5 83.4696 143.5 84C143.5 84.5304 143.289 85.0391 142.914 85.4142C142.539 85.7893 142.03 86 141.5 86C140.97 86 140.461 85.7893 140.086 85.4142C139.711 85.0391 139.5 84.5304 139.5 84ZM133.5 84C133.5 83.4696 133.711 82.9609 134.086 82.5858C134.461 82.2107 134.97 82 135.5 82C136.03 82 136.539 82.2107 136.914 82.5858C137.289 82.9609 137.5 83.4696 137.5 84C137.5 84.5304 137.289 85.0391 136.914 85.4142C136.539 85.7893 136.03 86 135.5 86C134.97 86 134.461 85.7893 134.086 85.4142C133.711 85.0391 133.5 84.5304 133.5 84Z" fill="black"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask3_4744_40067)">
|
||||||
|
<rect x="129.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
||||||
|
</g>
|
||||||
|
<path d="M0 116C0 113.791 1.79086 112 4 112H29C31.2091 112 33 113.791 33 116C33 118.209 31.2091 120 29 120H4C1.79086 120 0 118.209 0 116Z" fill="white" fill-opacity="0.48"/>
|
||||||
|
<path d="M0 132C0 127.582 3.58172 124 8 124H70C74.4183 124 78 127.582 78 132V160H0V132Z" fill="url(#paint0_linear_4744_40067)"/>
|
||||||
|
<path d="M8 124.5H70C74.1421 124.5 77.5 127.858 77.5 132V159.5H0.5V132C0.5 127.858 3.85786 124.5 8 124.5Z" stroke="url(#paint1_linear_4744_40067)" stroke-opacity="0.12"/>
|
||||||
|
<path d="M82 132C82 127.582 85.5817 124 90 124H152C156.418 124 160 127.582 160 132V160H82V132Z" fill="url(#paint2_linear_4744_40067)"/>
|
||||||
|
<path d="M90 124.5H152C156.142 124.5 159.5 127.858 159.5 132V159.5H82.5V132C82.5 127.858 85.8579 124.5 90 124.5Z" stroke="url(#paint3_linear_4744_40067)" stroke-opacity="0.12"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_4744_40067" x1="39" y1="124" x2="39" y2="160" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.5" stop-color="#1C1C1C"/>
|
||||||
|
<stop offset="1" stop-color="#1C1C1C" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear_4744_40067" x1="39" y1="124" x2="39" y2="160" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.5" stop-color="white" stop-opacity="0.24"/>
|
||||||
|
<stop offset="1" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint2_linear_4744_40067" x1="121" y1="124" x2="121" y2="160" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.5" stop-color="#1C1C1C"/>
|
||||||
|
<stop offset="1" stop-color="#1C1C1C" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint3_linear_4744_40067" x1="121" y1="124" x2="121" y2="160" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.5" stop-color="white" stop-opacity="0.24"/>
|
||||||
|
<stop offset="1" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="clip0_4744_40067">
|
||||||
|
<rect width="160" height="160" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,76 @@
|
|||||||
|
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_4744_39984)">
|
||||||
|
<path d="M0 6C0 2.68629 2.68629 0 6 0H28C31.3137 0 34 2.68629 34 6C34 9.31371 31.3137 12 28 12H6C2.68629 12 0 9.31371 0 6Z" fill="black" fill-opacity="0.32"/>
|
||||||
|
<path d="M0 28C0 23.5817 3.58172 20 8 20H42.6667C47.0849 20 50.6667 23.5817 50.6667 28V36C50.6667 40.4183 47.0849 44 42.6667 44H8.00001C3.58173 44 0 40.4183 0 36V28Z" fill="white"/>
|
||||||
|
<path d="M8 20.5H42.667C46.809 20.5002 50.167 23.858 50.167 28V36C50.167 40.142 46.809 43.4998 42.667 43.5H8C3.85787 43.5 0.5 40.1421 0.5 36V28C0.5 23.8579 3.85786 20.5 8 20.5Z" stroke="black" stroke-opacity="0.12"/>
|
||||||
|
<rect x="6" y="26" width="12" height="12" rx="6" fill="black" fill-opacity="0.12"/>
|
||||||
|
<path d="M24 31C24 29.3431 25.3431 28 27 28H39.6667C41.3235 28 42.6667 29.3431 42.6667 31V33C42.6667 34.6569 41.3235 36 39.6667 36H27C25.3431 36 24 34.6569 24 33V31Z" fill="black" fill-opacity="0.12"/>
|
||||||
|
<path d="M54.6667 28C54.6667 23.5817 58.2484 20 62.6667 20H97.3333C101.752 20 105.333 23.5817 105.333 28V36C105.333 40.4183 101.752 44 97.3334 44H62.6667C58.2484 44 54.6667 40.4183 54.6667 36V28Z" fill="white"/>
|
||||||
|
<path d="M62.6667 20.5H97.3337C101.476 20.5002 104.834 23.858 104.834 28V36C104.834 40.142 101.476 43.4998 97.3337 43.5H62.6667C58.5246 43.5 55.1667 40.1421 55.1667 36V28C55.1667 23.8579 58.5246 20.5 62.6667 20.5Z" stroke="black" stroke-opacity="0.12"/>
|
||||||
|
<rect x="60.6667" y="26" width="12" height="12" rx="6" fill="black" fill-opacity="0.12"/>
|
||||||
|
<path d="M78.6667 31C78.6667 29.3431 80.0098 28 81.6667 28H94.3334C95.9902 28 97.3334 29.3431 97.3334 31V33C97.3334 34.6569 95.9902 36 94.3334 36H81.6667C80.0098 36 78.6667 34.6569 78.6667 33V31Z" fill="black" fill-opacity="0.12"/>
|
||||||
|
<path d="M109.333 28C109.333 23.5817 112.915 20 117.333 20H152C156.418 20 160 23.5817 160 28V36C160 40.4183 156.418 44 152 44H117.333C112.915 44 109.333 40.4183 109.333 36V28Z" fill="white"/>
|
||||||
|
<path d="M117.333 20.5H152C156.142 20.5002 159.5 23.858 159.5 28V36C159.5 40.142 156.142 43.4998 152 43.5H117.333C113.191 43.5 109.833 40.1421 109.833 36V28C109.833 23.8579 113.191 20.5 117.333 20.5Z" stroke="black" stroke-opacity="0.12"/>
|
||||||
|
<rect x="115.333" y="26" width="12" height="12" rx="6" fill="black" fill-opacity="0.12"/>
|
||||||
|
<path d="M133.333 31C133.333 29.3431 134.676 28 136.333 28H149C150.657 28 152 29.3431 152 31V33C152 34.6569 150.657 36 149 36H136.333C134.676 36 133.333 34.6569 133.333 33V31Z" fill="black" fill-opacity="0.12"/>
|
||||||
|
<path d="M0 56C0 53.7909 1.79086 52 4 52H29C31.2091 52 33 53.7909 33 56C33 58.2091 31.2091 60 29 60H4C1.79086 60 0 58.2091 0 56Z" fill="black" fill-opacity="0.32"/>
|
||||||
|
<path d="M0 72C0 67.5817 3.58172 64 8 64H29C33.4183 64 37 67.5817 37 72V96C37 100.418 33.4183 104 29 104H8C3.58172 104 0 100.418 0 96V72Z" fill="white"/>
|
||||||
|
<path d="M8 64.5H29C33.1421 64.5 36.5 67.8579 36.5 72V96C36.5 100.142 33.1421 103.5 29 103.5H8C3.85786 103.5 0.5 100.142 0.5 96V72C0.5 67.8579 3.85786 64.5 8 64.5Z" stroke="black" stroke-opacity="0.12"/>
|
||||||
|
<mask id="mask0_4744_39984" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="6" y="72" width="25" height="24">
|
||||||
|
<path d="M18.5 74C16.6435 74 14.863 74.7375 13.5503 76.0503C12.2375 77.363 11.5 79.1435 11.5 81C11.5 83.38 12.69 85.47 14.5 86.74V89C14.5 89.2652 14.6054 89.5196 14.7929 89.7071C14.9804 89.8946 15.2348 90 15.5 90H21.5C21.7652 90 22.0196 89.8946 22.2071 89.7071C22.3946 89.5196 22.5 89.2652 22.5 89V86.74C24.31 85.47 25.5 83.38 25.5 81C25.5 79.1435 24.7625 77.363 23.4497 76.0503C22.137 74.7375 20.3565 74 18.5 74ZM15.5 93C15.5 93.2652 15.6054 93.5196 15.7929 93.7071C15.9804 93.8946 16.2348 94 16.5 94H20.5C20.7652 94 21.0196 93.8946 21.2071 93.7071C21.3946 93.5196 21.5 93.2652 21.5 93V92H15.5V93Z" fill="black"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_4744_39984)">
|
||||||
|
<rect x="6.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
||||||
|
</g>
|
||||||
|
<path d="M41 72C41 67.5817 44.5817 64 49 64H70C74.4183 64 78 67.5817 78 72V96C78 100.418 74.4183 104 70 104H49C44.5817 104 41 100.418 41 96V72Z" fill="white"/>
|
||||||
|
<path d="M49 64.5H70C74.1421 64.5 77.5 67.8579 77.5 72V96C77.5 100.142 74.1421 103.5 70 103.5H49C44.8579 103.5 41.5 100.142 41.5 96V72C41.5 67.8579 44.8579 64.5 49 64.5Z" stroke="black" stroke-opacity="0.12"/>
|
||||||
|
<mask id="mask1_4744_39984" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="47" y="72" width="25" height="24">
|
||||||
|
<path d="M66.5 80C67.61 80 68.5 80.9 68.5 82V88.76C69.11 89.31 69.5 90.11 69.5 91C69.5 92.66 68.16 94 66.5 94C64.84 94 63.5 92.66 63.5 91C63.5 90.11 63.89 89.31 64.5 88.76V82C64.5 80.9 65.4 80 66.5 80ZM66.5 81C65.95 81 65.5 81.45 65.5 82V83H67.5V82C67.5 81.45 67.05 81 66.5 81ZM52.5 92V84H49.5L59.5 75L63.9 78.96C63.04 79.69 62.5 80.78 62.5 82V88C61.87 88.83 61.5 89.87 61.5 91L61.6 92H52.5Z" fill="black"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask1_4744_39984)">
|
||||||
|
<rect x="47.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
||||||
|
</g>
|
||||||
|
<path d="M82 72C82 67.5817 85.5817 64 90 64H111C115.418 64 119 67.5817 119 72V96C119 100.418 115.418 104 111 104H90C85.5817 104 82 100.418 82 96V72Z" fill="white"/>
|
||||||
|
<path d="M90 64.5H111C115.142 64.5 118.5 67.8579 118.5 72V96C118.5 100.142 115.142 103.5 111 103.5H90C85.8579 103.5 82.5 100.142 82.5 96V72C82.5 67.8579 85.8579 64.5 90 64.5Z" stroke="black" stroke-opacity="0.12"/>
|
||||||
|
<mask id="mask2_4744_39984" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="88" y="72" width="25" height="24">
|
||||||
|
<path d="M100.5 84H107.5C106.97 88.11 104.22 91.78 100.5 92.92V84H93.5V78.3L100.5 75.19M100.5 73L91.5 77V83C91.5 88.55 95.34 93.73 100.5 95C105.66 93.73 109.5 88.55 109.5 83V77L100.5 73Z" fill="black"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask2_4744_39984)">
|
||||||
|
<rect x="88.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
||||||
|
</g>
|
||||||
|
<path d="M123 72C123 67.5817 126.582 64 131 64H152C156.418 64 160 67.5817 160 72V96C160 100.418 156.418 104 152 104H131C126.582 104 123 100.418 123 96V72Z" fill="white"/>
|
||||||
|
<path d="M131 64.5H152C156.142 64.5 159.5 67.8579 159.5 72V96C159.5 100.142 156.142 103.5 152 103.5H131C126.858 103.5 123.5 100.142 123.5 96V72C123.5 67.8579 126.858 64.5 131 64.5Z" stroke="black" stroke-opacity="0.12"/>
|
||||||
|
<mask id="mask3_4744_39984" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="129" y="72" width="25" height="24">
|
||||||
|
<path d="M145.5 84C145.5 83.4696 145.711 82.9609 146.086 82.5858C146.461 82.2107 146.97 82 147.5 82C148.03 82 148.539 82.2107 148.914 82.5858C149.289 82.9609 149.5 83.4696 149.5 84C149.5 84.5304 149.289 85.0391 148.914 85.4142C148.539 85.7893 148.03 86 147.5 86C146.97 86 146.461 85.7893 146.086 85.4142C145.711 85.0391 145.5 84.5304 145.5 84ZM139.5 84C139.5 83.4696 139.711 82.9609 140.086 82.5858C140.461 82.2107 140.97 82 141.5 82C142.03 82 142.539 82.2107 142.914 82.5858C143.289 82.9609 143.5 83.4696 143.5 84C143.5 84.5304 143.289 85.0391 142.914 85.4142C142.539 85.7893 142.03 86 141.5 86C140.97 86 140.461 85.7893 140.086 85.4142C139.711 85.0391 139.5 84.5304 139.5 84ZM133.5 84C133.5 83.4696 133.711 82.9609 134.086 82.5858C134.461 82.2107 134.97 82 135.5 82C136.03 82 136.539 82.2107 136.914 82.5858C137.289 82.9609 137.5 83.4696 137.5 84C137.5 84.5304 137.289 85.0391 136.914 85.4142C136.539 85.7893 136.03 86 135.5 86C134.97 86 134.461 85.7893 134.086 85.4142C133.711 85.0391 133.5 84.5304 133.5 84Z" fill="black"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask3_4744_39984)">
|
||||||
|
<rect x="129.5" y="72" width="24" height="24" fill="#18BCF2"/>
|
||||||
|
</g>
|
||||||
|
<path d="M0 116C0 113.791 1.79086 112 4 112H29C31.2091 112 33 113.791 33 116C33 118.209 31.2091 120 29 120H4C1.79086 120 0 118.209 0 116Z" fill="black" fill-opacity="0.32"/>
|
||||||
|
<path d="M0 132C0 127.582 3.58172 124 8 124H70C74.4183 124 78 127.582 78 132V160H0V132Z" fill="url(#paint0_linear_4744_39984)"/>
|
||||||
|
<path d="M8 124.5H70C74.1421 124.5 77.5 127.858 77.5 132V159.5H0.5V132C0.5 127.858 3.85786 124.5 8 124.5Z" stroke="url(#paint1_linear_4744_39984)" stroke-opacity="0.12"/>
|
||||||
|
<path d="M82 132C82 127.582 85.5817 124 90 124H152C156.418 124 160 127.582 160 132V160H82V132Z" fill="url(#paint2_linear_4744_39984)"/>
|
||||||
|
<path d="M90 124.5H152C156.142 124.5 159.5 127.858 159.5 132V159.5H82.5V132C82.5 127.858 85.8579 124.5 90 124.5Z" stroke="url(#paint3_linear_4744_39984)" stroke-opacity="0.12"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_4744_39984" x1="39" y1="124" x2="39" y2="160" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.5" stop-color="white"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear_4744_39984" x1="39" y1="124" x2="39" y2="160" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.5" stop-opacity="0.12"/>
|
||||||
|
<stop offset="1" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint2_linear_4744_39984" x1="121" y1="124" x2="121" y2="160" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.5" stop-color="white"/>
|
||||||
|
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint3_linear_4744_39984" x1="121" y1="124" x2="121" y2="160" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.5" stop-opacity="0.12"/>
|
||||||
|
<stop offset="1" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="clip0_4744_39984">
|
||||||
|
<rect width="160" height="160" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20250811.0"
|
version = "20250924.0"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
license-files = ["LICENSE*"]
|
license-files = ["LICENSE*"]
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
|
@@ -237,10 +237,11 @@ export class HaAuthFlow extends LitElement {
|
|||||||
@value-changed=${this._stepDataChanged}
|
@value-changed=${this._stepDataChanged}
|
||||||
></ha-auth-form>`
|
></ha-auth-form>`
|
||||||
)}
|
)}
|
||||||
${this.clientId === genClientId() &&
|
|
||||||
!["select_mfa_module", "mfa"].includes(step.step_id)
|
<div class="space-between">
|
||||||
? html`
|
${this.clientId === genClientId() &&
|
||||||
<div class="space-between">
|
!["select_mfa_module", "mfa"].includes(step.step_id)
|
||||||
|
? html`
|
||||||
<ha-formfield
|
<ha-formfield
|
||||||
class="store-token"
|
class="store-token"
|
||||||
.label=${this.localize(
|
.label=${this.localize(
|
||||||
@@ -252,18 +253,16 @@ export class HaAuthFlow extends LitElement {
|
|||||||
@change=${this._storeTokenChanged}
|
@change=${this._storeTokenChanged}
|
||||||
></ha-checkbox>
|
></ha-checkbox>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
<a
|
`
|
||||||
class="forgot-password"
|
: ""}
|
||||||
href="https://www.home-assistant.io/docs/locked_out/#forgot-password"
|
<a
|
||||||
target="_blank"
|
class="forgot-password"
|
||||||
rel="noreferrer noopener"
|
href="https://www.home-assistant.io/docs/locked_out/#forgot-password"
|
||||||
>${this.localize(
|
target="_blank"
|
||||||
"ui.panel.page-authorize.forgot_password"
|
rel="noreferrer noopener"
|
||||||
)}</a
|
>${this.localize("ui.panel.page-authorize.forgot_password")}</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
`;
|
`;
|
||||||
default:
|
default:
|
||||||
return nothing;
|
return nothing;
|
||||||
|
@@ -1,23 +1,40 @@
|
|||||||
|
import { formatHex, parse } from "culori";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expands a 3-digit hex color to a 6-digit hex color.
|
||||||
|
* @param hex - The hex color to expand.
|
||||||
|
* @returns The expanded hex color.
|
||||||
|
* @throws If the hex color is invalid.
|
||||||
|
*/
|
||||||
export const expandHex = (hex: string): string => {
|
export const expandHex = (hex: string): string => {
|
||||||
hex = hex.replace("#", "");
|
const color = parse(hex);
|
||||||
if (hex.length === 6) return hex;
|
if (!color) {
|
||||||
let result = "";
|
throw new Error(`Invalid hex color: ${hex}`);
|
||||||
for (const val of hex) {
|
|
||||||
result += val + val;
|
|
||||||
}
|
}
|
||||||
return result;
|
const formattedColor = formatHex(color);
|
||||||
|
if (!formattedColor) {
|
||||||
|
throw new Error(`Could not format hex color: ${hex}`);
|
||||||
|
}
|
||||||
|
return formattedColor.replace("#", "");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Blend 2 hex colors: c1 is placed over c2, blend is c1's opacity.
|
/**
|
||||||
|
* Blends two hex colors. c1 is placed over c2, blend is c1's opacity.
|
||||||
|
* @param c1 - The first hex color.
|
||||||
|
* @param c2 - The second hex color.
|
||||||
|
* @param blend - The blend percentage (0-100).
|
||||||
|
* @returns The blended hex color.
|
||||||
|
*/
|
||||||
export const hexBlend = (c1: string, c2: string, blend = 50): string => {
|
export const hexBlend = (c1: string, c2: string, blend = 50): string => {
|
||||||
let color = "";
|
|
||||||
c1 = expandHex(c1);
|
c1 = expandHex(c1);
|
||||||
c2 = expandHex(c2);
|
c2 = expandHex(c2);
|
||||||
|
let color = "";
|
||||||
for (let i = 0; i <= 5; i += 2) {
|
for (let i = 0; i <= 5; i += 2) {
|
||||||
const h1 = parseInt(c1.substring(i, i + 2), 16);
|
const h1 = parseInt(c1.substring(i, i + 2), 16);
|
||||||
const h2 = parseInt(c2.substring(i, i + 2), 16);
|
const h2 = parseInt(c2.substring(i, i + 2), 16);
|
||||||
let hex = Math.floor(h2 + (h1 - h2) * (blend / 100)).toString(16);
|
const hex = Math.floor(h2 + (h1 - h2) * (blend / 100))
|
||||||
while (hex.length < 2) hex = "0" + hex;
|
.toString(16)
|
||||||
|
.padStart(2, "0");
|
||||||
color += hex;
|
color += hex;
|
||||||
}
|
}
|
||||||
return `#${color}`;
|
return `#${color}`;
|
||||||
|
@@ -1,28 +1,49 @@
|
|||||||
export const luminosity = (rgb: [number, number, number]): number => {
|
import { wcagLuminance, wcagContrast } from "culori";
|
||||||
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
|
|
||||||
const lum: [number, number, number] = [0, 0, 0];
|
|
||||||
for (let i = 0; i < rgb.length; i++) {
|
|
||||||
const chan = rgb[i] / 255;
|
|
||||||
lum[i] = chan <= 0.03928 ? chan / 12.92 : ((chan + 0.055) / 1.055) ** 2.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
|
/**
|
||||||
};
|
* Calculates the luminosity of an RGB color.
|
||||||
|
* @param rgb - The RGB color to calculate the luminosity of.
|
||||||
|
* @returns The luminosity of the color.
|
||||||
|
*/
|
||||||
|
export const luminosity = (rgb: [number, number, number]): number =>
|
||||||
|
wcagLuminance({
|
||||||
|
mode: "rgb",
|
||||||
|
r: rgb[0] / 255,
|
||||||
|
g: rgb[1] / 255,
|
||||||
|
b: rgb[2] / 255,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the contrast ratio between two RGB colors.
|
||||||
|
* @param color1 - The first color to calculate the contrast ratio of.
|
||||||
|
* @param color2 - The second color to calculate the contrast ratio of.
|
||||||
|
* @returns The contrast ratio between the two colors.
|
||||||
|
*/
|
||||||
export const rgbContrast = (
|
export const rgbContrast = (
|
||||||
color1: [number, number, number],
|
color1: [number, number, number],
|
||||||
color2: [number, number, number]
|
color2: [number, number, number]
|
||||||
) => {
|
) =>
|
||||||
const lum1 = luminosity(color1);
|
wcagContrast(
|
||||||
const lum2 = luminosity(color2);
|
{
|
||||||
|
mode: "rgb",
|
||||||
if (lum1 > lum2) {
|
r: color1[0] / 255,
|
||||||
return (lum1 + 0.05) / (lum2 + 0.05);
|
g: color1[1] / 255,
|
||||||
}
|
b: color1[2] / 255,
|
||||||
|
},
|
||||||
return (lum2 + 0.05) / (lum1 + 0.05);
|
{
|
||||||
};
|
mode: "rgb",
|
||||||
|
r: color2[0] / 255,
|
||||||
|
g: color2[1] / 255,
|
||||||
|
b: color2[2] / 255,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the contrast ratio between two RGB colors.
|
||||||
|
* @param rgb1 - The first color to calculate the contrast ratio of.
|
||||||
|
* @param rgb2 - The second color to calculate the contrast ratio of.
|
||||||
|
* @returns The contrast ratio between the two colors.
|
||||||
|
*/
|
||||||
export const getRGBContrastRatio = (
|
export const getRGBContrastRatio = (
|
||||||
rgb1: [number, number, number],
|
rgb1: [number, number, number],
|
||||||
rgb2: [number, number, number]
|
rgb2: [number, number, number]
|
||||||
|
@@ -11,7 +11,7 @@ import {
|
|||||||
differenceInDays,
|
differenceInDays,
|
||||||
addDays,
|
addDays,
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import { toZonedTime, fromZonedTime } from "date-fns-tz";
|
import { TZDate } from "@date-fns/tz";
|
||||||
import type { HassConfig } from "home-assistant-js-websocket";
|
import type { HassConfig } from "home-assistant-js-websocket";
|
||||||
import type { FrontendLocaleData } from "../../data/translation";
|
import type { FrontendLocaleData } from "../../data/translation";
|
||||||
import { TimeZone } from "../../data/translation";
|
import { TimeZone } from "../../data/translation";
|
||||||
@@ -22,12 +22,13 @@ const calcZonedDate = (
|
|||||||
fn: (date: Date, options?: any) => Date | number | boolean,
|
fn: (date: Date, options?: any) => Date | number | boolean,
|
||||||
options?
|
options?
|
||||||
) => {
|
) => {
|
||||||
const inputZoned = toZonedTime(date, tz);
|
const tzDate = new TZDate(date, tz);
|
||||||
const fnZoned = fn(inputZoned, options);
|
const fnResult = fn(tzDate, options);
|
||||||
if (fnZoned instanceof Date) {
|
if (fnResult instanceof Date) {
|
||||||
return fromZonedTime(fnZoned, tz) as Date;
|
// Convert back to regular Date in the specified timezone
|
||||||
|
return new Date(fnResult.getTime());
|
||||||
}
|
}
|
||||||
return fnZoned;
|
return fnResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const calcDate = (
|
export const calcDate = (
|
||||||
@@ -65,7 +66,7 @@ export const calcDateDifferenceProperty = (
|
|||||||
locale,
|
locale,
|
||||||
config,
|
config,
|
||||||
locale.time_zone === TimeZone.server
|
locale.time_zone === TimeZone.server
|
||||||
? toZonedTime(startDate, config.time_zone)
|
? new TZDate(startDate, config.time_zone)
|
||||||
: startDate
|
: startDate
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -132,17 +133,48 @@ export const shiftDateRange = (
|
|||||||
end = calcDate(endDate, addDays, locale, config, difference);
|
end = calcDate(endDate, addDays, locale, config, difference);
|
||||||
} else {
|
} else {
|
||||||
const difference =
|
const difference =
|
||||||
((calcDateDifferenceProperty(
|
(calcDateDifferenceProperty(
|
||||||
endDate,
|
endDate,
|
||||||
startDate,
|
startDate,
|
||||||
differenceInMilliseconds,
|
differenceInMilliseconds,
|
||||||
locale,
|
locale,
|
||||||
config
|
config
|
||||||
) as number) +
|
) as number) * (forward ? 1 : -1);
|
||||||
1) *
|
|
||||||
(forward ? 1 : -1);
|
|
||||||
start = calcDate(startDate, addMilliseconds, locale, config, difference);
|
start = calcDate(startDate, addMilliseconds, locale, config, difference);
|
||||||
end = calcDate(endDate, addMilliseconds, locale, config, difference);
|
end = calcDate(endDate, addMilliseconds, locale, config, difference);
|
||||||
}
|
}
|
||||||
return { start, end };
|
return { start, end };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Parses a date in browser display timezone
|
||||||
|
* @param date - The date to parse
|
||||||
|
* @param timezone - The timezone to parse the date in
|
||||||
|
* @returns The parsed date as a Date object
|
||||||
|
*/
|
||||||
|
export const parseDate = (date: string, timezone: string): Date => {
|
||||||
|
const tzDate = new TZDate(date, timezone);
|
||||||
|
return new Date(tzDate.getTime());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Formats a date in browser display timezone
|
||||||
|
* @param date - The date to format
|
||||||
|
* @param timezone - The timezone to format the date in
|
||||||
|
* @returns The formatted date in YYYY-MM-DD format
|
||||||
|
*/
|
||||||
|
export const formatDate = (date: Date, timezone: string): string => {
|
||||||
|
const tzDate = new TZDate(date, timezone);
|
||||||
|
return tzDate.toISOString().split("T")[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Formats a time in browser display timezone
|
||||||
|
* @param date - The date to format
|
||||||
|
* @param timezone - The timezone to format the time in
|
||||||
|
* @returns The formatted time in HH:mm:ss format
|
||||||
|
*/
|
||||||
|
export const formatTime = (date: Date, timezone: string): string => {
|
||||||
|
const tzDate = new TZDate(date, timezone);
|
||||||
|
return tzDate.toISOString().split("T")[1].split(".")[0];
|
||||||
|
};
|
||||||
|
4
src/common/dom/prevent_default_stop_propagation.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export const preventDefaultStopPropagation = (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
};
|
@@ -10,9 +10,10 @@ import { stripPrefixFromEntityName } from "./strip_prefix_from_entity_name";
|
|||||||
|
|
||||||
export const computeEntityName = (
|
export const computeEntityName = (
|
||||||
stateObj: HassEntity,
|
stateObj: HassEntity,
|
||||||
hass: HomeAssistant
|
entities: HomeAssistant["entities"],
|
||||||
|
devices: HomeAssistant["devices"]
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
const entry = hass.entities[stateObj.entity_id] as
|
const entry = entities[stateObj.entity_id] as
|
||||||
| EntityRegistryDisplayEntry
|
| EntityRegistryDisplayEntry
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
@@ -20,25 +21,28 @@ export const computeEntityName = (
|
|||||||
// Fall back to state name if not in the entity registry (friendly name)
|
// Fall back to state name if not in the entity registry (friendly name)
|
||||||
return computeStateName(stateObj);
|
return computeStateName(stateObj);
|
||||||
}
|
}
|
||||||
return computeEntityEntryName(entry, hass);
|
return computeEntityEntryName(entry, devices);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const computeEntityEntryName = (
|
export const computeEntityEntryName = (
|
||||||
entry: EntityRegistryDisplayEntry | EntityRegistryEntry,
|
entry: EntityRegistryDisplayEntry | EntityRegistryEntry,
|
||||||
hass: HomeAssistant
|
devices: HomeAssistant["devices"],
|
||||||
|
fallbackStateObj?: HassEntity
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
const name =
|
const name =
|
||||||
entry.name || ("original_name" in entry ? entry.original_name : undefined);
|
entry.name ||
|
||||||
|
("original_name" in entry && entry.original_name != null
|
||||||
|
? String(entry.original_name)
|
||||||
|
: undefined);
|
||||||
|
|
||||||
const device = entry.device_id ? hass.devices[entry.device_id] : undefined;
|
const device = entry.device_id ? devices[entry.device_id] : undefined;
|
||||||
|
|
||||||
if (!device) {
|
if (!device) {
|
||||||
if (name) {
|
if (name) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
const stateObj = hass.states[entry.entity_id] as HassEntity | undefined;
|
if (fallbackStateObj) {
|
||||||
if (stateObj) {
|
return computeStateName(fallbackStateObj);
|
||||||
return computeStateName(stateObj);
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@@ -18,9 +18,12 @@ interface EntityContext {
|
|||||||
|
|
||||||
export const getEntityContext = (
|
export const getEntityContext = (
|
||||||
stateObj: HassEntity,
|
stateObj: HassEntity,
|
||||||
hass: HomeAssistant
|
entities: HomeAssistant["entities"],
|
||||||
|
devices: HomeAssistant["devices"],
|
||||||
|
areas: HomeAssistant["areas"],
|
||||||
|
floors: HomeAssistant["floors"]
|
||||||
): EntityContext => {
|
): EntityContext => {
|
||||||
const entry = hass.entities[stateObj.entity_id] as
|
const entry = entities[stateObj.entity_id] as
|
||||||
| EntityRegistryDisplayEntry
|
| EntityRegistryDisplayEntry
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
@@ -32,7 +35,7 @@ export const getEntityContext = (
|
|||||||
floor: null,
|
floor: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return getEntityEntryContext(entry, hass);
|
return getEntityEntryContext(entry, entities, devices, areas, floors);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEntityEntryContext = (
|
export const getEntityEntryContext = (
|
||||||
@@ -40,15 +43,18 @@ export const getEntityEntryContext = (
|
|||||||
| EntityRegistryDisplayEntry
|
| EntityRegistryDisplayEntry
|
||||||
| EntityRegistryEntry
|
| EntityRegistryEntry
|
||||||
| ExtEntityRegistryEntry,
|
| ExtEntityRegistryEntry,
|
||||||
hass: HomeAssistant
|
entities: HomeAssistant["entities"],
|
||||||
|
devices: HomeAssistant["devices"],
|
||||||
|
areas: HomeAssistant["areas"],
|
||||||
|
floors: HomeAssistant["floors"]
|
||||||
): EntityContext => {
|
): EntityContext => {
|
||||||
const entity = hass.entities[entry.entity_id];
|
const entity = entities[entry.entity_id];
|
||||||
const deviceId = entry?.device_id;
|
const deviceId = entry?.device_id;
|
||||||
const device = deviceId ? hass.devices[deviceId] : undefined;
|
const device = deviceId ? devices[deviceId] : undefined;
|
||||||
const areaId = entry?.area_id || device?.area_id;
|
const areaId = entry?.area_id || device?.area_id;
|
||||||
const area = areaId ? hass.areas[areaId] : undefined;
|
const area = areaId ? areas[areaId] : undefined;
|
||||||
const floorId = area?.floor_id;
|
const floorId = area?.floor_id;
|
||||||
const floor = floorId ? hass.floors[floorId] : undefined;
|
const floor = floorId ? floors[floorId] : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
entity: entity,
|
entity: entity,
|
||||||
|
@@ -60,17 +60,20 @@ export const generateEntityFilter = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { area, floor, device, entity } = getEntityContext(stateObj, hass);
|
const { area, floor, device, entity } = getEntityContext(
|
||||||
|
stateObj,
|
||||||
|
hass.entities,
|
||||||
|
hass.devices,
|
||||||
|
hass.areas,
|
||||||
|
hass.floors
|
||||||
|
);
|
||||||
|
|
||||||
if (entity && entity.hidden) {
|
if (entity && entity.hidden) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (floors) {
|
if (floors) {
|
||||||
if (!floor) {
|
if (!floor || !floors.has(floor.floor_id)) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!floors) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ export const FIXED_DOMAIN_STATES = {
|
|||||||
"pending",
|
"pending",
|
||||||
"triggered",
|
"triggered",
|
||||||
],
|
],
|
||||||
|
alert: ["on", "off", "idle"],
|
||||||
assist_satellite: ["idle", "listening", "responding", "processing"],
|
assist_satellite: ["idle", "listening", "responding", "processing"],
|
||||||
automation: ["on", "off"],
|
automation: ["on", "off"],
|
||||||
binary_sensor: ["on", "off"],
|
binary_sensor: ["on", "off"],
|
||||||
|
@@ -40,6 +40,7 @@ const STATE_COLORED_DOMAIN = new Set([
|
|||||||
"vacuum",
|
"vacuum",
|
||||||
"valve",
|
"valve",
|
||||||
"water_heater",
|
"water_heater",
|
||||||
|
"weather",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const stateColorCss = (stateObj: HassEntity, state?: string) => {
|
export const stateColorCss = (stateObj: HassEntity, state?: string) => {
|
||||||
|
@@ -63,3 +63,21 @@ export const navigate = async (
|
|||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate back in history, with fallback to a default path if no history exists.
|
||||||
|
* This prevents a user from getting stuck when they navigate directly to a page with no history.
|
||||||
|
*/
|
||||||
|
export const goBack = (fallbackPath?: string) => {
|
||||||
|
const { history } = mainWindow;
|
||||||
|
|
||||||
|
// Check if we have history to go back to
|
||||||
|
if (history.length > 1) {
|
||||||
|
history.back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No history available, navigate to fallback path
|
||||||
|
const fallback = fallbackPath || "/";
|
||||||
|
navigate(fallback, { replace: true });
|
||||||
|
};
|
||||||
|
@@ -32,6 +32,8 @@ export const numberFormatToLocale = (
|
|||||||
return ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
|
return ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
|
||||||
case NumberFormat.space_comma:
|
case NumberFormat.space_comma:
|
||||||
return ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
|
return ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
|
||||||
|
case NumberFormat.quote_decimal:
|
||||||
|
return ["de-CH"]; // Use German (Switzerland) formatting 1'234'567.89
|
||||||
case NumberFormat.system:
|
case NumberFormat.system:
|
||||||
return undefined;
|
return undefined;
|
||||||
default:
|
default:
|
||||||
|
@@ -2,6 +2,12 @@ import type { HassConfig, HassEntity } from "home-assistant-js-websocket";
|
|||||||
import type { FrontendLocaleData } from "../../data/translation";
|
import type { FrontendLocaleData } from "../../data/translation";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import type { LocalizeFunc } from "./localize";
|
import type { LocalizeFunc } from "./localize";
|
||||||
|
import { computeEntityName } from "../entity/compute_entity_name";
|
||||||
|
import { computeDeviceName } from "../entity/compute_device_name";
|
||||||
|
import { getEntityContext } from "../entity/context/get_entity_context";
|
||||||
|
import { computeAreaName } from "../entity/compute_area_name";
|
||||||
|
import { computeFloorName } from "../entity/compute_floor_name";
|
||||||
|
import { ensureArray } from "../array/ensure-array";
|
||||||
|
|
||||||
export type FormatEntityStateFunc = (
|
export type FormatEntityStateFunc = (
|
||||||
stateObj: HassEntity,
|
stateObj: HassEntity,
|
||||||
@@ -17,16 +23,28 @@ export type FormatEntityAttributeNameFunc = (
|
|||||||
attribute: string
|
attribute: string
|
||||||
) => string;
|
) => string;
|
||||||
|
|
||||||
|
export type EntityNameType = "entity" | "device" | "area" | "floor";
|
||||||
|
|
||||||
|
export type FormatEntityNameFunc = (
|
||||||
|
stateObj: HassEntity,
|
||||||
|
type: EntityNameType | EntityNameType[],
|
||||||
|
separator?: string
|
||||||
|
) => string;
|
||||||
|
|
||||||
export const computeFormatFunctions = async (
|
export const computeFormatFunctions = async (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
locale: FrontendLocaleData,
|
locale: FrontendLocaleData,
|
||||||
config: HassConfig,
|
config: HassConfig,
|
||||||
entities: HomeAssistant["entities"],
|
entities: HomeAssistant["entities"],
|
||||||
|
devices: HomeAssistant["devices"],
|
||||||
|
areas: HomeAssistant["areas"],
|
||||||
|
floors: HomeAssistant["floors"],
|
||||||
sensorNumericDeviceClasses: string[]
|
sensorNumericDeviceClasses: string[]
|
||||||
): Promise<{
|
): Promise<{
|
||||||
formatEntityState: FormatEntityStateFunc;
|
formatEntityState: FormatEntityStateFunc;
|
||||||
formatEntityAttributeValue: FormatEntityAttributeValueFunc;
|
formatEntityAttributeValue: FormatEntityAttributeValueFunc;
|
||||||
formatEntityAttributeName: FormatEntityAttributeNameFunc;
|
formatEntityAttributeName: FormatEntityAttributeNameFunc;
|
||||||
|
formatEntityName: FormatEntityNameFunc;
|
||||||
}> => {
|
}> => {
|
||||||
const { computeStateDisplay } = await import(
|
const { computeStateDisplay } = await import(
|
||||||
"../entity/compute_state_display"
|
"../entity/compute_state_display"
|
||||||
@@ -57,5 +75,45 @@ export const computeFormatFunctions = async (
|
|||||||
),
|
),
|
||||||
formatEntityAttributeName: (stateObj, attribute) =>
|
formatEntityAttributeName: (stateObj, attribute) =>
|
||||||
computeAttributeNameDisplay(localize, stateObj, entities, attribute),
|
computeAttributeNameDisplay(localize, stateObj, entities, attribute),
|
||||||
|
formatEntityName: (stateObj, type, separator = " ") => {
|
||||||
|
const types = ensureArray(type);
|
||||||
|
const namesList: (string | undefined)[] = [];
|
||||||
|
|
||||||
|
const { device, area, floor } = getEntityContext(
|
||||||
|
stateObj,
|
||||||
|
entities,
|
||||||
|
devices,
|
||||||
|
areas,
|
||||||
|
floors
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const t of types) {
|
||||||
|
switch (t) {
|
||||||
|
case "entity": {
|
||||||
|
namesList.push(computeEntityName(stateObj, entities, devices));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "device": {
|
||||||
|
if (device) {
|
||||||
|
namesList.push(computeDeviceName(device));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "area": {
|
||||||
|
if (area) {
|
||||||
|
namesList.push(computeAreaName(area));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "floor": {
|
||||||
|
if (floor) {
|
||||||
|
namesList.push(computeFloorName(floor));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return namesList.filter((name) => name !== undefined).join(separator);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -14,6 +14,7 @@ export type LocalizeKeys =
|
|||||||
| `ui.card.weather.attributes.${string}`
|
| `ui.card.weather.attributes.${string}`
|
||||||
| `ui.card.weather.cardinal_direction.${string}`
|
| `ui.card.weather.cardinal_direction.${string}`
|
||||||
| `ui.card.lawn_mower.actions.${string}`
|
| `ui.card.lawn_mower.actions.${string}`
|
||||||
|
| `ui.common.${string}`
|
||||||
| `ui.components.calendar.event.rrule.${string}`
|
| `ui.components.calendar.event.rrule.${string}`
|
||||||
| `ui.components.selectors.file.${string}`
|
| `ui.components.selectors.file.${string}`
|
||||||
| `ui.components.logbook.messages.detected_device_classes.${string}`
|
| `ui.components.logbook.messages.detected_device_classes.${string}`
|
||||||
@@ -26,6 +27,7 @@ export type LocalizeKeys =
|
|||||||
| `ui.dialogs.unsupported.reasons.${string}`
|
| `ui.dialogs.unsupported.reasons.${string}`
|
||||||
| `ui.panel.config.${string}.${"caption" | "description"}`
|
| `ui.panel.config.${string}.${"caption" | "description"}`
|
||||||
| `ui.panel.config.dashboard.${string}`
|
| `ui.panel.config.dashboard.${string}`
|
||||||
|
| `ui.panel.config.storage.segments.${string}`
|
||||||
| `ui.panel.config.zha.${string}`
|
| `ui.panel.config.zha.${string}`
|
||||||
| `ui.panel.config.zwave_js.${string}`
|
| `ui.panel.config.zwave_js.${string}`
|
||||||
| `ui.panel.lovelace.card.${string}`
|
| `ui.panel.lovelace.card.${string}`
|
||||||
|
18
src/common/util/order-properties.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Orders object properties according to a specified key order.
|
||||||
|
* Properties not in the order array will be placed at the end.
|
||||||
|
*/
|
||||||
|
export function orderProperties<T extends Record<string, any>>(
|
||||||
|
obj: T,
|
||||||
|
keys: readonly string[]
|
||||||
|
): T {
|
||||||
|
const orderedEntries = keys
|
||||||
|
.filter((key) => key in obj)
|
||||||
|
.map((key) => [key, obj[key]] as const);
|
||||||
|
|
||||||
|
const extraEntries = Object.entries(obj).filter(
|
||||||
|
([key]) => !keys.includes(key)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Object.fromEntries([...orderedEntries, ...extraEntries]) as T;
|
||||||
|
}
|
@@ -39,6 +39,7 @@ export type CustomLegendOption = ECOption["legend"] & {
|
|||||||
type: "custom";
|
type: "custom";
|
||||||
data?: {
|
data?: {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
secondaryIds?: string[]; // Other dataset IDs that should be controlled by this legend item.
|
||||||
name: string;
|
name: string;
|
||||||
itemStyle?: Record<string, any>;
|
itemStyle?: Record<string, any>;
|
||||||
}[];
|
}[];
|
||||||
@@ -62,6 +63,9 @@ export class HaChartBase extends LitElement {
|
|||||||
@property({ attribute: "small-controls", type: Boolean })
|
@property({ attribute: "small-controls", type: Boolean })
|
||||||
public smallControls?: boolean;
|
public smallControls?: boolean;
|
||||||
|
|
||||||
|
@property({ attribute: "hide-reset-button", type: Boolean })
|
||||||
|
public hideResetButton?: boolean;
|
||||||
|
|
||||||
// extraComponents is not reactive and should not trigger updates
|
// extraComponents is not reactive and should not trigger updates
|
||||||
public extraComponents?: any[];
|
public extraComponents?: any[];
|
||||||
|
|
||||||
@@ -181,6 +185,10 @@ export class HaChartBase extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let chartOptions: ECOption = {};
|
let chartOptions: ECOption = {};
|
||||||
|
if (changedProps.has("options")) {
|
||||||
|
// Separate 'if' from below since this must updated before _getSeries()
|
||||||
|
this._updateHiddenStatsFromOptions(this.options);
|
||||||
|
}
|
||||||
if (changedProps.has("data") || changedProps.has("_hiddenDatasets")) {
|
if (changedProps.has("data") || changedProps.has("_hiddenDatasets")) {
|
||||||
chartOptions.series = this._getSeries();
|
chartOptions.series = this._getSeries();
|
||||||
}
|
}
|
||||||
@@ -210,7 +218,7 @@ export class HaChartBase extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
${this._renderLegend()}
|
${this._renderLegend()}
|
||||||
<div class="chart-controls ${classMap({ small: this.smallControls })}">
|
<div class="chart-controls ${classMap({ small: this.smallControls })}">
|
||||||
${this._isZoomed
|
${this._isZoomed && !this.hideResetButton
|
||||||
? html`<ha-icon-button
|
? html`<ha-icon-button
|
||||||
class="zoom-reset"
|
class="zoom-reset"
|
||||||
.path=${mdiRestart}
|
.path=${mdiRestart}
|
||||||
@@ -348,20 +356,12 @@ export class HaChartBase extends LitElement {
|
|||||||
|
|
||||||
this.chart = echarts.init(container, "custom");
|
this.chart = echarts.init(container, "custom");
|
||||||
this.chart.on("datazoom", (e: any) => {
|
this.chart.on("datazoom", (e: any) => {
|
||||||
const { start, end } = e.batch?.[0] ?? e;
|
this._handleDataZoomEvent(e);
|
||||||
this._isZoomed = start !== 0 || end !== 100;
|
|
||||||
this._zoomRatio = (end - start) / 100;
|
|
||||||
if (this._isTouchDevice) {
|
|
||||||
// zooming changes the axis pointer so we need to hide it
|
|
||||||
this.chart?.dispatchAction({
|
|
||||||
type: "hideTip",
|
|
||||||
from: "datazoom",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
this.chart.on("click", (e: ECElementEvent) => {
|
this.chart.on("click", (e: ECElementEvent) => {
|
||||||
fireEvent(this, "chart-click", e);
|
fireEvent(this, "chart-click", e);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.options?.dataZoom) {
|
if (!this.options?.dataZoom) {
|
||||||
this.chart.getZr().on("dblclick", this._handleClickZoom);
|
this.chart.getZr().on("dblclick", this._handleClickZoom);
|
||||||
}
|
}
|
||||||
@@ -397,7 +397,7 @@ export class HaChartBase extends LitElement {
|
|||||||
...axis.axisPointer,
|
...axis.axisPointer,
|
||||||
status: "show",
|
status: "show",
|
||||||
handle: {
|
handle: {
|
||||||
color: style.getPropertyValue("primary-color"),
|
color: style.getPropertyValue("--primary-color"),
|
||||||
margin: 0,
|
margin: 0,
|
||||||
size: 20,
|
size: 20,
|
||||||
...axis.axisPointer?.handle,
|
...axis.axisPointer?.handle,
|
||||||
@@ -451,14 +451,7 @@ export class HaChartBase extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const legend = ensureArray(this.options?.legend || [])[0] as
|
this._updateHiddenStatsFromOptions(this.options);
|
||||||
| LegendComponentOption
|
|
||||||
| undefined;
|
|
||||||
Object.entries(legend?.selected || {}).forEach(([stat, selected]) => {
|
|
||||||
if (selected === false) {
|
|
||||||
this._hiddenDatasets.add(stat);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.chart.setOption({
|
this.chart.setOption({
|
||||||
...this._createOptions(),
|
...this._createOptions(),
|
||||||
@@ -469,6 +462,42 @@ export class HaChartBase extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return an array of all IDs associated with the legend item of the primaryId
|
||||||
|
private _getAllIdsFromLegend(
|
||||||
|
options: ECOption | undefined,
|
||||||
|
primaryId: string
|
||||||
|
): string[] {
|
||||||
|
if (!options) return [primaryId];
|
||||||
|
const legend = ensureArray(this.options?.legend || [])[0] as
|
||||||
|
| LegendComponentOption
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
let customLegendItem;
|
||||||
|
if (legend?.type === "custom") {
|
||||||
|
customLegendItem = (legend as CustomLegendOption).data?.find(
|
||||||
|
(li) => typeof li === "object" && li.id === primaryId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [primaryId, ...(customLegendItem?.secondaryIds || [])];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses the options structure and adds all ids of unselected legend items to hiddenDatasets.
|
||||||
|
// No known need to remove items at this time.
|
||||||
|
private _updateHiddenStatsFromOptions(options: ECOption | undefined) {
|
||||||
|
if (!options) return;
|
||||||
|
const legend = ensureArray(this.options?.legend || [])[0] as
|
||||||
|
| LegendComponentOption
|
||||||
|
| undefined;
|
||||||
|
Object.entries(legend?.selected || {}).forEach(([stat, selected]) => {
|
||||||
|
if (selected === false) {
|
||||||
|
this._getAllIdsFromLegend(options, stat).forEach((id) =>
|
||||||
|
this._hiddenDatasets.add(id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _getDataZoomConfig(): DataZoomComponentOption | undefined {
|
private _getDataZoomConfig(): DataZoomComponentOption | undefined {
|
||||||
const xAxis = (this.options?.xAxis?.[0] ?? this.options?.xAxis) as
|
const xAxis = (this.options?.xAxis?.[0] ?? this.options?.xAxis) as
|
||||||
| XAXisOption
|
| XAXisOption
|
||||||
@@ -834,20 +863,74 @@ export class HaChartBase extends LitElement {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public zoom(start: number, end: number, silent = false) {
|
||||||
|
this.chart?.dispatchAction({
|
||||||
|
type: "dataZoom",
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
silent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _handleZoomReset() {
|
private _handleZoomReset() {
|
||||||
this.chart?.dispatchAction({ type: "dataZoom", start: 0, end: 100 });
|
this.chart?.dispatchAction({ type: "dataZoom", start: 0, end: 100 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleDataZoomEvent(e: any) {
|
||||||
|
const zoomData = e.batch?.[0] ?? e;
|
||||||
|
let start = typeof zoomData.start === "number" ? zoomData.start : 0;
|
||||||
|
let end = typeof zoomData.end === "number" ? zoomData.end : 100;
|
||||||
|
|
||||||
|
if (
|
||||||
|
start === 0 &&
|
||||||
|
end === 100 &&
|
||||||
|
zoomData.startValue !== undefined &&
|
||||||
|
zoomData.endValue !== undefined
|
||||||
|
) {
|
||||||
|
const option = this.chart!.getOption();
|
||||||
|
const xAxis = option.xAxis?.[0] ?? option.xAxis;
|
||||||
|
|
||||||
|
if (xAxis?.min && xAxis?.max) {
|
||||||
|
const axisMin = new Date(xAxis.min).getTime();
|
||||||
|
const axisMax = new Date(xAxis.max).getTime();
|
||||||
|
const axisRange = axisMax - axisMin;
|
||||||
|
|
||||||
|
start = Math.max(
|
||||||
|
0,
|
||||||
|
Math.min(100, ((zoomData.startValue - axisMin) / axisRange) * 100)
|
||||||
|
);
|
||||||
|
end = Math.max(
|
||||||
|
0,
|
||||||
|
Math.min(100, ((zoomData.endValue - axisMin) / axisRange) * 100)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._isZoomed = start !== 0 || end !== 100;
|
||||||
|
this._zoomRatio = (end - start) / 100;
|
||||||
|
if (this._isTouchDevice) {
|
||||||
|
this.chart?.dispatchAction({
|
||||||
|
type: "hideTip",
|
||||||
|
from: "datazoom",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fireEvent(this, "chart-zoom", { start, end });
|
||||||
|
}
|
||||||
|
|
||||||
private _legendClick(ev: any) {
|
private _legendClick(ev: any) {
|
||||||
if (!this.chart) {
|
if (!this.chart) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const id = ev.currentTarget?.id;
|
const id = ev.currentTarget?.id;
|
||||||
if (this._hiddenDatasets.has(id)) {
|
if (this._hiddenDatasets.has(id)) {
|
||||||
this._hiddenDatasets.delete(id);
|
this._getAllIdsFromLegend(this.options, id).forEach((i) =>
|
||||||
|
this._hiddenDatasets.delete(i)
|
||||||
|
);
|
||||||
fireEvent(this, "dataset-unhidden", { id });
|
fireEvent(this, "dataset-unhidden", { id });
|
||||||
} else {
|
} else {
|
||||||
this._hiddenDatasets.add(id);
|
this._getAllIdsFromLegend(this.options, id).forEach((i) =>
|
||||||
|
this._hiddenDatasets.add(i)
|
||||||
|
);
|
||||||
fireEvent(this, "dataset-hidden", { id });
|
fireEvent(this, "dataset-hidden", { id });
|
||||||
}
|
}
|
||||||
this.requestUpdate("_hiddenDatasets");
|
this.requestUpdate("_hiddenDatasets");
|
||||||
@@ -891,7 +974,7 @@ export class HaChartBase extends LitElement {
|
|||||||
right: 4px;
|
right: 4px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: var(--ha-space-1);
|
||||||
}
|
}
|
||||||
.chart-controls.small {
|
.chart-controls.small {
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -928,7 +1011,7 @@ export class HaChartBase extends LitElement {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: var(--ha-space-2);
|
||||||
}
|
}
|
||||||
.chart-legend li {
|
.chart-legend li {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@@ -986,5 +1069,9 @@ declare global {
|
|||||||
"dataset-hidden": { id: string };
|
"dataset-hidden": { id: string };
|
||||||
"dataset-unhidden": { id: string };
|
"dataset-unhidden": { id: string };
|
||||||
"chart-click": ECElementEvent;
|
"chart-click": ECElementEvent;
|
||||||
|
"chart-zoom": {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,6 @@ export interface Node {
|
|||||||
value: number;
|
value: number;
|
||||||
index: number; // like z-index but for x/y
|
index: number; // like z-index but for x/y
|
||||||
label?: string;
|
label?: string;
|
||||||
tooltip?: string;
|
|
||||||
color?: string;
|
color?: string;
|
||||||
passThrough?: boolean;
|
passThrough?: boolean;
|
||||||
}
|
}
|
||||||
|
@@ -66,6 +66,9 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
@property({ attribute: "expand-legend", type: Boolean })
|
@property({ attribute: "expand-legend", type: Boolean })
|
||||||
public expandLegend?: boolean;
|
public expandLegend?: boolean;
|
||||||
|
|
||||||
|
@property({ attribute: "hide-reset-button", type: Boolean })
|
||||||
|
public hideResetButton?: boolean;
|
||||||
|
|
||||||
@state() private _chartData: LineSeriesOption[] = [];
|
@state() private _chartData: LineSeriesOption[] = [];
|
||||||
|
|
||||||
@state() private _entityIds: string[] = [];
|
@state() private _entityIds: string[] = [];
|
||||||
@@ -94,7 +97,9 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
style=${styleMap({ height: this.height })}
|
style=${styleMap({ height: this.height })}
|
||||||
@dataset-hidden=${this._datasetHidden}
|
@dataset-hidden=${this._datasetHidden}
|
||||||
@dataset-unhidden=${this._datasetUnhidden}
|
@dataset-unhidden=${this._datasetUnhidden}
|
||||||
|
@chart-zoom=${this._handleDataZoom}
|
||||||
.expandLegend=${this.expandLegend}
|
.expandLegend=${this.expandLegend}
|
||||||
|
.hideResetButton=${this.hideResetButton}
|
||||||
></ha-chart-base>
|
></ha-chart-base>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -192,6 +197,19 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
this._hiddenStats.delete(ev.detail.id);
|
this._hiddenStats.delete(ev.detail.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public zoom(start: number, end: number) {
|
||||||
|
const chartBase = this.shadowRoot!.querySelector("ha-chart-base")!;
|
||||||
|
chartBase.zoom(start, end, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleDataZoom(ev: CustomEvent) {
|
||||||
|
fireEvent(this, "chart-zoom-with-index", {
|
||||||
|
start: ev.detail.start ?? 0,
|
||||||
|
end: ev.detail.end ?? 100,
|
||||||
|
chartIndex: this.chartIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues) {
|
public willUpdate(changedProps: PropertyValues) {
|
||||||
if (
|
if (
|
||||||
changedProps.has("data") ||
|
changedProps.has("data") ||
|
||||||
|
@@ -51,6 +51,9 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false, type: Number }) public chartIndex?;
|
@property({ attribute: false, type: Number }) public chartIndex?;
|
||||||
|
|
||||||
|
@property({ attribute: "hide-reset-button", type: Boolean })
|
||||||
|
public hideResetButton?: boolean;
|
||||||
|
|
||||||
@state() private _chartData: CustomSeriesOption[] = [];
|
@state() private _chartData: CustomSeriesOption[] = [];
|
||||||
|
|
||||||
@state() private _chartOptions?: ECOption;
|
@state() private _chartOptions?: ECOption;
|
||||||
@@ -68,6 +71,8 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||||||
.data=${this._chartData as ECOption["series"]}
|
.data=${this._chartData as ECOption["series"]}
|
||||||
small-controls
|
small-controls
|
||||||
@chart-click=${this._handleChartClick}
|
@chart-click=${this._handleChartClick}
|
||||||
|
@chart-zoom=${this._handleDataZoom}
|
||||||
|
.hideResetButton=${this.hideResetButton}
|
||||||
></ha-chart-base>
|
></ha-chart-base>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -101,7 +106,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||||||
fill: api.value(4) as string,
|
fill: api.value(4) as string,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const text = api.value(3) as string;
|
const text = (api.value(3) as string).replaceAll("\n", " ");
|
||||||
const textWidth = measureTextWidth(text, 12);
|
const textWidth = measureTextWidth(text, 12);
|
||||||
const LABEL_PADDING = 4;
|
const LABEL_PADDING = 4;
|
||||||
if (textWidth < rectShape.width - LABEL_PADDING * 2) {
|
if (textWidth < rectShape.width - LABEL_PADDING * 2) {
|
||||||
@@ -256,6 +261,19 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public zoom(start: number, end: number) {
|
||||||
|
const chartBase = this.shadowRoot!.querySelector("ha-chart-base")!;
|
||||||
|
chartBase.zoom(start, end, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleDataZoom(ev: CustomEvent) {
|
||||||
|
fireEvent(this, "chart-zoom-with-index", {
|
||||||
|
start: ev.detail.start ?? 0,
|
||||||
|
end: ev.detail.end ?? 100,
|
||||||
|
chartIndex: this.chartIndex,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _generateData() {
|
private _generateData() {
|
||||||
const computedStyles = getComputedStyle(this);
|
const computedStyles = getComputedStyle(this);
|
||||||
let stateHistory = this.data;
|
let stateHistory = this.data;
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, eventOptions, property, state } from "lit/decorators";
|
import { customElement, eventOptions, property, state } from "lit/decorators";
|
||||||
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||||
|
import { mdiRestart } from "@mdi/js";
|
||||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||||
import type {
|
import type {
|
||||||
@@ -11,6 +12,10 @@ import type {
|
|||||||
} from "../../data/history";
|
} from "../../data/history";
|
||||||
import { loadVirtualizer } from "../../resources/virtualizer";
|
import { loadVirtualizer } from "../../resources/virtualizer";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import type { StateHistoryChartLine } from "./state-history-chart-line";
|
||||||
|
import type { StateHistoryChartTimeline } from "./state-history-chart-timeline";
|
||||||
|
import "../ha-fab";
|
||||||
|
import "../ha-svg-icon";
|
||||||
import "./state-history-chart-line";
|
import "./state-history-chart-line";
|
||||||
import "./state-history-chart-timeline";
|
import "./state-history-chart-timeline";
|
||||||
|
|
||||||
@@ -29,6 +34,11 @@ const chunkData = (inputArray: any[], chunks: number) =>
|
|||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
"y-width-changed": { value: number; chartIndex: number };
|
"y-width-changed": { value: number; chartIndex: number };
|
||||||
|
"chart-zoom-with-index": {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
chartIndex: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +84,9 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
@property({ attribute: "expand-legend", type: Boolean })
|
@property({ attribute: "expand-legend", type: Boolean })
|
||||||
public expandLegend?: boolean;
|
public expandLegend?: boolean;
|
||||||
|
|
||||||
|
@property({ attribute: "sync-charts", type: Boolean })
|
||||||
|
public syncCharts = false;
|
||||||
|
|
||||||
private _computedStartTime!: Date;
|
private _computedStartTime!: Date;
|
||||||
|
|
||||||
private _computedEndTime!: Date;
|
private _computedEndTime!: Date;
|
||||||
@@ -84,6 +97,10 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
|
|
||||||
@state() private _chartCount = 0;
|
@state() private _chartCount = 0;
|
||||||
|
|
||||||
|
@state() private _hasZoomedCharts = false;
|
||||||
|
|
||||||
|
private _isSyncing = false;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@restoreScroll(".container") private _savedScrollPos?: number;
|
@restoreScroll(".container") private _savedScrollPos?: number;
|
||||||
|
|
||||||
@@ -115,19 +132,36 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
// eslint-disable-next-line lit/no-this-assign-in-render
|
// eslint-disable-next-line lit/no-this-assign-in-render
|
||||||
this._chartCount = combinedItems.length;
|
this._chartCount = combinedItems.length;
|
||||||
|
|
||||||
return this.virtualize
|
return html`
|
||||||
? html`<div class="container ha-scrollbar" @scroll=${this._saveScrollPos}>
|
${this.virtualize
|
||||||
<lit-virtualizer
|
? html`<div
|
||||||
scroller
|
class="container ha-scrollbar"
|
||||||
class="ha-scrollbar"
|
@scroll=${this._saveScrollPos}
|
||||||
.items=${combinedItems}
|
|
||||||
.renderItem=${this._renderHistoryItem}
|
|
||||||
>
|
>
|
||||||
</lit-virtualizer>
|
<lit-virtualizer
|
||||||
</div>`
|
scroller
|
||||||
: html`${combinedItems.map((item, index) =>
|
class="ha-scrollbar"
|
||||||
this._renderHistoryItem(item, index)
|
.items=${combinedItems}
|
||||||
)}`;
|
.renderItem=${this._renderHistoryItem}
|
||||||
|
>
|
||||||
|
</lit-virtualizer>
|
||||||
|
</div>`
|
||||||
|
: html`${combinedItems.map((item, index) =>
|
||||||
|
this._renderHistoryItem(item, index)
|
||||||
|
)}`}
|
||||||
|
${this.syncCharts && this._hasZoomedCharts
|
||||||
|
? html`<ha-fab
|
||||||
|
slot="fab"
|
||||||
|
class="reset-button"
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.components.history_charts.zoom_reset"
|
||||||
|
)}
|
||||||
|
@click=${this._handleGlobalZoomReset}
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="icon" .path=${mdiRestart}></ha-svg-icon>
|
||||||
|
</ha-fab>`
|
||||||
|
: nothing}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderHistoryItem: RenderItemFunction<
|
private _renderHistoryItem: RenderItemFunction<
|
||||||
@@ -156,8 +190,10 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
.maxYAxis=${this.maxYAxis}
|
.maxYAxis=${this.maxYAxis}
|
||||||
.fitYData=${this.fitYData}
|
.fitYData=${this.fitYData}
|
||||||
@y-width-changed=${this._yWidthChanged}
|
@y-width-changed=${this._yWidthChanged}
|
||||||
|
@chart-zoom-with-index=${this._handleTimelineSync}
|
||||||
.height=${this.virtualize ? undefined : this.height}
|
.height=${this.virtualize ? undefined : this.height}
|
||||||
.expandLegend=${this.expandLegend}
|
.expandLegend=${this.expandLegend}
|
||||||
|
?hide-reset-button=${this.syncCharts}
|
||||||
></state-history-chart-line>
|
></state-history-chart-line>
|
||||||
</div> `;
|
</div> `;
|
||||||
}
|
}
|
||||||
@@ -175,6 +211,8 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
.chartIndex=${index}
|
.chartIndex=${index}
|
||||||
.clickForMoreInfo=${this.clickForMoreInfo}
|
.clickForMoreInfo=${this.clickForMoreInfo}
|
||||||
@y-width-changed=${this._yWidthChanged}
|
@y-width-changed=${this._yWidthChanged}
|
||||||
|
@chart-zoom-with-index=${this._handleTimelineSync}
|
||||||
|
?hide-reset-button=${this.syncCharts}
|
||||||
></state-history-chart-timeline>
|
></state-history-chart-timeline>
|
||||||
</div> `;
|
</div> `;
|
||||||
};
|
};
|
||||||
@@ -264,6 +302,66 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
this._maxYWidth = Math.max(...Object.values(this._childYWidths), 0);
|
this._maxYWidth = Math.max(...Object.values(this._childYWidths), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleTimelineSync(
|
||||||
|
e: CustomEvent<HASSDomEvents["chart-zoom-with-index"]>
|
||||||
|
) {
|
||||||
|
if (!this.syncCharts || this._isSyncing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { start, end, chartIndex } = e.detail;
|
||||||
|
|
||||||
|
this._hasZoomedCharts = start !== 0 || end !== 100;
|
||||||
|
this._syncZoomToAllCharts(start, end, chartIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _syncZoomToAllCharts(
|
||||||
|
start: number,
|
||||||
|
end: number,
|
||||||
|
sourceChartIndex?: number
|
||||||
|
) {
|
||||||
|
this._isSyncing = true;
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const chartComponents = this.renderRoot.querySelectorAll(
|
||||||
|
"state-history-chart-line, state-history-chart-timeline"
|
||||||
|
) as unknown as (StateHistoryChartLine | StateHistoryChartTimeline)[];
|
||||||
|
|
||||||
|
chartComponents.forEach((chartComponent, index) => {
|
||||||
|
if (index === sourceChartIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("zoom" in chartComponent) {
|
||||||
|
chartComponent.zoom(start, end);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._isSyncing = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleGlobalZoomReset() {
|
||||||
|
this._hasZoomedCharts = false;
|
||||||
|
this._isSyncing = true;
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const chartComponents = this.renderRoot.querySelectorAll(
|
||||||
|
"state-history-chart-line, state-history-chart-timeline"
|
||||||
|
);
|
||||||
|
|
||||||
|
chartComponents.forEach((chartComponent: any) => {
|
||||||
|
const chartBase =
|
||||||
|
chartComponent.renderRoot?.querySelector("ha-chart-base");
|
||||||
|
|
||||||
|
if (chartBase && chartBase.chart) {
|
||||||
|
chartBase.zoom(0, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._isSyncing = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _isHistoryEmpty(): boolean {
|
private _isHistoryEmpty(): boolean {
|
||||||
const historyDataEmpty =
|
const historyDataEmpty =
|
||||||
!this.historyData ||
|
!this.historyData ||
|
||||||
@@ -345,6 +443,12 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
state-history-chart-line {
|
state-history-chart-line {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.reset-button {
|
||||||
|
position: fixed;
|
||||||
|
bottom: calc(24px + var(--safe-area-inset-bottom));
|
||||||
|
right: calc(24px + var(--safe-area-inset-bottom));
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,6 +6,8 @@ import { computeDomain } from "../../common/entity/compute_domain";
|
|||||||
import { stateColorProperties } from "../../common/entity/state_color";
|
import { stateColorProperties } from "../../common/entity/state_color";
|
||||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||||
import { computeCssValue } from "../../resources/css-variables";
|
import { computeCssValue } from "../../resources/css-variables";
|
||||||
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
|
import { FIXED_DOMAIN_STATES } from "../../common/entity/get_states";
|
||||||
|
|
||||||
const DOMAIN_STATE_SHADES: Record<string, Record<string, number>> = {
|
const DOMAIN_STATE_SHADES: Record<string, Record<string, number>> = {
|
||||||
media_player: {
|
media_player: {
|
||||||
@@ -51,6 +53,28 @@ function computeTimelineStateColor(
|
|||||||
let colorIndex = 0;
|
let colorIndex = 0;
|
||||||
const stateColorMap = new Map<string, string>();
|
const stateColorMap = new Map<string, string>();
|
||||||
|
|
||||||
|
function computeTimelineEnumColor(
|
||||||
|
state: string,
|
||||||
|
computedStyles: CSSStyleDeclaration,
|
||||||
|
stateObj?: HassEntity
|
||||||
|
): string | undefined {
|
||||||
|
if (!stateObj) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const domain = computeStateDomain(stateObj);
|
||||||
|
const states =
|
||||||
|
FIXED_DOMAIN_STATES[domain] ||
|
||||||
|
(domain === "sensor" &&
|
||||||
|
stateObj.attributes.device_class === "enum" &&
|
||||||
|
stateObj.attributes.options) ||
|
||||||
|
[];
|
||||||
|
const idx = states.indexOf(state);
|
||||||
|
if (idx === -1) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return getGraphColorByIndex(idx, computedStyles);
|
||||||
|
}
|
||||||
|
|
||||||
function computeTimeLineGenericColor(
|
function computeTimeLineGenericColor(
|
||||||
state: string,
|
state: string,
|
||||||
computedStyles: CSSStyleDeclaration
|
computedStyles: CSSStyleDeclaration
|
||||||
@@ -71,6 +95,7 @@ export function computeTimelineColor(
|
|||||||
): string {
|
): string {
|
||||||
return (
|
return (
|
||||||
computeTimelineStateColor(state, computedStyles, stateObj) ||
|
computeTimelineStateColor(state, computedStyles, stateObj) ||
|
||||||
|
computeTimelineEnumColor(state, computedStyles, stateObj) ||
|
||||||
computeTimeLineGenericColor(state, computedStyles)
|
computeTimeLineGenericColor(state, computedStyles)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -12,9 +12,8 @@ class HaDataTableIcon extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<ha-tooltip .content=${this.tooltip}>
|
<ha-tooltip for="svg-icon">${this.tooltip}</ha-tooltip>
|
||||||
<ha-svg-icon .path=${this.path}></ha-svg-icon>
|
<ha-svg-icon id="svg-icon" .path=${this.path}></ha-svg-icon>
|
||||||
</ha-tooltip>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,8 +10,8 @@ import {
|
|||||||
} from "../../data/device_automation";
|
} from "../../data/device_automation";
|
||||||
import type { EntityRegistryEntry } from "../../data/entity_registry";
|
import type { EntityRegistryEntry } from "../../data/entity_registry";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../ha-list-item";
|
import "../ha-md-select-option";
|
||||||
import "../ha-select";
|
import "../ha-md-select";
|
||||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||||
|
|
||||||
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
|
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
|
||||||
@@ -100,35 +100,35 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
}
|
}
|
||||||
const value = this._value;
|
const value = this._value;
|
||||||
return html`
|
return html`
|
||||||
<ha-select
|
<ha-md-select
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.value=${value}
|
.value=${value}
|
||||||
@selected=${this._automationChanged}
|
@change=${this._automationChanged}
|
||||||
@closed=${stopPropagation}
|
@closed=${stopPropagation}
|
||||||
.disabled=${this._automations.length === 0}
|
.disabled=${this._automations.length === 0}
|
||||||
>
|
>
|
||||||
${value === NO_AUTOMATION_KEY
|
${value === NO_AUTOMATION_KEY
|
||||||
? html`<ha-list-item .value=${NO_AUTOMATION_KEY}>
|
? html`<ha-md-select-option .value=${NO_AUTOMATION_KEY}>
|
||||||
${this.NO_AUTOMATION_TEXT}
|
${this.NO_AUTOMATION_TEXT}
|
||||||
</ha-list-item>`
|
</ha-md-select-option>`
|
||||||
: ""}
|
: nothing}
|
||||||
${value === UNKNOWN_AUTOMATION_KEY
|
${value === UNKNOWN_AUTOMATION_KEY
|
||||||
? html`<ha-list-item .value=${UNKNOWN_AUTOMATION_KEY}>
|
? html`<ha-md-select-option .value=${UNKNOWN_AUTOMATION_KEY}>
|
||||||
${this.UNKNOWN_AUTOMATION_TEXT}
|
${this.UNKNOWN_AUTOMATION_TEXT}
|
||||||
</ha-list-item>`
|
</ha-md-select-option>`
|
||||||
: ""}
|
: nothing}
|
||||||
${this._automations.map(
|
${this._automations.map(
|
||||||
(automation, idx) => html`
|
(automation, idx) => html`
|
||||||
<ha-list-item .value=${`${automation.device_id}_${idx}`}>
|
<ha-md-select-option .value=${`${automation.device_id}_${idx}`}>
|
||||||
${this._localizeDeviceAutomation(
|
${this._localizeDeviceAutomation(
|
||||||
this.hass,
|
this.hass,
|
||||||
this._entityReg,
|
this._entityReg,
|
||||||
automation
|
automation
|
||||||
)}
|
)}
|
||||||
</ha-list-item>
|
</ha-md-select-option>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</ha-select>
|
</ha-md-select>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,12 +5,8 @@ import { html, LitElement, nothing, type PropertyValues } from "lit";
|
|||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { computeAreaName } from "../../common/entity/compute_area_name";
|
|
||||||
import { computeDeviceName } from "../../common/entity/compute_device_name";
|
|
||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
import { computeEntityName } from "../../common/entity/compute_entity_name";
|
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
import { getEntityContext } from "../../common/entity/context/get_entity_context";
|
|
||||||
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
||||||
import { computeRTL } from "../../common/util/compute_rtl";
|
import { computeRTL } from "../../common/util/compute_rtl";
|
||||||
import { domainToName } from "../../data/integration";
|
import { domainToName } from "../../data/integration";
|
||||||
@@ -148,11 +144,9 @@ export class HaEntityPicker extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { area, device } = getEntityContext(stateObj, this.hass);
|
const entityName = this.hass.formatEntityName(stateObj, "entity");
|
||||||
|
const deviceName = this.hass.formatEntityName(stateObj, "device");
|
||||||
const entityName = computeEntityName(stateObj, this.hass);
|
const areaName = this.hass.formatEntityName(stateObj, "area");
|
||||||
const deviceName = device ? computeDeviceName(device) : undefined;
|
|
||||||
const areaName = area ? computeAreaName(area) : undefined;
|
|
||||||
|
|
||||||
const isRTL = computeRTL(this.hass);
|
const isRTL = computeRTL(this.hass);
|
||||||
|
|
||||||
@@ -311,12 +305,10 @@ export class HaEntityPicker extends LitElement {
|
|||||||
items = entityIds.map<EntityComboBoxItem>((entityId) => {
|
items = entityIds.map<EntityComboBoxItem>((entityId) => {
|
||||||
const stateObj = hass!.states[entityId];
|
const stateObj = hass!.states[entityId];
|
||||||
|
|
||||||
const { area, device } = getEntityContext(stateObj, hass);
|
|
||||||
|
|
||||||
const friendlyName = computeStateName(stateObj); // Keep this for search
|
const friendlyName = computeStateName(stateObj); // Keep this for search
|
||||||
const entityName = computeEntityName(stateObj, hass);
|
const entityName = this.hass.formatEntityName(stateObj, "entity");
|
||||||
const deviceName = device ? computeDeviceName(device) : undefined;
|
const deviceName = this.hass.formatEntityName(stateObj, "device");
|
||||||
const areaName = area ? computeAreaName(area) : undefined;
|
const areaName = this.hass.formatEntityName(stateObj, "area");
|
||||||
|
|
||||||
const domainName = domainToName(
|
const domainName = domainToName(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
|
@@ -63,10 +63,10 @@ class HaEntityStatePicker extends LitElement {
|
|||||||
const entityIds = this.entityId ? ensureArray(this.entityId) : [];
|
const entityIds = this.entityId ? ensureArray(this.entityId) : [];
|
||||||
|
|
||||||
const entitiesOptions = entityIds.map<StateOption[]>((entityId) => {
|
const entitiesOptions = entityIds.map<StateOption[]>((entityId) => {
|
||||||
const stateObj = this.hass.states[entityId];
|
const stateObj = this.hass.states[entityId] || {
|
||||||
if (!stateObj) {
|
entity_id: entityId,
|
||||||
return [];
|
attributes: {},
|
||||||
}
|
};
|
||||||
|
|
||||||
const states = getStates(this.hass, stateObj, this.attribute).filter(
|
const states = getStates(this.hass, stateObj, this.attribute).filter(
|
||||||
(s) => !this.hideStates?.includes(s)
|
(s) => !this.hideStates?.includes(s)
|
||||||
|
149
src/components/entity/ha-entity-states-picker.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import type { PropertyValues } from "lit";
|
||||||
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { keyed } from "lit/directives/keyed";
|
||||||
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import "./ha-entity-state-picker";
|
||||||
|
|
||||||
|
@customElement("ha-entity-states-picker")
|
||||||
|
export class HaEntityStatesPicker extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public entityId?: string;
|
||||||
|
|
||||||
|
@property() public attribute?: string;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public extraOptions?: any[];
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: "allow-custom-value" })
|
||||||
|
public allowCustomValue;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Array }) public value?: string[];
|
||||||
|
|
||||||
|
@property() public helper?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public required = false;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public hideStates?: string[];
|
||||||
|
|
||||||
|
private _keys: string[] = [];
|
||||||
|
|
||||||
|
private _getKey(index: number) {
|
||||||
|
if (!this._keys[index]) {
|
||||||
|
this._keys[index] = Math.random().toString();
|
||||||
|
}
|
||||||
|
return this._keys[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
|
super.willUpdate(changedProps);
|
||||||
|
if (changedProps.has("value")) {
|
||||||
|
this.value = ensureArray(this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this.hass) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = this.value || [];
|
||||||
|
const hide = [...(this.hideStates || []), ...value];
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${repeat(
|
||||||
|
value,
|
||||||
|
(_state, index) => this._getKey(index),
|
||||||
|
(state, index) => html`
|
||||||
|
<div>
|
||||||
|
<ha-entity-state-picker
|
||||||
|
.index=${index}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.entityId=${this.entityId}
|
||||||
|
.attribute=${this.attribute}
|
||||||
|
.extraOptions=${this.extraOptions}
|
||||||
|
.hideStates=${hide.filter((v) => v !== state)}
|
||||||
|
.allowCustomValue=${this.allowCustomValue}
|
||||||
|
.label=${this.label}
|
||||||
|
.value=${state}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.helper=${this.disabled && index === value.length - 1
|
||||||
|
? this.helper
|
||||||
|
: undefined}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
></ha-entity-state-picker>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
${this.disabled && value.length
|
||||||
|
? nothing
|
||||||
|
: keyed(
|
||||||
|
value.length,
|
||||||
|
html`<ha-entity-state-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.entityId=${this.entityId}
|
||||||
|
.attribute=${this.attribute}
|
||||||
|
.extraOptions=${this.extraOptions}
|
||||||
|
.hideStates=${hide}
|
||||||
|
.allowCustomValue=${this.allowCustomValue}
|
||||||
|
.label=${this.label}
|
||||||
|
.helper=${this.helper}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required && !value.length}
|
||||||
|
@value-changed=${this._addValue}
|
||||||
|
></ha-entity-state-picker>`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const newState = ev.detail.value;
|
||||||
|
const newValue = [...this.value!];
|
||||||
|
const index = (ev.currentTarget as any)?.index;
|
||||||
|
if (index == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newState === undefined) {
|
||||||
|
newValue.splice(index, 1);
|
||||||
|
this._keys.splice(index, 1);
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: newValue,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newValue[index] = newState;
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: newValue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addValue(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: [...(this.value || []), ev.detail.value],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static override styles = css`
|
||||||
|
div {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-entity-states-picker": HaEntityStatesPicker;
|
||||||
|
}
|
||||||
|
}
|
@@ -112,7 +112,7 @@ export class HaEntityToggle extends LitElement {
|
|||||||
if (!this.hass || !this.stateObj) {
|
if (!this.hass || !this.stateObj) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
forwardHaptic("light");
|
forwardHaptic(this, "light");
|
||||||
const stateDomain = computeStateDomain(this.stateObj);
|
const stateDomain = computeStateDomain(this.stateObj);
|
||||||
let serviceDomain;
|
let serviceDomain;
|
||||||
let service;
|
let service;
|
||||||
|
@@ -6,11 +6,7 @@ import { customElement, property, query } from "lit/decorators";
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { ensureArray } from "../../common/array/ensure-array";
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { computeAreaName } from "../../common/entity/compute_area_name";
|
|
||||||
import { computeDeviceName } from "../../common/entity/compute_device_name";
|
|
||||||
import { computeEntityName } from "../../common/entity/compute_entity_name";
|
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
import { getEntityContext } from "../../common/entity/context/get_entity_context";
|
|
||||||
import { computeRTL } from "../../common/util/compute_rtl";
|
import { computeRTL } from "../../common/util/compute_rtl";
|
||||||
import { domainToName } from "../../data/integration";
|
import { domainToName } from "../../data/integration";
|
||||||
import {
|
import {
|
||||||
@@ -259,12 +255,10 @@ export class HaStatisticPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
const id = meta.statistic_id;
|
const id = meta.statistic_id;
|
||||||
|
|
||||||
const { area, device } = getEntityContext(stateObj, hass);
|
|
||||||
|
|
||||||
const friendlyName = computeStateName(stateObj); // Keep this for search
|
const friendlyName = computeStateName(stateObj); // Keep this for search
|
||||||
const entityName = computeEntityName(stateObj, hass);
|
const entityName = hass.formatEntityName(stateObj, "entity");
|
||||||
const deviceName = device ? computeDeviceName(device) : undefined;
|
const deviceName = hass.formatEntityName(stateObj, "device");
|
||||||
const areaName = area ? computeAreaName(area) : undefined;
|
const areaName = hass.formatEntityName(stateObj, "area");
|
||||||
|
|
||||||
const primary = entityName || deviceName || id;
|
const primary = entityName || deviceName || id;
|
||||||
const secondary = [areaName, entityName ? deviceName : undefined]
|
const secondary = [areaName, entityName ? deviceName : undefined]
|
||||||
@@ -337,11 +331,9 @@ export class HaStatisticPicker extends LitElement {
|
|||||||
const stateObj = this.hass.states[statisticId];
|
const stateObj = this.hass.states[statisticId];
|
||||||
|
|
||||||
if (stateObj) {
|
if (stateObj) {
|
||||||
const { area, device } = getEntityContext(stateObj, this.hass);
|
const entityName = this.hass.formatEntityName(stateObj, "entity");
|
||||||
|
const deviceName = this.hass.formatEntityName(stateObj, "device");
|
||||||
const entityName = computeEntityName(stateObj, this.hass);
|
const areaName = this.hass.formatEntityName(stateObj, "area");
|
||||||
const deviceName = device ? computeDeviceName(device) : undefined;
|
|
||||||
const areaName = area ? computeAreaName(area) : undefined;
|
|
||||||
|
|
||||||
const isRTL = computeRTL(this.hass);
|
const isRTL = computeRTL(this.hass);
|
||||||
|
|
||||||
|
@@ -36,39 +36,38 @@ class StateInfo extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
${this.inDialog
|
${this.inDialog
|
||||||
? html`<div class="time-ago">
|
? html`<div class="time-ago">
|
||||||
<ha-tooltip>
|
<ha-tooltip for="relative-time">
|
||||||
<ha-relative-time
|
<div class="row">
|
||||||
.hass=${this.hass}
|
<span class="column-name">
|
||||||
.datetime=${this.stateObj.last_changed}
|
${this.hass.localize(
|
||||||
capitalize
|
"ui.dialogs.more_info_control.last_changed"
|
||||||
></ha-relative-time>
|
)}:
|
||||||
<div slot="content">
|
</span>
|
||||||
<div class="row">
|
<ha-relative-time
|
||||||
<span class="column-name">
|
.hass=${this.hass}
|
||||||
${this.hass.localize(
|
.datetime=${this.stateObj.last_changed}
|
||||||
"ui.dialogs.more_info_control.last_changed"
|
capitalize
|
||||||
)}:
|
></ha-relative-time>
|
||||||
</span>
|
</div>
|
||||||
<ha-relative-time
|
<div class="row">
|
||||||
.hass=${this.hass}
|
<span>
|
||||||
.datetime=${this.stateObj.last_changed}
|
${this.hass.localize(
|
||||||
capitalize
|
"ui.dialogs.more_info_control.last_updated"
|
||||||
></ha-relative-time>
|
)}:
|
||||||
</div>
|
</span>
|
||||||
<div class="row">
|
<ha-relative-time
|
||||||
<span>
|
.hass=${this.hass}
|
||||||
${this.hass.localize(
|
.datetime=${this.stateObj.last_updated}
|
||||||
"ui.dialogs.more_info_control.last_updated"
|
capitalize
|
||||||
)}:
|
></ha-relative-time>
|
||||||
</span>
|
|
||||||
<ha-relative-time
|
|
||||||
.hass=${this.hass}
|
|
||||||
.datetime=${this.stateObj.last_updated}
|
|
||||||
capitalize
|
|
||||||
></ha-relative-time>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ha-tooltip>
|
</ha-tooltip>
|
||||||
|
<ha-relative-time
|
||||||
|
id="relative-time"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.datetime=${this.stateObj.last_changed}
|
||||||
|
capitalize
|
||||||
|
></ha-relative-time>
|
||||||
</div>`
|
</div>`
|
||||||
: html`<div class="extra-info"><slot></slot></div>`}
|
: html`<div class="extra-info"><slot></slot></div>`}
|
||||||
</div>`;
|
</div>`;
|
||||||
|
@@ -86,6 +86,10 @@ class HaAlert extends LitElement {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
.icon {
|
||||||
|
height: var(--ha-alert-icon-size, 24px);
|
||||||
|
width: var(--ha-alert-icon-size, 24px);
|
||||||
|
}
|
||||||
.issue-type::after {
|
.issue-type::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -97,9 +101,6 @@ class HaAlert extends LitElement {
|
|||||||
content: "";
|
content: "";
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.icon {
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
.icon.no-title {
|
.icon.no-title {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
}
|
}
|
||||||
@@ -122,10 +123,11 @@ class HaAlert extends LitElement {
|
|||||||
.main-content {
|
.main-content {
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
line-height: normal;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
margin-inline-start: 8px;
|
margin-inline-start: 8px;
|
||||||
margin-inline-end: 0;
|
margin-inline-end: 8px;
|
||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import type { LocalizeFunc } from "../common/translations/localize";
|
import type { LocalizeFunc } from "../common/translations/localize";
|
||||||
@@ -67,21 +67,24 @@ export class HaAnalytics extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<ha-tooltip
|
<ha-switch
|
||||||
content=${this.localize(
|
.id="switch-${preference}"
|
||||||
`ui.panel.${this.translationKeyPanel}.analytics.need_base_enabled`
|
@change=${this._handleRowClick}
|
||||||
)}
|
.checked=${this.analytics?.preferences[preference]}
|
||||||
placement="right"
|
.preference=${preference}
|
||||||
?disabled=${baseEnabled}
|
name=${preference}
|
||||||
>
|
>
|
||||||
<ha-switch
|
</ha-switch>
|
||||||
@change=${this._handleRowClick}
|
${baseEnabled
|
||||||
.checked=${this.analytics?.preferences[preference]}
|
? nothing
|
||||||
.preference=${preference}
|
: html`<ha-tooltip
|
||||||
name=${preference}
|
.for="switch-${preference}"
|
||||||
>
|
placement="right"
|
||||||
</ha-switch>
|
>
|
||||||
</ha-tooltip>
|
${this.localize(
|
||||||
|
`ui.panel.${this.translationKeyPanel}.analytics.need_base_enabled`
|
||||||
|
)}
|
||||||
|
</ha-tooltip>`}
|
||||||
</span>
|
</span>
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
`
|
`
|
||||||
|
@@ -3,11 +3,15 @@ import type { CSSResultGroup, PropertyValues } from "lit";
|
|||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { computeAttributeNameDisplay } from "../common/entity/compute_attribute_display";
|
import { computeAttributeNameDisplay } from "../common/entity/compute_attribute_display";
|
||||||
import { STATE_ATTRIBUTES } from "../data/entity_attributes";
|
import {
|
||||||
|
STATE_ATTRIBUTES,
|
||||||
|
STATE_ATTRIBUTES_DOMAIN_CLASS,
|
||||||
|
} from "../data/entity_attributes";
|
||||||
import { haStyle } from "../resources/styles";
|
import { haStyle } from "../resources/styles";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import "./ha-attribute-value";
|
import "./ha-attribute-value";
|
||||||
import "./ha-expansion-panel";
|
import "./ha-expansion-panel";
|
||||||
|
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||||
|
|
||||||
@customElement("ha-attributes")
|
@customElement("ha-attributes")
|
||||||
class HaAttributes extends LitElement {
|
class HaAttributes extends LitElement {
|
||||||
@@ -22,7 +26,12 @@ class HaAttributes extends LitElement {
|
|||||||
private get _filteredAttributes() {
|
private get _filteredAttributes() {
|
||||||
return this._computeDisplayAttributes(
|
return this._computeDisplayAttributes(
|
||||||
STATE_ATTRIBUTES.concat(
|
STATE_ATTRIBUTES.concat(
|
||||||
this.extraFilters ? this.extraFilters.split(",") : []
|
this.extraFilters ? this.extraFilters.split(",") : [],
|
||||||
|
(this.stateObj &&
|
||||||
|
STATE_ATTRIBUTES_DOMAIN_CLASS[computeStateDomain(this.stateObj)]?.[
|
||||||
|
this.stateObj.attributes?.device_class
|
||||||
|
]) ||
|
||||||
|
[]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
206
src/components/ha-automation-row.ts
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
import { mdiChevronUp } from "@mdi/js";
|
||||||
|
import type { TemplateResult } from "lit";
|
||||||
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
|
import { customElement, property, query } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import "./ha-icon-button";
|
||||||
|
|
||||||
|
@customElement("ha-automation-row")
|
||||||
|
export class HaAutomationRow extends LitElement {
|
||||||
|
@property({ attribute: "left-chevron", type: Boolean })
|
||||||
|
public leftChevron = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true })
|
||||||
|
public collapsed = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true })
|
||||||
|
public selected = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true, attribute: "sort-selected" })
|
||||||
|
public sortSelected = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true })
|
||||||
|
public disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true, attribute: "building-block" })
|
||||||
|
public buildingBlock = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public highlight?: boolean;
|
||||||
|
|
||||||
|
@query(".row")
|
||||||
|
private _rowElement?: HTMLDivElement;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="row"
|
||||||
|
tabindex="0"
|
||||||
|
role="button"
|
||||||
|
@keydown=${this._handleKeydown}
|
||||||
|
>
|
||||||
|
${this.leftChevron
|
||||||
|
? html`
|
||||||
|
<ha-icon-button
|
||||||
|
class="expand-button"
|
||||||
|
.path=${mdiChevronUp}
|
||||||
|
@click=${this._handleExpand}
|
||||||
|
@keydown=${this._handleExpand}
|
||||||
|
></ha-icon-button>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
<div class="leading-icon-wrapper">
|
||||||
|
<slot name="leading-icon"></slot>
|
||||||
|
</div>
|
||||||
|
<slot class="header" name="header"></slot>
|
||||||
|
<slot name="icons"></slot>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleExpand(ev) {
|
||||||
|
if (ev.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
fireEvent(this, "toggle-collapsed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleKeydown(ev: KeyboardEvent): Promise<void> {
|
||||||
|
if (ev.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
ev.key !== "Enter" &&
|
||||||
|
ev.key !== " " &&
|
||||||
|
!(
|
||||||
|
(this.sortSelected || ev.altKey) &&
|
||||||
|
!(ev.ctrlKey || ev.metaKey) &&
|
||||||
|
!ev.shiftKey &&
|
||||||
|
(ev.key === "ArrowUp" || ev.key === "ArrowDown")
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
if (ev.key === "ArrowUp" || ev.key === "ArrowDown") {
|
||||||
|
if (ev.key === "ArrowUp") {
|
||||||
|
fireEvent(this, "move-up");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fireEvent(this, "move-down");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.sortSelected && (ev.key === "Enter" || ev.key === " ")) {
|
||||||
|
fireEvent(this, "stop-sort-selection");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
public focus() {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this._rowElement?.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 8px;
|
||||||
|
min-height: 48px;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
font-weight: var(--ha-font-weight-medium);
|
||||||
|
outline: none;
|
||||||
|
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||||
|
}
|
||||||
|
.row:focus {
|
||||||
|
outline: var(--wa-focus-ring);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
.expand-button {
|
||||||
|
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
color: var(--ha-color-on-neutral-quiet);
|
||||||
|
margin-left: -8px;
|
||||||
|
}
|
||||||
|
:host([building-block]) .leading-icon-wrapper {
|
||||||
|
background-color: var(--ha-color-fill-neutral-loud-resting);
|
||||||
|
border-radius: var(--ha-border-radius-md);
|
||||||
|
padding: 4px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
::slotted([slot="leading-icon"]) {
|
||||||
|
color: var(--ha-color-on-neutral-quiet);
|
||||||
|
}
|
||||||
|
:host([building-block]) ::slotted([slot="leading-icon"]) {
|
||||||
|
--mdc-icon-size: 20px;
|
||||||
|
color: var(--white-color);
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
:host([collapsed]) .expand-button {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
:host([selected]) .row,
|
||||||
|
:host([selected]) .row:focus {
|
||||||
|
outline: solid;
|
||||||
|
outline-color: var(--primary-color);
|
||||||
|
outline-offset: -2px;
|
||||||
|
outline-width: 2px;
|
||||||
|
}
|
||||||
|
:host([disabled]) .row {
|
||||||
|
border-top-right-radius: var(--ha-border-radius-square);
|
||||||
|
border-top-left-radius: var(--ha-border-radius-square);
|
||||||
|
}
|
||||||
|
::slotted([slot="header"]) {
|
||||||
|
flex: 1;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
margin: 0 12px;
|
||||||
|
}
|
||||||
|
:host([sort-selected]) .row {
|
||||||
|
outline: solid;
|
||||||
|
outline-color: rgba(var(--rgb-accent-color), 0.6);
|
||||||
|
outline-offset: -2px;
|
||||||
|
outline-width: 2px;
|
||||||
|
background-color: rgba(var(--rgb-accent-color), 0.08);
|
||||||
|
}
|
||||||
|
.row:hover {
|
||||||
|
background-color: rgba(var(--rgb-primary-text-color), 0.04);
|
||||||
|
}
|
||||||
|
:host([highlight]) .row {
|
||||||
|
background-color: rgba(var(--rgb-primary-color), 0.08);
|
||||||
|
}
|
||||||
|
:host([highlight]) .row:hover {
|
||||||
|
background-color: rgba(var(--rgb-primary-color), 0.16);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-automation-row": HaAutomationRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"toggle-collapsed": undefined;
|
||||||
|
"stop-sort-selection": undefined;
|
||||||
|
"copy-row": undefined;
|
||||||
|
"cut-row": undefined;
|
||||||
|
"delete-row": undefined;
|
||||||
|
}
|
||||||
|
}
|
@@ -54,7 +54,7 @@ export class HaBadge extends LitElement {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8px;
|
gap: var(--ha-space-2);
|
||||||
height: var(--ha-badge-size, 36px);
|
height: var(--ha-badge-size, 36px);
|
||||||
min-width: var(--ha-badge-size, 36px);
|
min-width: var(--ha-badge-size, 36px);
|
||||||
padding: 0px 12px;
|
padding: 0px 12px;
|
||||||
|
74
src/components/ha-bottom-sheet.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { css, html, LitElement, type PropertyValues } from "lit";
|
||||||
|
import "@home-assistant/webawesome/dist/components/drawer/drawer";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
|
||||||
|
export const BOTTOM_SHEET_ANIMATION_DURATION_MS = 300;
|
||||||
|
|
||||||
|
@customElement("ha-bottom-sheet")
|
||||||
|
export class HaBottomSheet extends LitElement {
|
||||||
|
@property({ type: Boolean }) public open = false;
|
||||||
|
|
||||||
|
@state() private _drawerOpen = false;
|
||||||
|
|
||||||
|
private _handleAfterHide() {
|
||||||
|
this.open = false;
|
||||||
|
const ev = new Event("closed", {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
});
|
||||||
|
this.dispatchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProperties: PropertyValues): void {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
if (changedProperties.has("open")) {
|
||||||
|
this._drawerOpen = this.open;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<wa-drawer
|
||||||
|
placement="bottom"
|
||||||
|
.open=${this._drawerOpen}
|
||||||
|
@wa-after-hide=${this._handleAfterHide}
|
||||||
|
without-header
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</wa-drawer>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
wa-drawer {
|
||||||
|
--wa-color-surface-raised: var(
|
||||||
|
--ha-bottom-sheet-surface-background,
|
||||||
|
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
|
||||||
|
);
|
||||||
|
--spacing: 0;
|
||||||
|
--size: auto;
|
||||||
|
--show-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||||
|
--hide-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||||
|
}
|
||||||
|
wa-drawer::part(dialog) {
|
||||||
|
border-top-left-radius: var(
|
||||||
|
--ha-bottom-sheet-border-radius,
|
||||||
|
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||||
|
);
|
||||||
|
border-top-right-radius: var(
|
||||||
|
--ha-bottom-sheet-border-radius,
|
||||||
|
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||||
|
);
|
||||||
|
max-height: 90vh;
|
||||||
|
padding-bottom: var(--safe-area-inset-bottom);
|
||||||
|
padding-left: var(--safe-area-inset-left);
|
||||||
|
padding-right: var(--safe-area-inset-right);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-bottom-sheet": HaBottomSheet;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,141 +1,82 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@home-assistant/webawesome/dist/components/button-group/button-group";
|
||||||
import type { Button } from "@material/mwc-button/mwc-button";
|
|
||||||
import type { TemplateResult } from "lit";
|
import type { TemplateResult } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { customElement, property, queryAll } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import type { ToggleButton } from "../types";
|
import type { ToggleButton } from "../types";
|
||||||
import "./ha-icon-button";
|
import "./ha-button";
|
||||||
|
import "./ha-svg-icon";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @element ha-button-toggle-group
|
||||||
|
*
|
||||||
|
* @summary
|
||||||
|
* A button-group with one active selection.
|
||||||
|
*
|
||||||
|
* @attr {ToggleButton[]} buttons - the button config
|
||||||
|
* @attr {string} active - The value of the currently active button.
|
||||||
|
* @attr {("small"|"medium")} size - The size of the buttons in the group.
|
||||||
|
* @attr {("brand"|"neutral"|"success"|"warning"|"danger")} variant - The variant of the buttons in the group.
|
||||||
|
*
|
||||||
|
* @fires value-changed - Dispatched when the active button changes.
|
||||||
|
*/
|
||||||
@customElement("ha-button-toggle-group")
|
@customElement("ha-button-toggle-group")
|
||||||
export class HaButtonToggleGroup extends LitElement {
|
export class HaButtonToggleGroup extends LitElement {
|
||||||
@property({ attribute: false }) public buttons!: ToggleButton[];
|
@property({ attribute: false }) public buttons!: ToggleButton[];
|
||||||
|
|
||||||
@property() public active?: string;
|
@property() public active?: string;
|
||||||
|
|
||||||
@property({ attribute: "full-width", type: Boolean })
|
@property({ reflect: true }) size: "small" | "medium" = "medium";
|
||||||
public fullWidth = false;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public dense = false;
|
@property({ type: Boolean, reflect: true, attribute: "no-wrap" })
|
||||||
|
public nowrap = false;
|
||||||
|
|
||||||
@queryAll("mwc-button") private _buttons?: Button[];
|
@property() public variant:
|
||||||
|
| "brand"
|
||||||
|
| "neutral"
|
||||||
|
| "success"
|
||||||
|
| "warning"
|
||||||
|
| "danger" = "brand";
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div>
|
<wa-button-group childSelector="ha-button">
|
||||||
${this.buttons.map((button) =>
|
${this.buttons.map(
|
||||||
button.iconPath
|
(button) =>
|
||||||
? html`<ha-icon-button
|
html`<ha-button
|
||||||
.label=${button.label}
|
iconTag="ha-svg-icon"
|
||||||
.path=${button.iconPath}
|
class="icon"
|
||||||
.value=${button.value}
|
.variant=${this.variant}
|
||||||
?active=${this.active === button.value}
|
.size=${this.size}
|
||||||
@click=${this._handleClick}
|
.value=${button.value}
|
||||||
></ha-icon-button>`
|
@click=${this._handleClick}
|
||||||
: html`<mwc-button
|
.title=${button.label}
|
||||||
style=${styleMap({
|
.appearance=${this.active === button.value ? "accent" : "filled"}
|
||||||
width: this.fullWidth
|
>
|
||||||
? `${100 / this.buttons.length}%`
|
${button.iconPath
|
||||||
: "initial",
|
? html`<ha-svg-icon
|
||||||
})}
|
aria-label=${button.label}
|
||||||
outlined
|
.path=${button.iconPath}
|
||||||
.dense=${this.dense}
|
></ha-svg-icon>`
|
||||||
.value=${button.value}
|
: button.label}
|
||||||
?active=${this.active === button.value}
|
</ha-button>`
|
||||||
@click=${this._handleClick}
|
|
||||||
>${button.label}</mwc-button
|
|
||||||
>`
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</wa-button-group>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated() {
|
|
||||||
// Work around Safari default margin that is not reset in mwc-button as of aug 2021
|
|
||||||
this._buttons?.forEach(async (button) => {
|
|
||||||
await button.updateComplete;
|
|
||||||
(
|
|
||||||
button.shadowRoot!.querySelector("button") as HTMLButtonElement
|
|
||||||
).style.margin = "0";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleClick(ev): void {
|
private _handleClick(ev): void {
|
||||||
this.active = ev.currentTarget.value;
|
this.active = ev.currentTarget.value;
|
||||||
fireEvent(this, "value-changed", { value: this.active });
|
fireEvent(this, "value-changed", { value: this.active });
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
div {
|
:host {
|
||||||
display: flex;
|
|
||||||
--mdc-icon-button-size: var(--button-toggle-size, 36px);
|
|
||||||
--mdc-icon-size: var(--button-toggle-icon-size, 20px);
|
--mdc-icon-size: var(--button-toggle-icon-size, 20px);
|
||||||
direction: ltr;
|
|
||||||
}
|
}
|
||||||
mwc-button {
|
|
||||||
flex: 1;
|
:host([no-wrap]) wa-button-group::part(base) {
|
||||||
--mdc-shape-small: 0;
|
flex-wrap: nowrap;
|
||||||
--mdc-button-outline-width: 1px 0 1px 1px;
|
|
||||||
--mdc-button-outline-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
ha-icon-button {
|
|
||||||
border: 1px solid var(--primary-color);
|
|
||||||
border-right-width: 0px;
|
|
||||||
}
|
|
||||||
ha-icon-button,
|
|
||||||
mwc-button {
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
ha-icon-button::before,
|
|
||||||
mwc-button::before {
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
content: "";
|
|
||||||
transition:
|
|
||||||
opacity 15ms linear,
|
|
||||||
background-color 15ms linear;
|
|
||||||
}
|
|
||||||
ha-icon-button[active]::before,
|
|
||||||
mwc-button[active]::before {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
ha-icon-button[active] {
|
|
||||||
--icon-primary-color: var(--text-primary-color);
|
|
||||||
}
|
|
||||||
mwc-button[active] {
|
|
||||||
--mdc-theme-primary: var(--text-primary-color);
|
|
||||||
}
|
|
||||||
ha-icon-button:first-child,
|
|
||||||
mwc-button:first-child {
|
|
||||||
--mdc-shape-small: 4px 0 0 4px;
|
|
||||||
border-radius: 4px 0 0 4px;
|
|
||||||
--mdc-button-outline-width: 1px;
|
|
||||||
}
|
|
||||||
mwc-button:first-child::before {
|
|
||||||
border-radius: 4px 0 0 4px;
|
|
||||||
}
|
|
||||||
ha-icon-button:last-child,
|
|
||||||
mwc-button:last-child {
|
|
||||||
border-radius: 0 4px 4px 0;
|
|
||||||
border-right-width: 1px;
|
|
||||||
--mdc-shape-small: 0 4px 4px 0;
|
|
||||||
--mdc-button-outline-width: 1px;
|
|
||||||
}
|
|
||||||
mwc-button:last-child::before {
|
|
||||||
border-radius: 0 4px 4px 0;
|
|
||||||
}
|
|
||||||
ha-icon-button:only-child,
|
|
||||||
mwc-button:only-child {
|
|
||||||
--mdc-shape-small: 4px;
|
|
||||||
border-right-width: 1px;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
import Button from "@awesome.me/webawesome/dist/components/button/button";
|
import Button from "@home-assistant/webawesome/dist/components/button/button";
|
||||||
import { css, type CSSResultGroup } from "lit";
|
import { css, type CSSResultGroup } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
|
|
||||||
import { StateSet } from "../resources/polyfills/stateset";
|
|
||||||
|
|
||||||
export type Appearance = "accent" | "filled" | "outlined" | "plain";
|
export type Appearance = "accent" | "filled" | "outlined" | "plain";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,14 +37,6 @@ export type Appearance = "accent" | "filled" | "outlined" | "plain";
|
|||||||
export class HaButton extends Button {
|
export class HaButton extends Button {
|
||||||
variant: "brand" | "neutral" | "success" | "warning" | "danger" = "brand";
|
variant: "brand" | "neutral" | "success" | "warning" | "danger" = "brand";
|
||||||
|
|
||||||
attachInternals() {
|
|
||||||
const internals = super.attachInternals();
|
|
||||||
Object.defineProperty(internals, "states", {
|
|
||||||
value: new StateSet(this, internals.states),
|
|
||||||
});
|
|
||||||
return internals;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
Button.styles,
|
Button.styles,
|
||||||
@@ -67,6 +57,8 @@ export class HaButton extends Button {
|
|||||||
|
|
||||||
font-size: var(--ha-font-size-m);
|
font-size: var(--ha-font-size-m);
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|
||||||
|
transition: background-color 0.15s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([size="small"]) .button {
|
:host([size="small"]) .button {
|
||||||
@@ -75,6 +67,7 @@ export class HaButton extends Button {
|
|||||||
var(--button-height, 32px)
|
var(--button-height, 32px)
|
||||||
);
|
);
|
||||||
font-size: var(--wa-font-size-s, var(--ha-font-size-m));
|
font-size: var(--wa-font-size-s, var(--ha-font-size-m));
|
||||||
|
--wa-form-control-padding-inline: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([variant="brand"]) {
|
:host([variant="brand"]) {
|
||||||
@@ -180,6 +173,11 @@ export class HaButton extends Button {
|
|||||||
color: var(--wa-color-on-normal);
|
color: var(--wa-color-on-normal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
:host([appearance~="filled"]) .button {
|
||||||
|
color: var(--wa-color-on-normal);
|
||||||
|
background-color: var(--wa-color-fill-normal);
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
:host([appearance~="filled"])
|
:host([appearance~="filled"])
|
||||||
.button:not(.disabled):not(.loading):active {
|
.button:not(.disabled):not(.loading):active {
|
||||||
background-color: var(--button-color-fill-normal-active);
|
background-color: var(--button-color-fill-normal-active);
|
||||||
@@ -218,6 +216,13 @@ export class HaButton extends Button {
|
|||||||
slot[name="end"]::slotted(*) {
|
slot[name="end"]::slotted(*) {
|
||||||
margin-inline-start: 4px;
|
margin-inline-start: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button.has-start {
|
||||||
|
padding-inline-start: 8px;
|
||||||
|
}
|
||||||
|
.button.has-end {
|
||||||
|
padding-inline-end: 8px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|