Compare commits
908 Commits
20171102.0
...
20180827.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8cfd7ee170 | ||
![]() |
59a8354a7f | ||
![]() |
f443942e03 | ||
![]() |
772208ba22 | ||
![]() |
e46a1be5d7 | ||
![]() |
d295a9d0e4 | ||
![]() |
f0bf34073b | ||
![]() |
73098d106d | ||
![]() |
c8ea4cd85e | ||
![]() |
f64ddf46e2 | ||
![]() |
5e6e3dd793 | ||
![]() |
13ff59ec89 | ||
![]() |
7cc3fc728b | ||
![]() |
ec79e12bf3 | ||
![]() |
d38b989c02 | ||
![]() |
f9a629bf8d | ||
![]() |
02edbce460 | ||
![]() |
b9f84d012f | ||
![]() |
421478bb49 | ||
![]() |
cde106bd77 | ||
![]() |
144f00e2cc | ||
![]() |
74185f0beb | ||
![]() |
09f238162e | ||
![]() |
13ece650bc | ||
![]() |
cbef262805 | ||
![]() |
cbdd641d5f | ||
![]() |
f435c38aa5 | ||
![]() |
4e135681bc | ||
![]() |
8da969455b | ||
![]() |
f4ebf77b36 | ||
![]() |
bb3af18e46 | ||
![]() |
c025df2974 | ||
![]() |
5cf5e6ec6c | ||
![]() |
6236252a90 | ||
![]() |
9f71d9331c | ||
![]() |
488e6d09ca | ||
![]() |
00b1fbe091 | ||
![]() |
c1ea1bb54f | ||
![]() |
49a5d922fc | ||
![]() |
a960084438 | ||
![]() |
8ae44e6f7f | ||
![]() |
95ea220936 | ||
![]() |
7ffdb3ddc7 | ||
![]() |
decbbc9acd | ||
![]() |
496e05651e | ||
![]() |
efd36bf207 | ||
![]() |
283668ef18 | ||
![]() |
310299367b | ||
![]() |
63c7c55843 | ||
![]() |
4bc83b01d3 | ||
![]() |
743c3ab784 | ||
![]() |
98d2b0b889 | ||
![]() |
c39417c93d | ||
![]() |
1b2b62f04c | ||
![]() |
1a31855fc8 | ||
![]() |
f4e92dedff | ||
![]() |
85d6be64cc | ||
![]() |
033e058745 | ||
![]() |
1cbe0b7b9f | ||
![]() |
22433d1a39 | ||
![]() |
e048060c72 | ||
![]() |
60d04b5c58 | ||
![]() |
0a991ecdf8 | ||
![]() |
a9f29a3151 | ||
![]() |
50f417a7e2 | ||
![]() |
d69492988a | ||
![]() |
286fa14a30 | ||
![]() |
8fa9b15fbe | ||
![]() |
b432324159 | ||
![]() |
255ea41648 | ||
![]() |
6bcfdfaaf8 | ||
![]() |
2dada41791 | ||
![]() |
4b5152a348 | ||
![]() |
c537a8d389 | ||
![]() |
716e505c88 | ||
![]() |
cc652a9476 | ||
![]() |
db310646b7 | ||
![]() |
a8ce5e3e25 | ||
![]() |
7ca7d3e12c | ||
![]() |
be7fb50f8c | ||
![]() |
fdbaa3908a | ||
![]() |
ec9a259fc0 | ||
![]() |
1287f98357 | ||
![]() |
6cbca6d88a | ||
![]() |
e458cf1388 | ||
![]() |
36ba37c59c | ||
![]() |
1b18315e96 | ||
![]() |
690252a6ba | ||
![]() |
72390aaadc | ||
![]() |
7b579c8e7a | ||
![]() |
b32a6d58f3 | ||
![]() |
7953d960b6 | ||
![]() |
e9e7a3cdf3 | ||
![]() |
f7d9aecf47 | ||
![]() |
201959841c | ||
![]() |
5e91fbc54f | ||
![]() |
d1d248ab87 | ||
![]() |
00bee73bf1 | ||
![]() |
c375e5900b | ||
![]() |
dcc47b6b83 | ||
![]() |
b589412fdd | ||
![]() |
a899fb1df8 | ||
![]() |
0aee48cb2c | ||
![]() |
d30bf6d566 | ||
![]() |
22ed241286 | ||
![]() |
44ab96d590 | ||
![]() |
af4df647cc | ||
![]() |
bdf26bbccd | ||
![]() |
3b2d4de313 | ||
![]() |
aaea698a6c | ||
![]() |
97f548a9f1 | ||
![]() |
6b1d04584e | ||
![]() |
5d7e4322eb | ||
![]() |
bc71019573 | ||
![]() |
f42bddd447 | ||
![]() |
93b1e29876 | ||
![]() |
388c2c5db4 | ||
![]() |
3e60083633 | ||
![]() |
06532def08 | ||
![]() |
abf85ae19b | ||
![]() |
9634a13dc2 | ||
![]() |
a78aba5f50 | ||
![]() |
1b039aee50 | ||
![]() |
54860d7762 | ||
![]() |
5c74e31629 | ||
![]() |
61d3d446f4 | ||
![]() |
ec54036f1e | ||
![]() |
a5befbe153 | ||
![]() |
f952d9c307 | ||
![]() |
68b94cd7c7 | ||
![]() |
56907fb922 | ||
![]() |
5cc6d20674 | ||
![]() |
da78dacca9 | ||
![]() |
964c028c72 | ||
![]() |
708447b6f1 | ||
![]() |
7e257f6ee9 | ||
![]() |
10d44db8e6 | ||
![]() |
e84f5c683c | ||
![]() |
bfdde116e2 | ||
![]() |
51020d7e68 | ||
![]() |
8c44e243e1 | ||
![]() |
9ab4158e0a | ||
![]() |
4c3e039423 | ||
![]() |
7c5c5ea9b6 | ||
![]() |
6db76c8ab4 | ||
![]() |
3f0824bd39 | ||
![]() |
e2c4695613 | ||
![]() |
37678d99af | ||
![]() |
90328cfc33 | ||
![]() |
84b0542fb6 | ||
![]() |
229d167f89 | ||
![]() |
5c5a7e50da | ||
![]() |
bac1c8f630 | ||
![]() |
9c66d47afa | ||
![]() |
1f80769d9e | ||
![]() |
8dcda2dae9 | ||
![]() |
348b284ba6 | ||
![]() |
b5ff52febf | ||
![]() |
832f4ba6cd | ||
![]() |
89f9589084 | ||
![]() |
ed0d0ab944 | ||
![]() |
ed9c73429f | ||
![]() |
c11cf53f96 | ||
![]() |
834528506a | ||
![]() |
f2c56a624b | ||
![]() |
6a35fc9eb5 | ||
![]() |
1b262f7cea | ||
![]() |
c5fd32afac | ||
![]() |
19e58c775e | ||
![]() |
c90a3dcdfd | ||
![]() |
5df758c0b2 | ||
![]() |
77c65d43ae | ||
![]() |
dbb6a8e6d4 | ||
![]() |
89ab6a7d5d | ||
![]() |
c378eda17b | ||
![]() |
133d198e7c | ||
![]() |
2e4ddebcda | ||
![]() |
13819937a7 | ||
![]() |
b89ad9b217 | ||
![]() |
e51177b3c2 | ||
![]() |
bb7dc76996 | ||
![]() |
274f8e1b64 | ||
![]() |
d1d3ff9013 | ||
![]() |
c39b6bb665 | ||
![]() |
52f2c29726 | ||
![]() |
b0f29744bf | ||
![]() |
c17f390f58 | ||
![]() |
f58c612018 | ||
![]() |
d96c5f6bde | ||
![]() |
e649d37c05 | ||
![]() |
594c1d6615 | ||
![]() |
10009eea2e | ||
![]() |
cb60904912 | ||
![]() |
ddd37f63a0 | ||
![]() |
d71a80c4f8 | ||
![]() |
6b10eeb1e9 | ||
![]() |
e3d82f9e37 | ||
![]() |
6739c88428 | ||
![]() |
5b0ca026d5 | ||
![]() |
42525ae1df | ||
![]() |
c201a9073f | ||
![]() |
ad4814dfad | ||
![]() |
e5c900aec1 | ||
![]() |
6f8385afa7 | ||
![]() |
5cccf4836f | ||
![]() |
d299166f2a | ||
![]() |
c0ae7d50ad | ||
![]() |
dc034038c0 | ||
![]() |
536b1e7b73 | ||
![]() |
20d6f6d530 | ||
![]() |
173cd8126c | ||
![]() |
e452384ecd | ||
![]() |
918c2ce29e | ||
![]() |
66803cd4eb | ||
![]() |
66ae61374d | ||
![]() |
cf5460bf58 | ||
![]() |
c868df2718 | ||
![]() |
25fddad446 | ||
![]() |
890dc0512b | ||
![]() |
6b8fbd91c6 | ||
![]() |
06cf03bff0 | ||
![]() |
22ee205807 | ||
![]() |
7bbe8dc2f3 | ||
![]() |
e410274684 | ||
![]() |
76256699e0 | ||
![]() |
dc5213a79a | ||
![]() |
54fcb21917 | ||
![]() |
1a9af5595f | ||
![]() |
5f44009177 | ||
![]() |
42026f096f | ||
![]() |
122414e7bd | ||
![]() |
c781163f6b | ||
![]() |
cdb7a6261e | ||
![]() |
a8063f3359 | ||
![]() |
bb670b76a9 | ||
![]() |
05816374a8 | ||
![]() |
5b67a3691a | ||
![]() |
fb2fc9fbbb | ||
![]() |
ee0308849e | ||
![]() |
51f169aa13 | ||
![]() |
038f5b644b | ||
![]() |
3442700e1f | ||
![]() |
07d76739b4 | ||
![]() |
d40dea6d3b | ||
![]() |
8bbc8e0bb8 | ||
![]() |
9e2d311ce6 | ||
![]() |
de91aea3b9 | ||
![]() |
e0ccc1999a | ||
![]() |
b55004d73b | ||
![]() |
3d18462ec0 | ||
![]() |
654e74294d | ||
![]() |
20e92893e0 | ||
![]() |
f2d30ad850 | ||
![]() |
88453733eb | ||
![]() |
584e959f1a | ||
![]() |
479de6c4c7 | ||
![]() |
be7900cd87 | ||
![]() |
7bfc01b02c | ||
![]() |
590bba0aca | ||
![]() |
aa27ee609d | ||
![]() |
e1e88aa8b2 | ||
![]() |
e3d7220c0b | ||
![]() |
9fd1db0493 | ||
![]() |
748b5a8e41 | ||
![]() |
8991c966d2 | ||
![]() |
2cfff991ac | ||
![]() |
376228e0fe | ||
![]() |
4c1feb313b | ||
![]() |
b0561d7766 | ||
![]() |
bda2d42994 | ||
![]() |
d225105e58 | ||
![]() |
313a3dd2c9 | ||
![]() |
c579d7fed3 | ||
![]() |
c83184654e | ||
![]() |
7e0be4a2f6 | ||
![]() |
53e698c757 | ||
![]() |
8d5c862908 | ||
![]() |
d5266c1c56 | ||
![]() |
44d64bc7ce | ||
![]() |
545af4c6d2 | ||
![]() |
501033df15 | ||
![]() |
1f82934c93 | ||
![]() |
9d22645e87 | ||
![]() |
b312533948 | ||
![]() |
77f623d519 | ||
![]() |
8cebddcccc | ||
![]() |
75502bac6e | ||
![]() |
18b52b53cb | ||
![]() |
b6ee5442f0 | ||
![]() |
691b80c08a | ||
![]() |
f8b38ced26 | ||
![]() |
63b123fc8f | ||
![]() |
a92c187627 | ||
![]() |
92111cd5be | ||
![]() |
1f8f6f52bc | ||
![]() |
626b054540 | ||
![]() |
4acfa2ba88 | ||
![]() |
87bd9ed48a | ||
![]() |
ce4280e816 | ||
![]() |
3382430c8f | ||
![]() |
c186a5aab2 | ||
![]() |
21dcbb3b9d | ||
![]() |
cd33e2568a | ||
![]() |
4cf7959b12 | ||
![]() |
dc9a227aa3 | ||
![]() |
2a8462951b | ||
![]() |
449751c59f | ||
![]() |
d2eb0ef23f | ||
![]() |
d752060f7a | ||
![]() |
cc74374390 | ||
![]() |
dd87502688 | ||
![]() |
4e608e6a2c | ||
![]() |
ba2c3edc87 | ||
![]() |
13c8a00e97 | ||
![]() |
1076fd8fc4 | ||
![]() |
5f226d1809 | ||
![]() |
75e3f1f37b | ||
![]() |
b43b34263e | ||
![]() |
f79feae120 | ||
![]() |
052e659782 | ||
![]() |
890c31fb96 | ||
![]() |
58a0f6aab9 | ||
![]() |
2905cefd39 | ||
![]() |
67cef55f34 | ||
![]() |
710d98feda | ||
![]() |
b085121ab4 | ||
![]() |
274e1a671f | ||
![]() |
98df34c0a8 | ||
![]() |
e28c651930 | ||
![]() |
8e8d907090 | ||
![]() |
cf3d864378 | ||
![]() |
7f133d0316 | ||
![]() |
9717166fee | ||
![]() |
0b6214be2b | ||
![]() |
7f93317314 | ||
![]() |
b89de4b494 | ||
![]() |
d243f2ead6 | ||
![]() |
92930a2b94 | ||
![]() |
7a2aff712b | ||
![]() |
0ae676d9ba | ||
![]() |
673f769237 | ||
![]() |
bb442c824b | ||
![]() |
ab16a3712e | ||
![]() |
b71a4be67e | ||
![]() |
eb91cedf68 | ||
![]() |
9ac1384e1f | ||
![]() |
cab53b3324 | ||
![]() |
bf4d0e6bc9 | ||
![]() |
10c997b7b2 | ||
![]() |
4d48a63141 | ||
![]() |
af14fc6548 | ||
![]() |
4bd14a5280 | ||
![]() |
cf57cf853b | ||
![]() |
8133102bcb | ||
![]() |
dbcae9cb77 | ||
![]() |
cb284d9718 | ||
![]() |
d16f4c846a | ||
![]() |
ce3564625e | ||
![]() |
e928be353a | ||
![]() |
addb8e3111 | ||
![]() |
fa11fbc85d | ||
![]() |
c3d67133c2 | ||
![]() |
1a3966e55f | ||
![]() |
e11cca28fd | ||
![]() |
1f14373117 | ||
![]() |
490c37e899 | ||
![]() |
3c48973584 | ||
![]() |
b378b92aa8 | ||
![]() |
c0919cfe11 | ||
![]() |
f96db5003a | ||
![]() |
a8fa8d46e5 | ||
![]() |
673c7c5184 | ||
![]() |
ff50414cbb | ||
![]() |
b8ac150ee4 | ||
![]() |
2f6bb9af0c | ||
![]() |
348943f221 | ||
![]() |
74e0779d38 | ||
![]() |
21e4bc4ee4 | ||
![]() |
af6167b9c8 | ||
![]() |
eb29b73390 | ||
![]() |
e158709b1e | ||
![]() |
045b1d02be | ||
![]() |
66012da4de | ||
![]() |
294f0febbf | ||
![]() |
a79f98efbf | ||
![]() |
dd57ddbef6 | ||
![]() |
d3b6740488 | ||
![]() |
f51503af4f | ||
![]() |
0c92b356a1 | ||
![]() |
9393bb2fba | ||
![]() |
61bc2d04f3 | ||
![]() |
71196b9704 | ||
![]() |
29912d1b63 | ||
![]() |
2d67a01ec2 | ||
![]() |
9d8f055f7b | ||
![]() |
c6f2f43767 | ||
![]() |
f4c36e37bb | ||
![]() |
d3c10d9b49 | ||
![]() |
4e9a7a4a23 | ||
![]() |
f24a26c686 | ||
![]() |
df384dc23e | ||
![]() |
008fcbe1dc | ||
![]() |
86a522ce65 | ||
![]() |
6bf34afc31 | ||
![]() |
081e8d9824 | ||
![]() |
8e6929659d | ||
![]() |
4d50ab937a | ||
![]() |
0edf06bfb9 | ||
![]() |
d1fcdfd5a3 | ||
![]() |
d7b2a03880 | ||
![]() |
4a734fbffc | ||
![]() |
41990767e2 | ||
![]() |
0789c0884c | ||
![]() |
4de7cbec30 | ||
![]() |
1d144a101c | ||
![]() |
1eda9155fd | ||
![]() |
43b0be9581 | ||
![]() |
52b6fe006f | ||
![]() |
365d660e79 | ||
![]() |
34ec3e0ae5 | ||
![]() |
059eda861e | ||
![]() |
5ede26f162 | ||
![]() |
960bdc0c9b | ||
![]() |
81fbda49bd | ||
![]() |
e57d9f7751 | ||
![]() |
964ada87b7 | ||
![]() |
ba3670c5db | ||
![]() |
20ea9e5df7 | ||
![]() |
e1c9f3deea | ||
![]() |
dba388f723 | ||
![]() |
3d1164f09c | ||
![]() |
bc27f854f1 | ||
![]() |
cb0db95abe | ||
![]() |
8ac08bc802 | ||
![]() |
f70c0aea6c | ||
![]() |
d1adc2fed0 | ||
![]() |
1971223ad3 | ||
![]() |
3fa9896543 | ||
![]() |
7b3b717f43 | ||
![]() |
405cb36904 | ||
![]() |
0c6f8c34fb | ||
![]() |
23a2a479a5 | ||
![]() |
5a16095270 | ||
![]() |
89cb8c87ae | ||
![]() |
96d7ec7cda | ||
![]() |
47642957c8 | ||
![]() |
65780de61e | ||
![]() |
646f0bb718 | ||
![]() |
68fb35a401 | ||
![]() |
658b349755 | ||
![]() |
a4afc2e37a | ||
![]() |
205d6a8347 | ||
![]() |
89333aa55e | ||
![]() |
2a3325efd7 | ||
![]() |
3b7a206cec | ||
![]() |
912969111f | ||
![]() |
9116f5733d | ||
![]() |
0fdf980fee | ||
![]() |
8ecc41388a | ||
![]() |
356e104096 | ||
![]() |
eeba117e4b | ||
![]() |
3da55c6ab5 | ||
![]() |
faed5fbfe4 | ||
![]() |
6f738510fa | ||
![]() |
de55c13355 | ||
![]() |
38e1b16031 | ||
![]() |
06341edc49 | ||
![]() |
9fc3d9d019 | ||
![]() |
3e90db5fa3 | ||
![]() |
30555eda88 | ||
![]() |
ac38fdb9df | ||
![]() |
4c6d9602ae | ||
![]() |
811d99b68c | ||
![]() |
a983a5dbc5 | ||
![]() |
f21db486eb | ||
![]() |
e1b924a154 | ||
![]() |
421b9ec800 | ||
![]() |
e8b84c6d52 | ||
![]() |
86db23a957 | ||
![]() |
a22b62cf2a | ||
![]() |
db2f588e86 | ||
![]() |
02cf337f1a | ||
![]() |
eafd7fb296 | ||
![]() |
c0e3f8ec6b | ||
![]() |
510cc6448e | ||
![]() |
a832566715 | ||
![]() |
75ae672ef9 | ||
![]() |
570cb5d52b | ||
![]() |
afc4663870 | ||
![]() |
a5aaaf6a0a | ||
![]() |
288478978f | ||
![]() |
e719f113d9 | ||
![]() |
2aa02ff561 | ||
![]() |
0a60ec2298 | ||
![]() |
cd3136908c | ||
![]() |
6d547aaf21 | ||
![]() |
85c4c51228 | ||
![]() |
5667f06a12 | ||
![]() |
5dc9efd995 | ||
![]() |
a2ec19e10f | ||
![]() |
8047313165 | ||
![]() |
5068178aac | ||
![]() |
a597749020 | ||
![]() |
1335a74605 | ||
![]() |
027165c8ac | ||
![]() |
a512d9e910 | ||
![]() |
f385b6c80d | ||
![]() |
fdfa09ed2e | ||
![]() |
dc6ca7c774 | ||
![]() |
f40d64d68c | ||
![]() |
961f43e4a5 | ||
![]() |
2cf508e0b0 | ||
![]() |
9d3b5c9d9b | ||
![]() |
57e500b109 | ||
![]() |
d5776f750f | ||
![]() |
bfaebfe418 | ||
![]() |
9cff9cac10 | ||
![]() |
7967ab307c | ||
![]() |
b57116f39c | ||
![]() |
51f4028343 | ||
![]() |
c83a05c1ea | ||
![]() |
b102925323 | ||
![]() |
1cda7791c1 | ||
![]() |
efa956a0b5 | ||
![]() |
0a5767913a | ||
![]() |
859ffdb66d | ||
![]() |
4c65767e5a | ||
![]() |
d7d167fbf9 | ||
![]() |
6447e98caa | ||
![]() |
d8fb01ab44 | ||
![]() |
77330d03b3 | ||
![]() |
88ad376045 | ||
![]() |
ed0696ea9c | ||
![]() |
5508805f0b | ||
![]() |
d2688cab75 | ||
![]() |
06502cb93a | ||
![]() |
0e227708b9 | ||
![]() |
e46d2e2934 | ||
![]() |
caf1c2fdd1 | ||
![]() |
209a118833 | ||
![]() |
0790cd1ac9 | ||
![]() |
fb8fb09c73 | ||
![]() |
7081fb5123 | ||
![]() |
6c2126fb5f | ||
![]() |
af1581b4c5 | ||
![]() |
38542e1f0c | ||
![]() |
d87d845dbc | ||
![]() |
7be6d17b37 | ||
![]() |
981e94e84d | ||
![]() |
c04c30337f | ||
![]() |
8451f9487e | ||
![]() |
41e7235ccf | ||
![]() |
3b76238241 | ||
![]() |
796ec4a4b0 | ||
![]() |
6ee9808d42 | ||
![]() |
399d14a5e0 | ||
![]() |
42f3294523 | ||
![]() |
d609ce241e | ||
![]() |
e5a5c353c4 | ||
![]() |
0df4aa6117 | ||
![]() |
6bdf1c8b80 | ||
![]() |
421b9abc7d | ||
![]() |
b107e0e15c | ||
![]() |
fe6c8faad7 | ||
![]() |
46a2f4fd72 | ||
![]() |
3324c7e01c | ||
![]() |
8146794857 | ||
![]() |
b03c361198 | ||
![]() |
860860099e | ||
![]() |
b80c52dab2 | ||
![]() |
e0ca88b3ad | ||
![]() |
196ea97917 | ||
![]() |
4f55103a12 | ||
![]() |
9082b47687 | ||
![]() |
05da295f27 | ||
![]() |
9dc809cdb9 | ||
![]() |
09fe2172b2 | ||
![]() |
3d101116a2 | ||
![]() |
2ed5b7e7c1 | ||
![]() |
7066b40e4c | ||
![]() |
2c79094fb4 | ||
![]() |
198e2dd11f | ||
![]() |
3234777154 | ||
![]() |
0ec4cd668f | ||
![]() |
f1736024cc | ||
![]() |
29f9b2d201 | ||
![]() |
97eec435ab | ||
![]() |
b52d2967df | ||
![]() |
964b048a93 | ||
![]() |
a3ac015f84 | ||
![]() |
85ab32d752 | ||
![]() |
a1dbd18a9a | ||
![]() |
08d4e0af18 | ||
![]() |
1fbad393bf | ||
![]() |
675a3adfbe | ||
![]() |
d0fc088c06 | ||
![]() |
ca6ec8bb0f | ||
![]() |
f9b7268b9a | ||
![]() |
bec9162198 | ||
![]() |
710139bf4b | ||
![]() |
e41b5238c2 | ||
![]() |
d188821765 | ||
![]() |
19705a9c2b | ||
![]() |
addad74019 | ||
![]() |
9dc33de49f | ||
![]() |
39172f8c49 | ||
![]() |
101794c88e | ||
![]() |
bae1ab822c | ||
![]() |
f87dbc5735 | ||
![]() |
7377db312b | ||
![]() |
bb6c46180d | ||
![]() |
3ddb456970 | ||
![]() |
3fbd5e351e | ||
![]() |
9c6d28fbc4 | ||
![]() |
5966e872cf | ||
![]() |
9ca00d1c85 | ||
![]() |
16734c7423 | ||
![]() |
26b9ddf3ed | ||
![]() |
73608a73e7 | ||
![]() |
c2b090b912 | ||
![]() |
f75147c799 | ||
![]() |
5296dcfe85 | ||
![]() |
24bafceb71 | ||
![]() |
579adb0455 | ||
![]() |
3430996700 | ||
![]() |
ebc21aaa40 | ||
![]() |
a88c6f49a2 | ||
![]() |
d27d9dd5da | ||
![]() |
807837a0dc | ||
![]() |
bb6b0ff714 | ||
![]() |
1ba34b1f78 | ||
![]() |
44c325a929 | ||
![]() |
294dec59a7 | ||
![]() |
d4b257854d | ||
![]() |
9f3458fcd1 | ||
![]() |
ff658c86ff | ||
![]() |
0ab240c678 | ||
![]() |
672bfb375f | ||
![]() |
cdb9936795 | ||
![]() |
8b719778d0 | ||
![]() |
1a18ee2755 | ||
![]() |
5f5ac3834d | ||
![]() |
cdd4cabb4b | ||
![]() |
b47c5beacf | ||
![]() |
0ffb31999e | ||
![]() |
c149ac735a | ||
![]() |
bb946d9eec | ||
![]() |
ea57e71c8b | ||
![]() |
346022c48e | ||
![]() |
0a070a3fda | ||
![]() |
24c9dd0472 | ||
![]() |
9f27f75397 | ||
![]() |
35c8c70783 | ||
![]() |
6a5de599df | ||
![]() |
72a0b3520d | ||
![]() |
7acab579b4 | ||
![]() |
9de71db3cb | ||
![]() |
f3c3bb8c43 | ||
![]() |
012e0981f2 | ||
![]() |
83e34f3f95 | ||
![]() |
f83a9d7339 | ||
![]() |
5731c1fa28 | ||
![]() |
7860133709 | ||
![]() |
1783696ecb | ||
![]() |
10c07673c1 | ||
![]() |
e2ff04e40b | ||
![]() |
932d8afbed | ||
![]() |
1cf18a34b8 | ||
![]() |
7f461defc1 | ||
![]() |
4c5d85746c | ||
![]() |
b6ad4edd32 | ||
![]() |
c6030e6edc | ||
![]() |
500edbad0d | ||
![]() |
3701e022bc | ||
![]() |
be3f35c8cd | ||
![]() |
81c49628e4 | ||
![]() |
ac628787ac | ||
![]() |
6ce72444ae | ||
![]() |
710c2f1094 | ||
![]() |
1f703fbdda | ||
![]() |
76153d1e17 | ||
![]() |
ec930d2c56 | ||
![]() |
9e09d5b095 | ||
![]() |
6ad0c254b5 | ||
![]() |
a6340fb856 | ||
![]() |
21ee9b297d | ||
![]() |
da807dc508 | ||
![]() |
384d5fc8a9 | ||
![]() |
fcea1fa57d | ||
![]() |
4f99bd6811 | ||
![]() |
0256f73404 | ||
![]() |
a1b578f81e | ||
![]() |
13f8fa7e11 | ||
![]() |
c11a525a2d | ||
![]() |
d21c1bc615 | ||
![]() |
42d11c5a3f | ||
![]() |
41fe6e8021 | ||
![]() |
c3e35a27ba | ||
![]() |
89464c16ff | ||
![]() |
9c2f6e591d | ||
![]() |
3c95559f33 | ||
![]() |
50ed7678a1 | ||
![]() |
8649c5352b | ||
![]() |
31bc099cef | ||
![]() |
447dd6640f | ||
![]() |
31d2b6ffe1 | ||
![]() |
aced689207 | ||
![]() |
3736d45318 | ||
![]() |
5749e2f07c | ||
![]() |
783f356679 | ||
![]() |
85d58ba134 | ||
![]() |
8b8ba5875f | ||
![]() |
9131a7c7e3 | ||
![]() |
3e0193c704 | ||
![]() |
40731152e9 | ||
![]() |
811e9e2a0e | ||
![]() |
4029508b3f | ||
![]() |
6fd8ad52b0 | ||
![]() |
fbe44598ac | ||
![]() |
58b2a28fe5 | ||
![]() |
0b47d1f6a5 | ||
![]() |
d6fd21521c | ||
![]() |
e9dfa79f36 | ||
![]() |
728d781843 | ||
![]() |
f2358acf2d | ||
![]() |
c8e4ac422b | ||
![]() |
48b0857edb | ||
![]() |
c06be58a33 | ||
![]() |
7231976af6 | ||
![]() |
ea16ebd4f0 | ||
![]() |
0b9bd62251 | ||
![]() |
1d13126bb5 | ||
![]() |
27d343b488 | ||
![]() |
0a091a272c | ||
![]() |
5728d8ad1b | ||
![]() |
bdea42f0b7 | ||
![]() |
fdcc73c6cc | ||
![]() |
9e2396375e | ||
![]() |
5085c78f7e | ||
![]() |
eeb949a081 | ||
![]() |
0fd84a2f8d | ||
![]() |
4379df0d5c | ||
![]() |
cf4d867fa1 | ||
![]() |
b3ded276b5 | ||
![]() |
38088acf14 | ||
![]() |
1b60a93fcc | ||
![]() |
e7df8cb195 | ||
![]() |
440145ab1b | ||
![]() |
0c840e1751 | ||
![]() |
36c658096a | ||
![]() |
34fd3e4899 | ||
![]() |
b16bc88eb5 | ||
![]() |
60ac82edc5 | ||
![]() |
f0f1a56537 | ||
![]() |
097a8cfdc6 | ||
![]() |
1b66492db9 | ||
![]() |
e202f08193 | ||
![]() |
6b180988fd | ||
![]() |
c8c21e6fac | ||
![]() |
af8f77779b | ||
![]() |
1aa1ac709d | ||
![]() |
91fadccf33 | ||
![]() |
0904af2ad2 | ||
![]() |
aa5ff72710 | ||
![]() |
f8261d93d3 | ||
![]() |
f385c7e7d5 | ||
![]() |
788650f8e5 | ||
![]() |
0018a9a9c8 | ||
![]() |
bf126b6c5e | ||
![]() |
c18247cd6b | ||
![]() |
fa2cc68139 | ||
![]() |
8dde92d572 | ||
![]() |
baccd6fb88 | ||
![]() |
b99d9923ea | ||
![]() |
9d0e89e792 | ||
![]() |
13f5e33087 | ||
![]() |
a061a58494 | ||
![]() |
9b3448d44c | ||
![]() |
9ae2325834 | ||
![]() |
688de2ff2d | ||
![]() |
e0a63a2ee3 | ||
![]() |
2765c88d3f | ||
![]() |
b092cdd04d | ||
![]() |
a723c62f4f | ||
![]() |
c1e7f4cc77 | ||
![]() |
b0837059d4 | ||
![]() |
8dc81b1daa | ||
![]() |
77cc77396b | ||
![]() |
7303e55f63 | ||
![]() |
b73c3ed233 | ||
![]() |
f9cd2d9612 | ||
![]() |
90e6f59a74 | ||
![]() |
27046b00c6 | ||
![]() |
7cfa694980 | ||
![]() |
640e6eb1ef | ||
![]() |
7d20d8fe71 | ||
![]() |
2680a3f7e3 | ||
![]() |
326fa00365 | ||
![]() |
8054aa744e | ||
![]() |
10ddb7faac | ||
![]() |
288ffad23a | ||
![]() |
9aaf50b089 | ||
![]() |
de5a33d1c7 | ||
![]() |
edf8cbb95b | ||
![]() |
70d09641f8 | ||
![]() |
bc94dce8f7 | ||
![]() |
48ecfe07a2 | ||
![]() |
958c5bf935 | ||
![]() |
db0438dd4d | ||
![]() |
3ba15cb7b5 | ||
![]() |
69eb10c6dd | ||
![]() |
508e1fd737 | ||
![]() |
0707528bd7 | ||
![]() |
28457747e7 | ||
![]() |
5f5a62d094 | ||
![]() |
80a11206af | ||
![]() |
c330b87506 | ||
![]() |
a960559639 | ||
![]() |
6c2cd420f5 | ||
![]() |
95288f8c3d | ||
![]() |
2cfda880ac | ||
![]() |
713117d4d9 | ||
![]() |
db630677a4 | ||
![]() |
3a100bff23 | ||
![]() |
ea4fd25330 | ||
![]() |
0dd1d4f478 | ||
![]() |
57ecbf27ca | ||
![]() |
3d90d1d016 | ||
![]() |
3412edb843 | ||
![]() |
7e77a7c32c | ||
![]() |
cb5c9b3f3f | ||
![]() |
8d790e9601 | ||
![]() |
2ff0be8529 | ||
![]() |
0b9e7d5fa2 | ||
![]() |
e5974ab71b | ||
![]() |
5a65fd7526 | ||
![]() |
4f18bdf0ea | ||
![]() |
fbc9755796 | ||
![]() |
d658beacea | ||
![]() |
01fab1075e | ||
![]() |
415b0b127f | ||
![]() |
1a71ee5af3 | ||
![]() |
1c2d713846 | ||
![]() |
b15edbd4fd | ||
![]() |
d79ae551b2 | ||
![]() |
6074de356c | ||
![]() |
72f22f6214 | ||
![]() |
c0df1e2a89 | ||
![]() |
1af77e682d | ||
![]() |
7db89d5bc2 | ||
![]() |
ef5155984f | ||
![]() |
8f0ebcb69d | ||
![]() |
70c082716f | ||
![]() |
3ff9fe1041 | ||
![]() |
41e97a6f83 | ||
![]() |
8078158a56 | ||
![]() |
57997be342 | ||
![]() |
6fac4e9027 | ||
![]() |
de87c5b19b | ||
![]() |
8cf0c0e94d | ||
![]() |
675a7a3b86 | ||
![]() |
a4bcf062d5 | ||
![]() |
056e9e0d74 | ||
![]() |
926c46b701 | ||
![]() |
1314caba97 | ||
![]() |
1ab551116e | ||
![]() |
1f9fc46576 | ||
![]() |
3701683d4b | ||
![]() |
f106767eae | ||
![]() |
b7d2b2fcfd | ||
![]() |
508b5d6d77 | ||
![]() |
adac8e55d7 | ||
![]() |
a2612af6a9 | ||
![]() |
67e040ad8e | ||
![]() |
152df2297a | ||
![]() |
6de540a0b1 | ||
![]() |
583abedd34 | ||
![]() |
86128d54b4 | ||
![]() |
f81429702c | ||
![]() |
5722b6bbdb | ||
![]() |
bac3d8c17e | ||
![]() |
9f6edeec6e | ||
![]() |
11f4f3b3c9 | ||
![]() |
4f4224953f | ||
![]() |
f600a8e7f4 | ||
![]() |
aa389bf206 | ||
![]() |
a8b5d07d66 | ||
![]() |
f16886f63d | ||
![]() |
d1325da6e6 | ||
![]() |
95031fdd79 | ||
![]() |
fe439723ee | ||
![]() |
798a2bbd34 | ||
![]() |
0dc3bc7926 | ||
![]() |
3025b6049f | ||
![]() |
ba5f401890 | ||
![]() |
db9dc653e0 | ||
![]() |
9b0f4fa234 | ||
![]() |
2596d2ba52 | ||
![]() |
904c3db3ea | ||
![]() |
90b80880ed | ||
![]() |
d2faeaffe7 | ||
![]() |
6959b1849f | ||
![]() |
c3a6495eb1 | ||
![]() |
fdf2fa3d3f | ||
![]() |
551cbe5afa | ||
![]() |
a574bf8e0f | ||
![]() |
28a0f2405e | ||
![]() |
d2e9918f88 |
@@ -1,6 +1,4 @@
|
||||
node_modules
|
||||
bower_components
|
||||
hass_frontend
|
||||
build
|
||||
build-temp
|
||||
hass_frontend_es5
|
||||
.git
|
||||
|
@@ -9,16 +9,25 @@
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "h"
|
||||
},
|
||||
"import/resolver": {
|
||||
"webpack": {
|
||||
"config": "webpack.config.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"__DEV__": false,
|
||||
"__DEMO__": false,
|
||||
"__BUILD__": false,
|
||||
"__VERSION__": false,
|
||||
"__STATIC_PATH__": false,
|
||||
"Polymer": true,
|
||||
"webkitSpeechRecognition": false,
|
||||
"ResizeObserver": false
|
||||
},
|
||||
"env": {
|
||||
"browser": true
|
||||
"browser": true,
|
||||
"mocha": true
|
||||
},
|
||||
"rules": {
|
||||
"class-methods-use-this": 0,
|
||||
@@ -40,10 +49,15 @@
|
||||
"no-multi-assign": 0,
|
||||
"radix": 0,
|
||||
"no-alert": 0,
|
||||
"no-return-await": 0,
|
||||
"prefer-destructuring": 0,
|
||||
"no-restricted-globals": 0,
|
||||
"no-restricted-globals": [2, "event"],
|
||||
"prefer-promise-reject-errors": 0,
|
||||
"import/prefer-default-export": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"import/extensions": [2, "ignorePackages"],
|
||||
"object-curly-newline": 0,
|
||||
"default-case": 0,
|
||||
"react/jsx-no-bind": [2, { "ignoreRefs": true }],
|
||||
"react/jsx-no-duplicate-props": 2,
|
||||
"react/self-closing-comp": 2,
|
||||
@@ -56,10 +70,10 @@
|
||||
"react/jsx-curly-spacing": 2,
|
||||
"react/jsx-no-undef": 2,
|
||||
"react/jsx-uses-react": 2,
|
||||
"react/jsx-uses-vars": 2
|
||||
"react/jsx-uses-vars": 2,
|
||||
"no-restricted-syntax": [0, "ForOfStatement"]
|
||||
},
|
||||
"plugins": [
|
||||
"html",
|
||||
"react"
|
||||
]
|
||||
}
|
14
.eslintrc.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./.eslintrc-hound.json",
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"import/no-unresolved": 2,
|
||||
"linebreak-style": 0
|
||||
}
|
||||
}
|
33
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
<!-- READ THIS FIRST:
|
||||
- If you need additional help with this template please refer to https://www.home-assistant.io/help/reporting_issues/
|
||||
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
|
||||
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
|
||||
- Provide as many details as possible. Do not delete any text from this template!
|
||||
-->
|
||||
|
||||
**Home Assistant release with the issue:**
|
||||
<!--
|
||||
- Frontend -> Developer tools -> Info
|
||||
- Or use this command: hass --version
|
||||
-->
|
||||
|
||||
**Last working Home Assistant release (if known):**
|
||||
|
||||
|
||||
**Browser and Operating System:**
|
||||
<!--
|
||||
Provide details about what browser (and version) you are seeing the issue in. And also which operating system this is on. If possible try to replicate the issue in other browsers and include your findings here.
|
||||
-->
|
||||
|
||||
**Description of problem:**
|
||||
<!--
|
||||
Explain what the issue is, and how things should look/behave. If possible provide a screenshot with a description.
|
||||
-->
|
||||
|
||||
|
||||
**Javascript errors shown in the web inspector (if applicable):**
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
**Additional information:**
|
13
.github/move.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Configuration for move-issues - https://github.com/dessant/move-issues
|
||||
|
||||
# Delete the command comment. Ignored when the comment also contains other content
|
||||
deleteCommand: true
|
||||
# Close the source issue after moving
|
||||
closeSourceIssue: true
|
||||
# Lock the source issue after moving
|
||||
lockSourceIssue: false
|
||||
# Set custom aliases for targets
|
||||
# aliases:
|
||||
# r: repo
|
||||
# or: owner/repo
|
||||
|
4
.github/release-drafter.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
template: |
|
||||
## What's Changed
|
||||
|
||||
$CHANGES
|
11
.gitignore
vendored
@@ -1,10 +1,11 @@
|
||||
build/*
|
||||
build-temp/*
|
||||
build
|
||||
build-translations/*
|
||||
node_modules/*
|
||||
bower_components/*
|
||||
npm-debug.log
|
||||
.DS_Store
|
||||
hass_frontend/*
|
||||
hass_frontend_es5/*
|
||||
.reify-cache
|
||||
|
||||
# Python stuff
|
||||
*.py[cod]
|
||||
@@ -19,3 +20,7 @@ venv
|
||||
lib
|
||||
bin
|
||||
dist
|
||||
|
||||
# Secrets
|
||||
.lokalise_token
|
||||
yarn-error.log
|
||||
|
@@ -3,4 +3,4 @@ jshint:
|
||||
|
||||
eslint:
|
||||
enabled: true
|
||||
config_file: .eslintrc
|
||||
config_file: .eslintrc-hound.json
|
||||
|
28
.travis.yml
@@ -3,22 +3,24 @@ language: node_js
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
- bower_components
|
||||
install:
|
||||
- yarn install
|
||||
- ./node_modules/.bin/bower install
|
||||
addons:
|
||||
firefox: latest
|
||||
apt:
|
||||
sources:
|
||||
- google-chrome
|
||||
packages:
|
||||
- google-chrome-stable
|
||||
- bower_components
|
||||
install: yarn install
|
||||
script:
|
||||
- npm run build
|
||||
- hassio/script/build_hassio
|
||||
- npm run test
|
||||
- xvfb-run wct
|
||||
- if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct --plugin sauce; fi
|
||||
# - xvfb-run wct --module-resolution=node --npm
|
||||
# - 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct --module-resolution=node --npm --plugin sauce; fi'
|
||||
services:
|
||||
- docker
|
||||
before_deploy:
|
||||
- 'docker pull lokalise/lokalise-cli@sha256:2198814ebddfda56ee041a4b427521757dd57f75415ea9693696a64c550cef21'
|
||||
deploy:
|
||||
provider: script
|
||||
script: script/travis_deploy
|
||||
'on':
|
||||
branch: master
|
||||
dist: trusty
|
||||
addons:
|
||||
sauce_connect: true
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
include README.md
|
||||
include LICENSE.md
|
||||
graft hass_frontend
|
||||
graft hass_frontend_es5
|
||||
recursive-exclude * *.py[co]
|
||||
|
22
README.md
@@ -1,17 +1,21 @@
|
||||
# Home Assistant Polymer [](https://travis-ci.org/home-assistant/home-assistant-polymer)
|
||||
|
||||
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend. The frontend is built on top of the following technologies:
|
||||
|
||||
* [Websockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)
|
||||
* [Polymer](https://www.polymer-project.org/)
|
||||
* [Rollup](http://rollupjs.org/) to package Home Assistant JS
|
||||
* [Bower](https://bower.io) for Polymer package management
|
||||
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
|
||||
|
||||
[](https://home-assistant.io/demo/)
|
||||
|
||||
[View demo of the Polymer frontend](https://home-assistant.io/demo/)
|
||||
[More information about Home Assistant](https://home-assistant.io)
|
||||
[Frontend development instructions](https://home-assistant.io/developers/frontend/)
|
||||
- [View demo of the Polymer frontend](https://home-assistant.io/demo/)
|
||||
- [More information about Home Assistant](https://home-assistant.io)
|
||||
- [Frontend development instructions](https://developers.home-assistant.io/docs/en/frontend_index.html)
|
||||
|
||||
## Development
|
||||
|
||||
- Initial setup: `script/setup`
|
||||
- Development: [Instructions](https://developers.home-assistant.io/docs/en/frontend_development.html)
|
||||
- Production build: `script/build_frontend`
|
||||
- Gallery: `cd gallery && script/develop_gallery`
|
||||
- Hass.io: [Instructions](https://developers.home-assistant.io/docs/en/hassio_hass.html)
|
||||
|
||||
## License
|
||||
|
||||
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.
|
||||
|
62
bower.json
@@ -1,62 +0,0 @@
|
||||
{
|
||||
"name": "home-assistant",
|
||||
"version": "0.1.0",
|
||||
"authors": [
|
||||
"Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
|
||||
],
|
||||
"main": "src/home-assistant.html",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"app-layout": "^2.0.0",
|
||||
"app-localize-behavior": "PolymerElements/app-localize-behavior#~2.0.0",
|
||||
"app-route": "PolymerElements/app-route#^2.0.0",
|
||||
"app-storage": "^2.0.2",
|
||||
"fecha": "~2.3.0",
|
||||
"font-roboto-local": "~1.0.1",
|
||||
"font-roboto": "PolymerElements/font-roboto-local#~1.0.1",
|
||||
"google-apis": "GoogleWebComponents/google-apis#~2.0.0",
|
||||
"iron-autogrow-textarea": "PolymerElements/iron-autogrow-textarea#^2.0.0",
|
||||
"iron-flex-layout": "PolymerElements/iron-flex-layout#^2.0.0",
|
||||
"iron-icon": "PolymerElements/iron-icon#^2.0.0",
|
||||
"iron-image": "PolymerElements/iron-image#^2.1.1",
|
||||
"iron-input": "PolymerElements/iron-input#^2.0.0",
|
||||
"iron-media-query": "PolymerElements/iron-media-query#^2.0.0",
|
||||
"iron-pages": "PolymerElements/iron-pages#^2.0.0",
|
||||
"leaflet": "^1.0.2",
|
||||
"neon-animation": "PolymerElements/neon-animation#^2.0.1",
|
||||
"paper-button": "PolymerElements/paper-button#^2.0.0",
|
||||
"paper-card": "PolymerElements/paper-card#^2.0.0",
|
||||
"paper-checkbox": "PolymerElements/paper-checkbox#^2.0.0",
|
||||
"paper-dialog": "PolymerElements/paper-dialog#^2.0.0",
|
||||
"paper-dialog-scrollable": "PolymerElements/paper-dialog-scrollable#^2.1.0",
|
||||
"paper-drawer-panel": "PolymerElements/paper-drawer-panel#^2.0.0",
|
||||
"paper-dropdown-menu": "PolymerElements/paper-dropdown-menu#^2.0.0",
|
||||
"paper-fab": "PolymerElements/paper-fab#^2.0.0",
|
||||
"paper-icon-button": "PolymerElements/paper-icon-button#^2.0.0",
|
||||
"paper-input": "PolymerElements/paper-input#^2.0.1",
|
||||
"paper-item": "PolymerElements/paper-item#^2.0.0",
|
||||
"paper-listbox": "PolymerElements/paper-listbox#^2.0.0",
|
||||
"paper-material": "PolymerElements/paper-material#^2.0.0",
|
||||
"paper-menu-button": "PolymerElements/paper-menu-button#^2.0.0",
|
||||
"paper-progress": "PolymerElements/paper-progress#^2.0.1",
|
||||
"paper-radio-button": "PolymerElements/paper-radio-button#^2.0.0",
|
||||
"paper-radio-group": "PolymerElements/paper-radio-group#^2.0.0",
|
||||
"paper-scroll-header-panel": "~2.0.0",
|
||||
"paper-slider": "PolymerElements/paper-slider#^2.0.1",
|
||||
"paper-spinner": "PolymerElements/paper-spinner#^2.0.0",
|
||||
"paper-styles": "PolymerElements/paper-styles#^2.0.0",
|
||||
"paper-tabs": "PolymerElements/paper-tabs#^2.0.0",
|
||||
"paper-time-input": "ryanburns23/paper-time-input#^2.0.4",
|
||||
"paper-toast": "PolymerElements/paper-toast#^2.0.0",
|
||||
"paper-toggle-button": "PolymerElements/paper-toggle-button#^2.0.0",
|
||||
"polymer": "^2.1.1",
|
||||
"vaadin-combo-box": "vaadin/vaadin-combo-box#^2.0.0",
|
||||
"vaadin-date-picker": "vaadin/vaadin-date-picker#^2.0.0",
|
||||
"web-animations-js": "^2.2.5",
|
||||
"webcomponentsjs": "^1.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"web-component-tester": "^6.3.0"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 232 KiB After Width: | Height: | Size: 226 KiB |
6
gallery/.eslintrc.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../.eslintrc.json",
|
||||
"rules": {
|
||||
"no-console": 0
|
||||
}
|
||||
}
|
BIN
gallery/public/api/media_player_proxy/media_player.bedroom
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
gallery/public/api/media_player_proxy/media_player.living_room
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
gallery/public/api/media_player_proxy/media_player.walkman
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
gallery/public/images/bed.png
Normal file
After Width: | Height: | Size: 111 KiB |
BIN
gallery/public/images/divider.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
gallery/public/images/floorplan.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
gallery/public/images/kitchen.png
Normal file
After Width: | Height: | Size: 115 KiB |
BIN
gallery/public/images/light_bulb_off.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
gallery/public/images/light_bulb_on.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
gallery/public/images/living_room.png
Normal file
After Width: | Height: | Size: 115 KiB |
18
gallery/public/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#2157BC">
|
||||
<title>HAGallery</title>
|
||||
<script src='./main.js' async></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Roboto, Noto, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
17
gallery/script/build_gallery
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
# Run the gallery
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
OUTPUT_DIR=dist
|
||||
|
||||
rm -rf $OUTPUT_DIR
|
||||
|
||||
cd ..
|
||||
./node_modules/.bin/gulp build-translations gen-icons
|
||||
cd gallery
|
||||
|
||||
NODE_ENV=production ../node_modules/.bin/webpack -p --config webpack.config.js
|
13
gallery/script/develop_gallery
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
# Run the gallery
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
cd ..
|
||||
./node_modules/.bin/gulp build-translations gen-icons
|
||||
cd gallery
|
||||
|
||||
../node_modules/.bin/webpack-dev-server
|
95
gallery/src/components/demo-card.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import JsYaml from 'js-yaml';
|
||||
|
||||
import HomeAssistant from '../data/hass.js';
|
||||
import demoConfig from '../data/demo_config.js';
|
||||
import demoResources from '../data/demo_resources.js';
|
||||
import demoStates from '../data/demo_states.js';
|
||||
import createCardElement from '../../../src/panels/lovelace/common/create-card-element.js';
|
||||
|
||||
class DemoCard extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
.root {
|
||||
display: flex;
|
||||
}
|
||||
h2 {
|
||||
margin: 0 0 20px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
#card {
|
||||
width: 400px;
|
||||
}
|
||||
pre {
|
||||
width: 400px;
|
||||
margin: 16px;
|
||||
overflow: auto;
|
||||
}
|
||||
@media only screen and (max-width: 800px) {
|
||||
.root {
|
||||
flex-direction: column;
|
||||
}
|
||||
pre {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<h2>[[config.heading]]</h2>
|
||||
<div class='root'>
|
||||
<div id="card"></div>
|
||||
<template is='dom-if' if='[[showConfig]]'>
|
||||
<pre>[[_trim(config.config)]]</pre>
|
||||
</template>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
observer: '_hassChanged',
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
observer: '_configChanged'
|
||||
},
|
||||
showConfig: Boolean,
|
||||
};
|
||||
}
|
||||
|
||||
_configChanged(config) {
|
||||
const card = this.$.card;
|
||||
while (card.lastChild) {
|
||||
card.removeChild(card.lastChild);
|
||||
}
|
||||
|
||||
const el = createCardElement(JsYaml.safeLoad(config.config)[0]);
|
||||
|
||||
if (this.hass) {
|
||||
el.hass = this.hass;
|
||||
} else {
|
||||
const hass = new HomeAssistant(demoStates);
|
||||
hass.config = demoConfig;
|
||||
hass.resources = demoResources;
|
||||
hass.language = 'en';
|
||||
hass.states = demoStates;
|
||||
el.hass = hass;
|
||||
}
|
||||
|
||||
card.appendChild(el);
|
||||
}
|
||||
|
||||
_hassChanged(hass) {
|
||||
const card = this.$.card.lastChild;
|
||||
if (card) card.hass = hass;
|
||||
}
|
||||
|
||||
_trim(config) {
|
||||
return config.trim();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-card', DemoCard);
|
58
gallery/src/components/demo-cards.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/paper-toggle-button/paper-toggle-button.js';
|
||||
|
||||
import './demo-card.js';
|
||||
|
||||
class DemoCards extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
demo-card {
|
||||
margin: 16px 16px 32px;
|
||||
}
|
||||
app-toolbar {
|
||||
background-color: var(--light-primary-color);
|
||||
}
|
||||
.filters {
|
||||
margin-left: 60px;
|
||||
}
|
||||
</style>
|
||||
<app-toolbar>
|
||||
<div class='filters'>
|
||||
<paper-toggle-button
|
||||
checked='{{_showConfig}}'
|
||||
>Show config</paper-toggle-button>
|
||||
</div>
|
||||
</app-toolbar>
|
||||
<div class='cards'>
|
||||
<template is='dom-repeat' items='[[configs]]'>
|
||||
<demo-card
|
||||
config='[[item]]'
|
||||
show-config='[[_showConfig]]'
|
||||
hass='[[hass]]'
|
||||
></demo-card>
|
||||
</template>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
configs: Object,
|
||||
hass: Object,
|
||||
_showConfig: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-cards', DemoCards);
|
93
gallery/src/components/demo-more-info.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/state-summary/state-card-content.js';
|
||||
import '../../../src/dialogs/more-info/controls/more-info-content.js';
|
||||
import '../../../src/components/ha-card.js';
|
||||
|
||||
|
||||
class DemoMoreInfo extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
width: 333px;
|
||||
}
|
||||
|
||||
state-card-content {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
more-info-content {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
pre {
|
||||
width: 400px;
|
||||
margin: 16px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
:host {
|
||||
flex-direction: column;
|
||||
}
|
||||
pre {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<ha-card>
|
||||
<state-card-content
|
||||
state-obj="[[_stateObj]]"
|
||||
hass="[[hass]]"
|
||||
in-dialog
|
||||
></state-card-content>
|
||||
|
||||
<more-info-content
|
||||
hass='[[hass]]'
|
||||
state-obj='[[_stateObj]]'
|
||||
></more-info-content>
|
||||
</ha-card>
|
||||
<template is='dom-if' if='[[showConfig]]'>
|
||||
<pre>[[_jsonEntity(_stateObj)]]</pre>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
entityId: String,
|
||||
showConfig: Boolean,
|
||||
_stateObj: {
|
||||
type: Object,
|
||||
computed: '_getState(entityId, hass.states)'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_getState(entityId, states) {
|
||||
return states[entityId];
|
||||
}
|
||||
|
||||
_jsonEntity(stateObj) {
|
||||
// We are caching some things on stateObj
|
||||
// (it sucks, we will remove in the future)
|
||||
const tmp = {};
|
||||
Object.keys(stateObj).forEach((key) => {
|
||||
if (key[0] !== '_') {
|
||||
tmp[key] = stateObj[key];
|
||||
}
|
||||
});
|
||||
return JSON.stringify(tmp, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-more-info', DemoMoreInfo);
|
58
gallery/src/components/demo-more-infos.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/paper-toggle-button/paper-toggle-button.js';
|
||||
|
||||
import './demo-more-info.js';
|
||||
|
||||
class DemoMoreInfos extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
demo-more-info {
|
||||
margin: 16px 16px 32px;
|
||||
}
|
||||
app-toolbar {
|
||||
background-color: var(--light-primary-color);
|
||||
}
|
||||
.filters {
|
||||
margin-left: 60px;
|
||||
}
|
||||
</style>
|
||||
<app-toolbar>
|
||||
<div class='filters'>
|
||||
<paper-toggle-button
|
||||
checked='{{_showConfig}}'
|
||||
>Show entity</paper-toggle-button>
|
||||
</div>
|
||||
</app-toolbar>
|
||||
<div class='cards'>
|
||||
<template is='dom-repeat' items='[[entities]]'>
|
||||
<demo-more-info
|
||||
entity-id='[[item]]'
|
||||
show-config='[[_showConfig]]'
|
||||
hass='[[hass]]'
|
||||
></demo-more-info>
|
||||
</template>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
entities: Array,
|
||||
hass: Object,
|
||||
_showConfig: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-more-infos', DemoMoreInfos);
|
175
gallery/src/data/demo_config.js
Normal file
@@ -0,0 +1,175 @@
|
||||
export default {
|
||||
core: {
|
||||
elevation: 300,
|
||||
latitude: 51.5287352,
|
||||
longitude: -0.381773,
|
||||
unit_system: {
|
||||
length: 'km',
|
||||
mass: 'kg',
|
||||
temperature: '°C',
|
||||
volume: 'L'
|
||||
}
|
||||
},
|
||||
services: {
|
||||
configurator: [
|
||||
'configure'
|
||||
],
|
||||
tts: [
|
||||
'demo_say',
|
||||
'clear_cache'
|
||||
],
|
||||
cover: [
|
||||
'open_cover',
|
||||
'close_cover',
|
||||
'open_cover_tilt',
|
||||
'close_cover_tilt',
|
||||
'set_cover_tilt_position',
|
||||
'set_cover_position',
|
||||
'stop_cover_tilt',
|
||||
'stop_cover'
|
||||
],
|
||||
group: [
|
||||
'set',
|
||||
'reload',
|
||||
'remove',
|
||||
'set_visibility'
|
||||
],
|
||||
alarm_control_panel: [
|
||||
'alarm_arm_night',
|
||||
'alarm_disarm',
|
||||
'alarm_trigger',
|
||||
'alarm_arm_home',
|
||||
'alarm_arm_away',
|
||||
'alarm_arm_custom_bypass'
|
||||
],
|
||||
conversation: [
|
||||
'process'
|
||||
],
|
||||
notify: [
|
||||
'demo_test_target_name',
|
||||
'notify'
|
||||
],
|
||||
lock: [
|
||||
'open',
|
||||
'lock',
|
||||
'unlock'
|
||||
],
|
||||
input_select: [
|
||||
'select_previous',
|
||||
'set_options',
|
||||
'select_next',
|
||||
'select_option'
|
||||
],
|
||||
recorder: [
|
||||
'purge'
|
||||
],
|
||||
persistent_notification: [
|
||||
'create',
|
||||
'dismiss'
|
||||
],
|
||||
timer: [
|
||||
'pause',
|
||||
'cancel',
|
||||
'finish',
|
||||
'start'
|
||||
],
|
||||
input_boolean: [
|
||||
'turn_off',
|
||||
'toggle',
|
||||
'turn_on'
|
||||
],
|
||||
fan: [
|
||||
'set_speed',
|
||||
'turn_on',
|
||||
'turn_off',
|
||||
'set_direction',
|
||||
'oscillate',
|
||||
'toggle'
|
||||
],
|
||||
climate: [
|
||||
'set_humidity',
|
||||
'set_operation_mode',
|
||||
'set_aux_heat',
|
||||
'turn_on',
|
||||
'set_hold_mode',
|
||||
'set_away_mode',
|
||||
'turn_off',
|
||||
'set_fan_mode',
|
||||
'set_temperature',
|
||||
'set_swing_mode'
|
||||
],
|
||||
switch: [
|
||||
'turn_off',
|
||||
'toggle',
|
||||
'turn_on'
|
||||
],
|
||||
script: [
|
||||
'turn_off',
|
||||
'demo',
|
||||
'reload',
|
||||
'toggle',
|
||||
'turn_on'
|
||||
],
|
||||
scene: [
|
||||
'turn_on'
|
||||
],
|
||||
system_log: [
|
||||
'clear',
|
||||
'write'
|
||||
],
|
||||
camera: [
|
||||
'disable_motion_detection',
|
||||
'enable_motion_detection',
|
||||
'snapshot'
|
||||
],
|
||||
image_processing: [
|
||||
'scan'
|
||||
],
|
||||
media_player: [
|
||||
'media_previous_track',
|
||||
'clear_playlist',
|
||||
'shuffle_set',
|
||||
'media_seek',
|
||||
'turn_on',
|
||||
'media_play_pause',
|
||||
'media_next_track',
|
||||
'media_pause',
|
||||
'volume_down',
|
||||
'volume_set',
|
||||
'media_stop',
|
||||
'toggle',
|
||||
'media_play',
|
||||
'play_media',
|
||||
'volume_mute',
|
||||
'turn_off',
|
||||
'select_sound_mode',
|
||||
'select_source',
|
||||
'volume_up'
|
||||
],
|
||||
input_number: [
|
||||
'set_value',
|
||||
'increment',
|
||||
'decrement'
|
||||
],
|
||||
device_tracker: [
|
||||
'see'
|
||||
],
|
||||
homeassistant: [
|
||||
'stop',
|
||||
'check_config',
|
||||
'reload_core_config',
|
||||
'turn_on',
|
||||
'turn_off',
|
||||
'restart',
|
||||
'toggle'
|
||||
],
|
||||
light: [
|
||||
'turn_off',
|
||||
'toggle',
|
||||
'turn_on'
|
||||
],
|
||||
input_text: [
|
||||
'set_value'
|
||||
]
|
||||
}
|
||||
};
|
253
gallery/src/data/demo_resources.js
Normal file
@@ -0,0 +1,253 @@
|
||||
export default {
|
||||
en: {
|
||||
'state.default.off': 'Off',
|
||||
'state.default.on': 'On',
|
||||
'state.default.unknown': 'Unknown',
|
||||
'state.default.unavailable': 'Unavailable',
|
||||
'state.alarm_control_panel.armed': 'Armed',
|
||||
'state.alarm_control_panel.disarmed': 'Disarmed',
|
||||
'state.alarm_control_panel.armed_home': 'Armed home',
|
||||
'state.alarm_control_panel.armed_away': 'Armed away',
|
||||
'state.alarm_control_panel.armed_night': 'Armed night',
|
||||
'state.alarm_control_panel.armed_custom_bypass': 'Armed custom bypass',
|
||||
'state.alarm_control_panel.pending': 'Pending',
|
||||
'state.alarm_control_panel.arming': 'Arming',
|
||||
'state.alarm_control_panel.disarming': 'Disarming',
|
||||
'state.alarm_control_panel.triggered': 'Triggered',
|
||||
'state.automation.off': 'Off',
|
||||
'state.automation.on': 'On',
|
||||
'state.binary_sensor.default.off': 'Off',
|
||||
'state.binary_sensor.default.on': 'On',
|
||||
'state.binary_sensor.battery.off': 'Normal',
|
||||
'state.binary_sensor.battery.on': 'Low',
|
||||
'state.binary_sensor.cold.off': 'Normal',
|
||||
'state.binary_sensor.cold.on': 'Cold',
|
||||
'state.binary_sensor.connectivity.off': 'Disconnected',
|
||||
'state.binary_sensor.connectivity.on': 'Connected',
|
||||
'state.binary_sensor.door.off': 'Closed',
|
||||
'state.binary_sensor.door.on': 'Open',
|
||||
'state.binary_sensor.garage_door.off': 'Closed',
|
||||
'state.binary_sensor.garage_door.on': 'Open',
|
||||
'state.binary_sensor.gas.off': 'Clear',
|
||||
'state.binary_sensor.gas.on': 'Detected',
|
||||
'state.binary_sensor.heat.off': 'Normal',
|
||||
'state.binary_sensor.heat.on': 'Hot',
|
||||
'state.binary_sensor.lock.off': 'Locked',
|
||||
'state.binary_sensor.lock.on': 'Unlocked',
|
||||
'state.binary_sensor.moisture.off': 'Dry',
|
||||
'state.binary_sensor.moisture.on': 'Wet',
|
||||
'state.binary_sensor.motion.off': 'Clear',
|
||||
'state.binary_sensor.motion.on': 'Detected',
|
||||
'state.binary_sensor.occupancy.off': 'Clear',
|
||||
'state.binary_sensor.occupancy.on': 'Detected',
|
||||
'state.binary_sensor.opening.off': 'Closed',
|
||||
'state.binary_sensor.opening.on': 'Open',
|
||||
'state.binary_sensor.presence.off': 'Away',
|
||||
'state.binary_sensor.presence.on': 'Home',
|
||||
'state.binary_sensor.problem.off': 'OK',
|
||||
'state.binary_sensor.problem.on': 'Problem',
|
||||
'state.binary_sensor.safety.off': 'Safe',
|
||||
'state.binary_sensor.safety.on': 'Unsafe',
|
||||
'state.binary_sensor.smoke.off': 'Clear',
|
||||
'state.binary_sensor.smoke.on': 'Detected',
|
||||
'state.binary_sensor.sound.off': 'Clear',
|
||||
'state.binary_sensor.sound.on': 'Detected',
|
||||
'state.binary_sensor.vibration.off': 'Clear',
|
||||
'state.binary_sensor.vibration.on': 'Detected',
|
||||
'state.binary_sensor.window.off': 'Closed',
|
||||
'state.binary_sensor.window.on': 'Open',
|
||||
'state.calendar.off': 'Off',
|
||||
'state.calendar.on': 'On',
|
||||
'state.camera.recording': 'Recording',
|
||||
'state.camera.streaming': 'Streaming',
|
||||
'state.camera.idle': 'Idle',
|
||||
'state.climate.off': 'Off',
|
||||
'state.climate.on': 'On',
|
||||
'state.climate.heat': 'Heat',
|
||||
'state.climate.cool': 'Cool',
|
||||
'state.climate.idle': 'Idle',
|
||||
'state.climate.auto': 'Auto',
|
||||
'state.climate.dry': 'Dry',
|
||||
'state.climate.fan_only': 'Fan only',
|
||||
'state.climate.eco': 'Eco',
|
||||
'state.climate.electric': 'Electric',
|
||||
'state.climate.performance': 'Performance',
|
||||
'state.climate.high_demand': 'High demand',
|
||||
'state.climate.heat_pump': 'Heat pump',
|
||||
'state.climate.gas': 'Gas',
|
||||
'state.configurator.configure': 'Configure',
|
||||
'state.configurator.configured': 'Configured',
|
||||
'state.cover.open': 'Open',
|
||||
'state.cover.opening': 'Opening',
|
||||
'state.cover.closed': 'Closed',
|
||||
'state.cover.closing': 'Closing',
|
||||
'state.cover.stopped': 'Stopped',
|
||||
'state.device_tracker.home': 'Home',
|
||||
'state.device_tracker.not_home': 'Away',
|
||||
'state.fan.off': 'Off',
|
||||
'state.fan.on': 'On',
|
||||
'state.group.off': 'Off',
|
||||
'state.group.on': 'On',
|
||||
'state.group.home': 'Home',
|
||||
'state.group.not_home': 'Away',
|
||||
'state.group.open': 'Open',
|
||||
'state.group.opening': 'Opening',
|
||||
'state.group.closed': 'Closed',
|
||||
'state.group.closing': 'Closing',
|
||||
'state.group.stopped': 'Stopped',
|
||||
'state.group.locked': 'Locked',
|
||||
'state.group.unlocked': 'Unlocked',
|
||||
'state.group.ok': 'OK',
|
||||
'state.group.problem': 'Problem',
|
||||
'state.input_boolean.off': 'Off',
|
||||
'state.input_boolean.on': 'On',
|
||||
'state.light.off': 'Off',
|
||||
'state.light.on': 'On',
|
||||
'state.lock.locked': 'Locked',
|
||||
'state.lock.unlocked': 'Unlocked',
|
||||
'state.media_player.off': 'Off',
|
||||
'state.media_player.on': 'On',
|
||||
'state.media_player.playing': 'Playing',
|
||||
'state.media_player.paused': 'Paused',
|
||||
'state.media_player.idle': 'Idle',
|
||||
'state.media_player.standby': 'Standby',
|
||||
'state.plant.ok': 'OK',
|
||||
'state.plant.problem': 'Problem',
|
||||
'state.remote.off': 'Off',
|
||||
'state.remote.on': 'On',
|
||||
'state.scene.scening': 'Scening',
|
||||
'state.script.off': 'Off',
|
||||
'state.script.on': 'On',
|
||||
'state.sensor.off': 'Off',
|
||||
'state.sensor.on': 'On',
|
||||
'state.sun.above_horizon': 'Above horizon',
|
||||
'state.sun.below_horizon': 'Below horizon',
|
||||
'state.switch.off': 'Off',
|
||||
'state.switch.on': 'On',
|
||||
'state.weather.clear-night': 'Clear, night',
|
||||
'state.weather.cloudy': 'Cloudy',
|
||||
'state.weather.fog': 'Fog',
|
||||
'state.weather.hail': 'Hail',
|
||||
'state.weather.lightning': 'Lightning',
|
||||
'state.weather.lightning-rainy': 'Lightning, rainy',
|
||||
'state.weather.partlycloudy': 'Partly cloudy',
|
||||
'state.weather.pouring': 'Pouring',
|
||||
'state.weather.rainy': 'Rainy',
|
||||
'state.weather.snowy': 'Snowy',
|
||||
'state.weather.snowy-rainy': 'Snowy, rainy',
|
||||
'state.weather.sunny': 'Sunny',
|
||||
'state.weather.windy': 'Windy',
|
||||
'state.weather.windy-variant': 'Windy',
|
||||
'state.zwave.default.initializing': 'Initializing',
|
||||
'state.zwave.default.dead': 'Dead',
|
||||
'state.zwave.default.sleeping': 'Sleeping',
|
||||
'state.zwave.default.ready': 'Ready',
|
||||
'state.zwave.query_stage.initializing': 'Initializing ({query_stage})',
|
||||
'state.zwave.query_stage.dead': 'Dead ({query_stage})',
|
||||
'state_badge.default.unknown': 'Unk',
|
||||
'state_badge.default.unavailable': 'Unavai',
|
||||
'state_badge.alarm_control_panel.armed': 'Armed',
|
||||
'state_badge.alarm_control_panel.disarmed': 'Disarm',
|
||||
'state_badge.alarm_control_panel.armed_home': 'Armed',
|
||||
'state_badge.alarm_control_panel.armed_away': 'Armed',
|
||||
'state_badge.alarm_control_panel.armed_night': 'Armed',
|
||||
'state_badge.alarm_control_panel.armed_custom_bypass': 'Armed',
|
||||
'state_badge.alarm_control_panel.pending': 'Pend',
|
||||
'state_badge.alarm_control_panel.arming': 'Arming',
|
||||
'state_badge.alarm_control_panel.disarming': 'Disarm',
|
||||
'state_badge.alarm_control_panel.triggered': 'Trig',
|
||||
'state_badge.device_tracker.home': 'Home',
|
||||
'state_badge.device_tracker.not_home': 'Away',
|
||||
'ui.card.alarm_control_panel.code': 'Code',
|
||||
'ui.card.alarm_control_panel.clear_code': 'Clear',
|
||||
'ui.card.alarm_control_panel.disarm': 'Disarm',
|
||||
'ui.card.alarm_control_panel.arm_home': 'Arm home',
|
||||
'ui.card.alarm_control_panel.arm_away': 'Arm away',
|
||||
'ui.card.automation.last_triggered': 'Last triggered',
|
||||
'ui.card.automation.trigger': 'Trigger',
|
||||
'ui.card.camera.not_available': 'Image not available',
|
||||
'ui.card.climate.currently': 'Currently',
|
||||
'ui.card.climate.on_off': 'On / off',
|
||||
'ui.card.climate.target_temperature': 'Target temperature',
|
||||
'ui.card.climate.target_humidity': 'Target humidity',
|
||||
'ui.card.climate.operation': 'Operation',
|
||||
'ui.card.climate.fan_mode': 'Fan mode',
|
||||
'ui.card.climate.swing_mode': 'Swing mode',
|
||||
'ui.card.climate.away_mode': 'Away mode',
|
||||
'ui.card.climate.aux_heat': 'Aux heat',
|
||||
'ui.card.cover.position': 'Position',
|
||||
'ui.card.cover.tilt_position': 'Tilt position',
|
||||
'ui.card.fan.speed': 'Speed',
|
||||
'ui.card.fan.oscillate': 'Oscillate',
|
||||
'ui.card.fan.direction': 'Direction',
|
||||
'ui.card.light.brightness': 'Brightness',
|
||||
'ui.card.light.color_temperature': 'Color temperature',
|
||||
'ui.card.light.white_value': 'White value',
|
||||
'ui.card.light.effect': 'Effect',
|
||||
'ui.card.lock.code': 'Code',
|
||||
'ui.card.lock.lock': 'Lock',
|
||||
'ui.card.lock.unlock': 'Unlock',
|
||||
'ui.card.media_player.source': 'Source',
|
||||
'ui.card.media_player.sound_mode': 'Sound mode',
|
||||
'ui.card.media_player.text_to_speak': 'Text to speak',
|
||||
'ui.card.persistent_notification.dismiss': 'Dismiss',
|
||||
'ui.card.scene.activate': 'Activate',
|
||||
'ui.card.script.execute': 'Execute',
|
||||
'ui.card.weather.attributes.air_pressure': 'Air pressure',
|
||||
'ui.card.weather.attributes.humidity': 'Humidity',
|
||||
'ui.card.weather.attributes.temperature': 'Temperature',
|
||||
'ui.card.weather.attributes.visibility': 'Visibility',
|
||||
'ui.card.weather.attributes.wind_speed': 'Wind speed',
|
||||
'ui.card.weather.cardinal_direction.e': 'E',
|
||||
'ui.card.weather.cardinal_direction.ene': 'ENE',
|
||||
'ui.card.weather.cardinal_direction.ese': 'ESE',
|
||||
'ui.card.weather.cardinal_direction.n': 'N',
|
||||
'ui.card.weather.cardinal_direction.ne': 'NE',
|
||||
'ui.card.weather.cardinal_direction.nne': 'NNE',
|
||||
'ui.card.weather.cardinal_direction.nw': 'NW',
|
||||
'ui.card.weather.cardinal_direction.nnw': 'NNW',
|
||||
'ui.card.weather.cardinal_direction.s': 'S',
|
||||
'ui.card.weather.cardinal_direction.se': 'SE',
|
||||
'ui.card.weather.cardinal_direction.sse': 'SSE',
|
||||
'ui.card.weather.cardinal_direction.ssw': 'SSW',
|
||||
'ui.card.weather.cardinal_direction.sw': 'SW',
|
||||
'ui.card.weather.cardinal_direction.w': 'W',
|
||||
'ui.card.weather.cardinal_direction.wnw': 'WNW',
|
||||
'ui.card.weather.cardinal_direction.wsw': 'WSW',
|
||||
'ui.card.weather.forecast': 'Forecast',
|
||||
'ui.common.loading': 'Loading',
|
||||
'ui.common.cancel': 'Cancel',
|
||||
'ui.components.entity.entity-picker.entity': 'Entity',
|
||||
'ui.components.relative_time.past': '{time} ago',
|
||||
'ui.components.relative_time.future': 'In {time}',
|
||||
'ui.components.relative_time.never': 'Never',
|
||||
'ui.components.relative_time.duration.second': '{count} {count, plural,\n one {second}\n other {seconds}\n}',
|
||||
'ui.components.relative_time.duration.minute': '{count} {count, plural,\n one {minute}\n other {minutes}\n}',
|
||||
'ui.components.relative_time.duration.hour': '{count} {count, plural,\n one {hour}\n other {hours}\n}',
|
||||
'ui.components.relative_time.duration.day': '{count} {count, plural,\n one {day}\n other {days}\n}',
|
||||
'ui.components.relative_time.duration.week': '{count} {count, plural,\n one {week}\n other {weeks}\n}',
|
||||
'ui.components.history_charts.loading_history': 'Loading state history...',
|
||||
'ui.components.history_charts.no_history_found': 'No state history found.',
|
||||
'ui.components.service-picker.service': 'Service',
|
||||
'ui.dialogs.more_info_settings.save': 'Save',
|
||||
'ui.dialogs.more_info_settings.name': 'Name',
|
||||
'ui.duration.second': '{count} {count, plural,\n one {second}\n other {seconds}\n}',
|
||||
'ui.duration.minute': '{count} {count, plural,\n one {minute}\n other {minutes}\n}',
|
||||
'ui.duration.hour': '{count} {count, plural,\n one {hour}\n other {hours}\n}',
|
||||
'ui.duration.day': '{count} {count, plural,\n one {day}\n other {days}\n}',
|
||||
'ui.duration.week': '{count} {count, plural,\n one {week}\n other {weeks}\n}',
|
||||
'ui.login-form.password': 'Password',
|
||||
'ui.login-form.remember': 'Remember',
|
||||
'ui.login-form.log_in': 'Log in',
|
||||
'ui.notification_toast.entity_turned_on': 'Turned on {entity}.',
|
||||
'ui.notification_toast.entity_turned_off': 'Turned off {entity}.',
|
||||
'ui.notification_toast.service_called': 'Service {service} called.',
|
||||
'ui.notification_toast.service_call_failed': 'Failed to call service {service}.',
|
||||
'ui.notification_toast.connection_lost': 'Connection lost. Reconnecting…',
|
||||
'ui.sidebar.developer_tools': 'Developer tools',
|
||||
'ui.sidebar.log_out': 'Log out',
|
||||
'attribute.weather.humidity': 'Humidity',
|
||||
'attribute.weather.visibility': 'Visibility',
|
||||
'attribute.weather.wind_speed': 'Wind speed',
|
||||
}
|
||||
};
|
1252
gallery/src/data/demo_states.js
Normal file
116
gallery/src/data/entity.js
Normal file
@@ -0,0 +1,116 @@
|
||||
const now = () => new Date().toISOString();
|
||||
const randomTime = () =>
|
||||
new Date(new Date().getTime() - (Math.random() * 80 * 60 * 1000)).toISOString();
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
export class Entity {
|
||||
constructor(domain, objectId, state, baseAttributes) {
|
||||
this.domain = domain;
|
||||
this.objectId = objectId;
|
||||
this.entityId = `${domain}.${objectId}`;
|
||||
this.lastChanged = randomTime();
|
||||
this.lastUpdated = randomTime();
|
||||
this.state = state;
|
||||
// These are the attributes that we always write to the state machine
|
||||
this.baseAttributes = baseAttributes;
|
||||
this.attributes = baseAttributes;
|
||||
}
|
||||
|
||||
async handleService(domain, service, data) {
|
||||
console.log(`Unmocked service for ${this.entityId}: ${domain}/${service}`, data);
|
||||
}
|
||||
|
||||
update(state, attributes = {}) {
|
||||
this.state = state;
|
||||
this.lastUpdated = now();
|
||||
this.lastChanged = state === this.state ? this.lastChanged : this.lastUpdated;
|
||||
this.attributes = Object.assign({}, this.baseAttributes, attributes);
|
||||
|
||||
console.log('update', this.entityId, this);
|
||||
|
||||
this.hass.updateStates({
|
||||
[this.entityId]: this.toState()
|
||||
});
|
||||
}
|
||||
|
||||
toState() {
|
||||
return {
|
||||
entity_id: this.entityId,
|
||||
state: this.state,
|
||||
attributes: this.attributes,
|
||||
last_changed: this.lastChanged,
|
||||
last_updated: this.lastUpdated,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class LightEntity extends Entity {
|
||||
async handleService(domain, service, data) {
|
||||
if (!['homeassistant', this.domain].includes(domain)) return;
|
||||
|
||||
if (service === 'turn_on') {
|
||||
// eslint-disable-next-line
|
||||
const { brightness, hs_color } = data;
|
||||
this.update('on', Object.assign(this.attributes, {
|
||||
brightness,
|
||||
hs_color,
|
||||
}));
|
||||
} else if (service === 'turn_off') {
|
||||
this.update('off');
|
||||
} else if (service === 'toggle') {
|
||||
if (this.state === 'on') {
|
||||
this.handleService(domain, 'turn_off', data);
|
||||
} else {
|
||||
this.handleService(domain, 'turn_on', data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class LockEntity extends Entity {
|
||||
async handleService(domain, service, data) {
|
||||
if (domain !== this.domain) return;
|
||||
|
||||
if (service === 'lock') {
|
||||
this.update('locked');
|
||||
} else if (service === 'unlock') {
|
||||
this.update('unlocked');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CoverEntity extends Entity {
|
||||
async handleService(domain, service, data) {
|
||||
if (domain !== this.domain) return;
|
||||
|
||||
if (service === 'open_cover') {
|
||||
this.update('open');
|
||||
} else if (service === 'close_cover') {
|
||||
this.update('closing');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupEntity extends Entity {
|
||||
async handleService(domain, service, data) {
|
||||
if (!['homeassistant', this.domain].includes(domain)) return;
|
||||
|
||||
await Promise.all(this.attributes.entity_id.map((ent) => {
|
||||
const entity = this.hass.mockEntities[ent];
|
||||
return entity.handleService(entity.domain, service, data);
|
||||
}));
|
||||
|
||||
this.update(service === 'turn_on' ? 'on' : 'off');
|
||||
}
|
||||
}
|
||||
|
||||
const TYPES = {
|
||||
light: LightEntity,
|
||||
lock: LockEntity,
|
||||
cover: CoverEntity,
|
||||
group: GroupEntity,
|
||||
};
|
||||
|
||||
export default (domain, objectId, state, baseAttributes = {}) =>
|
||||
new (TYPES[domain] || Entity)(domain, objectId, state, baseAttributes);
|
34
gallery/src/data/hass.js
Normal file
@@ -0,0 +1,34 @@
|
||||
export default class FakeHass {
|
||||
constructor(states = {}) {
|
||||
this.states = states;
|
||||
this._wsCommands = {};
|
||||
}
|
||||
|
||||
addWSCommand(command, callback) {
|
||||
this._wsCommands[command] = callback;
|
||||
}
|
||||
|
||||
async callService(domain, service, serviceData) {
|
||||
console.log('callService', { domain, service, serviceData });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async callWS(msg) {
|
||||
const callback = this._wsCommands[msg.type];
|
||||
return callback ? callback(msg) : Promise.reject({
|
||||
code: 'command_not_mocked',
|
||||
message: 'This command is not implemented in the gallery.',
|
||||
});
|
||||
}
|
||||
|
||||
async sendWS(msg) {
|
||||
const callback = this._wsCommands[msg.type];
|
||||
|
||||
if (callback) {
|
||||
callback(msg);
|
||||
} else {
|
||||
console.error(`Unknown command: ${msg.type}`);
|
||||
}
|
||||
console.log('sendWS', msg);
|
||||
}
|
||||
}
|
79
gallery/src/data/provide_hass.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import fireEvent from '../../../src/common/dom/fire_event.js';
|
||||
|
||||
import demoConfig from './demo_config.js';
|
||||
import demoResources from './demo_resources.js';
|
||||
|
||||
const ensureArray = val => (Array.isArray(val) ? val : [val]);
|
||||
|
||||
export default (elements, { initialStates = {} } = {}) => {
|
||||
elements = ensureArray(elements);
|
||||
|
||||
const wsCommands = {};
|
||||
let hass;
|
||||
const entities = {};
|
||||
|
||||
function updateHass(obj) {
|
||||
hass = Object.assign({}, hass, obj);
|
||||
elements.forEach((el) => { el.hass = hass; });
|
||||
}
|
||||
|
||||
updateHass({
|
||||
// Home Assistant properties
|
||||
config: demoConfig,
|
||||
language: 'en',
|
||||
resources: demoResources,
|
||||
states: initialStates,
|
||||
|
||||
// Mock properties
|
||||
mockEntities: entities,
|
||||
|
||||
// Home Assistant functions
|
||||
async callService(domain, service, data) {
|
||||
fireEvent(elements[0], 'show-notification', { message: `Called service ${domain}/${service}` });
|
||||
if (data.entity_id) {
|
||||
await Promise.all(ensureArray(data.entity_id).map(ent =>
|
||||
entities[ent].handleService(domain, service, data)));
|
||||
} else {
|
||||
console.log('unmocked callService', domain, service, data);
|
||||
}
|
||||
},
|
||||
|
||||
async callWS(msg) {
|
||||
const callback = wsCommands[msg.type];
|
||||
return callback ? callback(msg) : Promise.reject({
|
||||
code: 'command_not_mocked',
|
||||
message: 'This command is not implemented in the gallery.',
|
||||
});
|
||||
},
|
||||
|
||||
async sendWS(msg) {
|
||||
const callback = wsCommands[msg.type];
|
||||
|
||||
if (callback) {
|
||||
callback(msg);
|
||||
} else {
|
||||
console.error(`Unknown command: ${msg.type}`);
|
||||
}
|
||||
console.log('sendWS', msg);
|
||||
},
|
||||
|
||||
// Mock functions
|
||||
updateHass,
|
||||
updateStates(newStates) {
|
||||
updateHass({
|
||||
states: Object.assign({}, hass.states, newStates),
|
||||
});
|
||||
},
|
||||
addEntities(newEntities) {
|
||||
const states = {};
|
||||
ensureArray(newEntities).forEach((ent) => {
|
||||
ent.hass = hass;
|
||||
entities[ent.entityId] = ent;
|
||||
states[ent.entityId] = ent.toState();
|
||||
});
|
||||
this.updateStates(states);
|
||||
}
|
||||
});
|
||||
|
||||
return hass;
|
||||
};
|
84
gallery/src/demos/demo-hui-conditional-card.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import getEntity from '../data/entity.js';
|
||||
import provideHass from '../data/provide_hass.js';
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity('light', 'controller_1', 'on', {
|
||||
friendly_name: 'Controller 1'
|
||||
}),
|
||||
getEntity('light', 'controller_2', 'on', {
|
||||
friendly_name: 'Controller 2'
|
||||
}),
|
||||
getEntity('light', 'floor', 'off', {
|
||||
friendly_name: 'Floor light'
|
||||
}),
|
||||
getEntity('light', 'kitchen', 'on', {
|
||||
friendly_name: 'Kitchen light'
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Controller',
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- light.controller_1
|
||||
- light.controller_2
|
||||
- type: divider
|
||||
- light.floor
|
||||
- light.kitchen
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Demo',
|
||||
config: `
|
||||
- type: conditional
|
||||
conditions:
|
||||
- entity: light.controller_1
|
||||
state: "on"
|
||||
- entity: light.controller_2
|
||||
state_not: "off"
|
||||
card:
|
||||
type: entities
|
||||
entities:
|
||||
- light.controller_1
|
||||
- light.controller_2
|
||||
- light.floor
|
||||
- light.kitchen
|
||||
`
|
||||
},
|
||||
];
|
||||
|
||||
class DemoConditional extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards
|
||||
id='demos'
|
||||
hass='[[hass]]'
|
||||
configs="[[_configs]]"
|
||||
></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
},
|
||||
hass: Object,
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-conditional-card', DemoConditional);
|
206
gallery/src/demos/demo-hui-entities-card.js
Normal file
@@ -0,0 +1,206 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import getEntity from '../data/entity.js';
|
||||
import provideHass from '../data/provide_hass.js';
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity('light', 'bed_light', 'on', {
|
||||
friendly_name: 'Bed Light'
|
||||
}),
|
||||
getEntity('group', 'kitchen', 'on', {
|
||||
entity_id: [
|
||||
'light.bed_light',
|
||||
],
|
||||
order: 8,
|
||||
friendly_name: 'Kitchen'
|
||||
}),
|
||||
getEntity('lock', 'kitchen_door', 'locked', {
|
||||
friendly_name: 'Kitchen Door'
|
||||
}),
|
||||
getEntity('cover', 'kitchen_window', 'open', {
|
||||
friendly_name: 'Kitchen Window',
|
||||
supported_features: 11
|
||||
}),
|
||||
getEntity('scene', 'romantic_lights', 'scening', {
|
||||
entity_id: [
|
||||
'light.bed_light',
|
||||
'light.ceiling_lights'
|
||||
],
|
||||
friendly_name: 'Romantic lights'
|
||||
}),
|
||||
getEntity('device_tracker', 'demo_paulus', 'home', {
|
||||
source_type: 'gps',
|
||||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
friendly_name: 'Paulus'
|
||||
}),
|
||||
getEntity('climate', 'ecobee', 'auto', {
|
||||
current_temperature: 73,
|
||||
min_temp: 45,
|
||||
max_temp: 95,
|
||||
temperature: null,
|
||||
target_temp_high: 75,
|
||||
target_temp_low: 70,
|
||||
fan_mode: 'Auto Low',
|
||||
fan_list: ['On Low', 'On High', 'Auto Low', 'Auto High', 'Off'],
|
||||
operation_mode: 'auto',
|
||||
operation_list: ['heat', 'cool', 'auto', 'off'],
|
||||
hold_mode: 'home',
|
||||
swing_mode: 'Auto',
|
||||
swing_list: ['Auto', '1', '2', '3', 'Off'],
|
||||
unit_of_measurement: '°F',
|
||||
friendly_name: 'Ecobee',
|
||||
supported_features: 1014
|
||||
}),
|
||||
getEntity('input_number', 'noise_allowance', 5, {
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 1,
|
||||
mode: 'slider',
|
||||
unit_of_measurement: 'dB',
|
||||
friendly_name: 'Allowed Noise',
|
||||
icon: 'mdi:bell-ring'
|
||||
})
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Basic',
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- scene.romantic_lights
|
||||
- device_tracker.demo_paulus
|
||||
- cover.kitchen_window
|
||||
- group.kitchen
|
||||
- lock.kitchen_door
|
||||
- light.bed_light
|
||||
- light.non_existing
|
||||
- climate.ecobee
|
||||
- input_number.noise_allowance
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'With title, toggle-able',
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- scene.romantic_lights
|
||||
- device_tracker.demo_paulus
|
||||
- cover.kitchen_window
|
||||
- group.kitchen
|
||||
- lock.kitchen_door
|
||||
- light.bed_light
|
||||
- climate.ecobee
|
||||
- input_number.noise_allowance
|
||||
title: Random group
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'With title, toggle = false',
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- scene.romantic_lights
|
||||
- device_tracker.demo_paulus
|
||||
- cover.kitchen_window
|
||||
- group.kitchen
|
||||
- lock.kitchen_door
|
||||
- light.bed_light
|
||||
- climate.ecobee
|
||||
- input_number.noise_allowance
|
||||
title: Random group
|
||||
show_header_toggle: false
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'With title, can\'t toggle',
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
title: Random group
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Custom name, secondary info, custom icon',
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: scene.romantic_lights
|
||||
name: ¯\\_(ツ)_/¯
|
||||
- entity: device_tracker.demo_paulus
|
||||
secondary_info: entity-id
|
||||
- entity: cover.kitchen_window
|
||||
secondary_info: last-changed
|
||||
- entity: group.kitchen
|
||||
icon: mdi:home-assistant
|
||||
- lock.kitchen_door
|
||||
- entity: light.bed_light
|
||||
icon: mdi:alarm-light
|
||||
name: Bed Light Custom Icon
|
||||
- climate.ecobee
|
||||
- input_number.noise_allowance
|
||||
title: Random group
|
||||
show_header_toggle: false
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Special rows',
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- type: weblink
|
||||
url: http://google.com/
|
||||
icon: mdi:google
|
||||
name: Google
|
||||
- type: call-service
|
||||
icon: mdi:power
|
||||
name: Bed light
|
||||
action_name: Toggle light
|
||||
service: light.toggle
|
||||
service_data:
|
||||
entity_id: light.bed_light
|
||||
- type: divider
|
||||
- type: divider
|
||||
style:
|
||||
height: 30px
|
||||
margin: 4px 0
|
||||
background: center / contain url("/images/divider.png") no-repeat
|
||||
`
|
||||
},
|
||||
];
|
||||
|
||||
class DemoEntities extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards
|
||||
id='demos'
|
||||
hass='[[hass]]'
|
||||
configs="[[_configs]]"
|
||||
></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
},
|
||||
hass: Object,
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-entities-card', DemoEntities);
|
74
gallery/src/demos/demo-hui-entity-filter-card.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Basic',
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
- light.bed_light
|
||||
- light.ceiling_lights
|
||||
- light.kitchen_lights
|
||||
state_filter:
|
||||
- "on"
|
||||
- not_home
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'With card config',
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
- light.bed_light
|
||||
- light.ceiling_lights
|
||||
- light.kitchen_lights
|
||||
state_filter:
|
||||
- "on"
|
||||
- not_home
|
||||
card:
|
||||
type: glance
|
||||
show_state: false
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Showing single entity conditionally',
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- media_player.lounge_room
|
||||
state_filter:
|
||||
- 'playing'
|
||||
card:
|
||||
type: media-control
|
||||
entity: media_player.lounge_room
|
||||
`
|
||||
}
|
||||
];
|
||||
|
||||
class DemoFilter extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-entity-filter-card', DemoFilter);
|
164
gallery/src/demos/demo-hui-glance-card.js
Normal file
@@ -0,0 +1,164 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Basic example',
|
||||
config: `
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'With title',
|
||||
config: `
|
||||
- type: glance
|
||||
title: This is glance
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Custom column width',
|
||||
config: `
|
||||
- type: glance
|
||||
column_width: calc(100% / 7)
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'No name',
|
||||
config: `
|
||||
- type: glance
|
||||
show_name: false
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'No state',
|
||||
config: `
|
||||
- type: glance
|
||||
show_state: false
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'No name and no state',
|
||||
config: `
|
||||
- type: glance
|
||||
show_name: false
|
||||
show_state: false
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Custom name, custom icon',
|
||||
config: `
|
||||
- type: glance
|
||||
entities:
|
||||
- entity: device_tracker.demo_paulus
|
||||
name: ¯\\_(ツ)_/¯
|
||||
icon: mdi:home-assistant
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- entity: light.kitchen_lights
|
||||
icon: mdi:alarm-light
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Custom tap action',
|
||||
config: `
|
||||
- type: glance
|
||||
entities:
|
||||
- entity: lock.kitchen_door
|
||||
tap_action: toggle
|
||||
- entity: light.ceiling_lights
|
||||
tap_action: call-service
|
||||
service: light.turn_on
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Selectively hidden name',
|
||||
config: `
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- entity: media_player.living_room
|
||||
name:
|
||||
- sun.sun
|
||||
- entity: cover.kitchen_window
|
||||
name:
|
||||
- light.kitchen_lights
|
||||
`
|
||||
},
|
||||
];
|
||||
|
||||
class DemoPicEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-glance-card', DemoPicEntity);
|
57
gallery/src/demos/demo-hui-iframe-card.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Without title',
|
||||
config: `
|
||||
- type: iframe
|
||||
url: https://embed.windy.com/embed2.html
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'With title',
|
||||
config: `
|
||||
- type: iframe
|
||||
url: https://embed.windy.com/embed2.html
|
||||
title: Weather radar
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Height-Width 3:4',
|
||||
config: `
|
||||
- type: iframe
|
||||
url: https://embed.windy.com/embed2.html
|
||||
aspect_ratio: 75%
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Height-Width 1:1',
|
||||
config: `
|
||||
- type: iframe
|
||||
url: https://embed.windy.com/embed2.html
|
||||
aspect_ratio: 100%
|
||||
`
|
||||
},
|
||||
];
|
||||
|
||||
class DemoIframe extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-iframe-card', DemoIframe);
|
149
gallery/src/demos/demo-hui-map-card.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import getEntity from '../data/entity.js';
|
||||
import provideHass from '../data/provide_hass.js';
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity('device_tracker', 'demo_paulus', 'not_home', {
|
||||
source_type: 'gps',
|
||||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
friendly_name: 'Paulus'
|
||||
}),
|
||||
getEntity('device_tracker', 'demo_home_boy', 'home', {
|
||||
source_type: 'gps',
|
||||
latitude: 32.87334,
|
||||
longitude: 117.22745,
|
||||
gps_accuracy: 20,
|
||||
battery: 53,
|
||||
friendly_name: 'Home Boy'
|
||||
}),
|
||||
getEntity('zone', 'home', 'zoning', {
|
||||
latitude: 32.87354,
|
||||
longitude: 117.22765,
|
||||
radius: 100,
|
||||
friendly_name: 'Home',
|
||||
icon: 'mdi:home'
|
||||
})
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Without title',
|
||||
config: `
|
||||
- type: map
|
||||
entities:
|
||||
- entity: device_tracker.demo_paulus
|
||||
- device_tracker.demo_home_boy
|
||||
- zone.home
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'With title',
|
||||
config: `
|
||||
- type: map
|
||||
entities:
|
||||
- entity: device_tracker.demo_paulus
|
||||
- zone.home
|
||||
title: Where is Paulus?
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Height-Width 1:2',
|
||||
config: `
|
||||
- type: map
|
||||
entities:
|
||||
- entity: device_tracker.demo_paulus
|
||||
- zone.home
|
||||
aspect_ratio: 50%
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Default Zoom',
|
||||
config: `
|
||||
- type: map
|
||||
default_zoom: 12
|
||||
entities:
|
||||
- entity: device_tracker.demo_paulus
|
||||
- zone.home
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Default Zoom too High',
|
||||
config: `
|
||||
- type: map
|
||||
default_zoom: 20
|
||||
entities:
|
||||
- entity: device_tracker.demo_paulus
|
||||
- zone.home
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Single Marker',
|
||||
config: `
|
||||
- type: map
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Single Marker Default Zoom',
|
||||
config: `
|
||||
- type: map
|
||||
default_zoom: 8
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'No Entities',
|
||||
config: `
|
||||
- type: map
|
||||
entities:
|
||||
- light.bed_light
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'No Entities, Default Zoom',
|
||||
config: `
|
||||
- type: map
|
||||
default_zoom: 8
|
||||
entities:
|
||||
- light.bed_light
|
||||
`
|
||||
},
|
||||
];
|
||||
|
||||
class DemoMap extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards
|
||||
id="demos"
|
||||
hass="[[hass]]"
|
||||
configs="[[_configs]]"
|
||||
></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
},
|
||||
hass: Object
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-map-card', DemoMap);
|
272
gallery/src/demos/demo-hui-markdown-card.js
Normal file
@@ -0,0 +1,272 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'markdown-it demo',
|
||||
config: `
|
||||
- type: markdown
|
||||
content: >
|
||||
# h1 Heading 8-)
|
||||
|
||||
## h2 Heading
|
||||
|
||||
### h3 Heading
|
||||
|
||||
#### h4 Heading
|
||||
|
||||
##### h5 Heading
|
||||
|
||||
###### h6 Heading
|
||||
|
||||
|
||||
## Horizontal Rules
|
||||
|
||||
___
|
||||
|
||||
---
|
||||
|
||||
***
|
||||
|
||||
|
||||
## Typographic replacements
|
||||
|
||||
Enable typographer option to see result.
|
||||
|
||||
(c) (C) (r) (R) (tm) (TM) (p) (P) +-
|
||||
|
||||
test.. test... test..... test?..... test!....
|
||||
|
||||
!!!!!! ???? ,, -- ---
|
||||
|
||||
"Smartypants, double quotes" and 'single quotes'
|
||||
|
||||
|
||||
## Emphasis
|
||||
|
||||
**This is bold text**
|
||||
|
||||
__This is bold text__
|
||||
|
||||
*This is italic text*
|
||||
|
||||
_This is italic text_
|
||||
|
||||
~~Strikethrough~~
|
||||
|
||||
|
||||
## Blockquotes
|
||||
|
||||
|
||||
> Blockquotes can also be nested...
|
||||
>> ...by using additional greater-than signs right next to each other...
|
||||
> > > ...or with spaces between arrows.
|
||||
|
||||
|
||||
## Lists
|
||||
|
||||
Unordered
|
||||
|
||||
+ Create a list by starting a line with \`+\`, \`-\`, or \`*\`
|
||||
+ Sub-lists are made by indenting 2 spaces:
|
||||
- Marker character change forces new list start:
|
||||
* Ac tristique libero volutpat at
|
||||
+ Facilisis in pretium nisl aliquet
|
||||
- Nulla volutpat aliquam velit
|
||||
+ Very easy!
|
||||
|
||||
|
||||
Ordered
|
||||
|
||||
1. Lorem ipsum dolor sit amet
|
||||
2. Consectetur adipiscing elit
|
||||
3. Integer molestie lorem at massa
|
||||
|
||||
|
||||
1. You can use sequential numbers...
|
||||
1. ...or keep all the numbers as \`1.\`
|
||||
|
||||
Start numbering with offset:
|
||||
|
||||
57. foo
|
||||
1. bar
|
||||
|
||||
|
||||
## Code
|
||||
|
||||
Inline \`code\`
|
||||
|
||||
Indented code
|
||||
|
||||
// Some comments
|
||||
line 1 of code
|
||||
line 2 of code
|
||||
line 3 of code
|
||||
|
||||
|
||||
Block code "fences"
|
||||
|
||||
\`\`\`
|
||||
Sample text here...
|
||||
\`\`\`
|
||||
|
||||
Syntax highlighting
|
||||
|
||||
\`\`\` js
|
||||
var foo = function (bar) {
|
||||
return bar++;
|
||||
};
|
||||
|
||||
console.log(foo(5));
|
||||
\`\`\`
|
||||
|
||||
## Tables
|
||||
|
||||
| Option | Description |
|
||||
| ------ | ----------- |
|
||||
| data | path to data files to supply the data that will be passed into templates. |
|
||||
| engine | engine to be used for processing templates. Handlebars is the default. |
|
||||
| ext | extension to be used for dest files. |
|
||||
|
||||
Right aligned columns
|
||||
|
||||
| Option | Description |
|
||||
| ------:| -----------:|
|
||||
| data | path to data files to supply the data that will be passed into templates. |
|
||||
| engine | engine to be used for processing templates. Handlebars is the default. |
|
||||
| ext | extension to be used for dest files. |
|
||||
|
||||
|
||||
## Links
|
||||
|
||||
[link text](http://dev.nodeca.com)
|
||||
|
||||
[link with title](http://nodeca.github.io/pica/demo/ "title text!")
|
||||
|
||||
Autoconverted link https://github.com/nodeca/pica (enable linkify to see)
|
||||
|
||||
|
||||
## Images
|
||||
|
||||

|
||||

|
||||
|
||||
Like links, Images also have a footnote style syntax
|
||||
|
||||
![Alt text][id]
|
||||
|
||||
With a reference later in the document defining the URL location:
|
||||
|
||||
[id]: https://octodex.github.com/images/dojocat.jpg "The Dojocat"
|
||||
|
||||
|
||||
## Plugins
|
||||
|
||||
The killer feature of \`markdown-it\` is very effective support of
|
||||
[syntax plugins](https://www.npmjs.org/browse/keyword/markdown-it-plugin).
|
||||
|
||||
|
||||
### [Emojies](https://github.com/markdown-it/markdown-it-emoji)
|
||||
|
||||
> Classic markup: :wink: :crush: :cry: :tear: :laughing: :yum:
|
||||
>
|
||||
> Shortcuts (emoticons): :-) :-( 8-) ;)
|
||||
|
||||
see [how to change output](https://github.com/markdown-it/markdown-it-emoji#change-output) with twemoji.
|
||||
|
||||
|
||||
### [Subscript](https://github.com/markdown-it/markdown-it-sub) / [Superscript](https://github.com/markdown-it/markdown-it-sup)
|
||||
|
||||
- 19^th^
|
||||
- H~2~O
|
||||
|
||||
|
||||
### [<ins>](https://github.com/markdown-it/markdown-it-ins)
|
||||
|
||||
++Inserted text++
|
||||
|
||||
|
||||
### [<mark>](https://github.com/markdown-it/markdown-it-mark)
|
||||
|
||||
==Marked text==
|
||||
|
||||
|
||||
### [Footnotes](https://github.com/markdown-it/markdown-it-footnote)
|
||||
|
||||
Footnote 1 link[^first].
|
||||
|
||||
Footnote 2 link[^second].
|
||||
|
||||
Inline footnote^[Text of inline footnote] definition.
|
||||
|
||||
Duplicated footnote reference[^second].
|
||||
|
||||
[^first]: Footnote **can have markup**
|
||||
|
||||
and multiple paragraphs.
|
||||
|
||||
[^second]: Footnote text.
|
||||
|
||||
|
||||
### [Definition lists](https://github.com/markdown-it/markdown-it-deflist)
|
||||
|
||||
Term 1
|
||||
|
||||
: Definition 1
|
||||
with lazy continuation.
|
||||
|
||||
Term 2 with *inline markup*
|
||||
|
||||
: Definition 2
|
||||
|
||||
{ some code, part of Definition 2 }
|
||||
|
||||
Third paragraph of definition 2.
|
||||
|
||||
_Compact style:_
|
||||
|
||||
Term 1
|
||||
~ Definition 1
|
||||
|
||||
Term 2
|
||||
~ Definition 2a
|
||||
~ Definition 2b
|
||||
|
||||
|
||||
### [Abbreviations](https://github.com/markdown-it/markdown-it-abbr)
|
||||
|
||||
This is HTML abbreviation example.
|
||||
|
||||
It converts "HTML", but keep intact partial entries like "xxxHTMLyyy" and so on.
|
||||
|
||||
*[HTML]: Hyper Text Markup Language
|
||||
|
||||
### [Custom containers](https://github.com/markdown-it/markdown-it-container)
|
||||
|
||||
::: warning
|
||||
*here be dragons*
|
||||
:::
|
||||
`
|
||||
},
|
||||
];
|
||||
|
||||
class DemoMarkdown extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-markdown-card', DemoMarkdown);
|
73
gallery/src/demos/demo-hui-picture-elements-card.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Card with few elements',
|
||||
config: `
|
||||
- type: picture-elements
|
||||
image: /images/floorplan.png
|
||||
elements:
|
||||
- type: service-button
|
||||
title: Lights Off
|
||||
style:
|
||||
top: 97%
|
||||
left: 90%
|
||||
padding: 0px
|
||||
service: light.turn_off
|
||||
service_data:
|
||||
entity_id: group.all_lights
|
||||
- type: icon
|
||||
icon: mdi:cctv
|
||||
entity: camera.demo_camera
|
||||
style:
|
||||
top: 12%
|
||||
left: 6%
|
||||
transform: rotate(-60deg) scaleX(-1)
|
||||
--iron-icon-height: 30px
|
||||
--iron-icon-width: 30px
|
||||
--iron-icon-stroke-color: black
|
||||
--iron-icon-fill-color: rgba(50, 50, 50, .75)
|
||||
- type: image
|
||||
entity: light.bed_light
|
||||
tap_action: toggle
|
||||
image: /images/light_bulb_off.png
|
||||
state_image:
|
||||
'on': /images/light_bulb_on.png
|
||||
state_filter:
|
||||
'on': brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)
|
||||
'off': brightness(80%) saturate(0.8)
|
||||
style:
|
||||
top: 35%
|
||||
left: 65%
|
||||
width: 7%
|
||||
padding: 50px 50px 100px 50px
|
||||
- type: state-icon
|
||||
entity: binary_sensor.movement_backyard
|
||||
style:
|
||||
top: 8%
|
||||
left: 35%
|
||||
`
|
||||
},
|
||||
];
|
||||
|
||||
class DemoPicElements extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-picture-elements-card', DemoPicElements);
|
85
gallery/src/demos/demo-hui-picture-entity-card.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'State on',
|
||||
config: `
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'State off',
|
||||
config: `
|
||||
- type: picture-entity
|
||||
image: /images/bed.png
|
||||
entity: light.bed_light
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Entity unavailable',
|
||||
config: `
|
||||
- type: picture-entity
|
||||
image: /images/living_room.png
|
||||
entity: light.non_existing
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Camera entity',
|
||||
config: `
|
||||
- type: picture-entity
|
||||
entity: camera.demo_camera
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Hidden name',
|
||||
config: `
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
show_name: false
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Hidden state',
|
||||
config: `
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
show_state: false
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Both hidden',
|
||||
config: `
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
show_name: false
|
||||
show_state: false
|
||||
`
|
||||
},
|
||||
];
|
||||
|
||||
class DemoPicEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-picture-entity-card', DemoPicEntity);
|
105
gallery/src/demos/demo-hui-picture-glance-card.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Title, dialog, toggle',
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
title: Living room
|
||||
entities:
|
||||
- switch.decorative_lights
|
||||
- light.ceiling_lights
|
||||
- binary_sensor.movement_backyard
|
||||
- binary_sensor.basement_floor_wet
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Title, dialog, no toggle',
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
title: Living room
|
||||
entities:
|
||||
- binary_sensor.movement_backyard
|
||||
- binary_sensor.basement_floor_wet
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Title, no dialog, toggle',
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
title: Living room
|
||||
entities:
|
||||
- switch.decorative_lights
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'No title, dialog, toggle',
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
entities:
|
||||
- switch.decorative_lights
|
||||
- light.ceiling_lights
|
||||
- binary_sensor.movement_backyard
|
||||
- binary_sensor.basement_floor_wet
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'No title, dialog, no toggle',
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
entities:
|
||||
- binary_sensor.movement_backyard
|
||||
- binary_sensor.basement_floor_wet
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'No title, no dialog, toggle',
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
entities:
|
||||
- switch.decorative_lights
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Custom icon',
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
title: Living room
|
||||
entities:
|
||||
- entity: switch.decorative_lights
|
||||
icon: mdi:power
|
||||
- binary_sensor.basement_floor_wet
|
||||
`
|
||||
},
|
||||
];
|
||||
|
||||
class DemoPicGlance extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-picture-glance-card', DemoPicGlance);
|
76
gallery/src/demos/demo-hui-stack-card.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Vertical Stack',
|
||||
config: `
|
||||
- type: vertical-stack
|
||||
cards:
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Horizontal Stack',
|
||||
config: `
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Combination of both',
|
||||
config: `
|
||||
- type: vertical-stack
|
||||
cards:
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
- type: picture-entity
|
||||
image: /images/bed.png
|
||||
entity: light.bed_light
|
||||
`
|
||||
},
|
||||
];
|
||||
|
||||
class DemoStack extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-stack-card', DemoStack);
|
60
gallery/src/demos/demo-more-info-light.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/dialogs/more-info/controls/more-info-content.js';
|
||||
import '../../../src/components/ha-card.js';
|
||||
|
||||
import getEntity from '../data/entity.js';
|
||||
import provideHass from '../data/provide_hass.js';
|
||||
|
||||
import '../components/demo-more-infos.js';
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
const SUPPORT_BRIGHTNESS = 1;
|
||||
const SUPPORT_COLOR_TEMP = 2;
|
||||
const SUPPORT_EFFECT = 4;
|
||||
const SUPPORT_FLASH = 8;
|
||||
const SUPPORT_COLOR = 16;
|
||||
const SUPPORT_TRANSITION = 32;
|
||||
const SUPPORT_WHITE_VALUE = 128;
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity('light', 'bed_light', 'on', {
|
||||
friendly_name: 'Basic Light'
|
||||
}),
|
||||
getEntity('light', 'kitchen_light', 'on', {
|
||||
friendly_name: 'Brightness Light',
|
||||
brightness: 80,
|
||||
supported_features: SUPPORT_BRIGHTNESS,
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
class DemoMoreInfoLight extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-more-infos
|
||||
hass='[[hass]]'
|
||||
entities='[[_entities]]'
|
||||
></demo-more-infos>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_entities: {
|
||||
type: Array,
|
||||
value: ENTITIES.map(ent => ent.entityId),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-more-info-light', DemoMoreInfoLight);
|
12
gallery/src/entrypoint.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import '@polymer/paper-styles/typography.js';
|
||||
import '@polymer/polymer/lib/elements/dom-if.js';
|
||||
import '@polymer/polymer/lib/elements/dom-repeat.js';
|
||||
|
||||
import '../../src/resources/hass-icons.js';
|
||||
import '../../src/resources/ha-style.js';
|
||||
import '../../src/resources/roboto.js';
|
||||
import '../../src/components/ha-iconset-svg.js';
|
||||
|
||||
import './ha-gallery.js';
|
||||
|
||||
document.body.appendChild(document.createElement('ha-gallery'));
|
201
gallery/src/ha-gallery.js
Normal file
@@ -0,0 +1,201 @@
|
||||
import '@polymer/app-layout/app-header-layout/app-header-layout.js';
|
||||
import '@polymer/app-layout/app-header/app-header.js';
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/iron-icon/iron-icon.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import '@polymer/paper-item/paper-item.js';
|
||||
import '@polymer/paper-item/paper-item-body.js';
|
||||
import '@polymer/paper-icon-button/paper-icon-button.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../src/managers/notification-manager.js';
|
||||
|
||||
const DEMOS = require.context('./demos', true, /^(.*\.(js$))[^.]*$/im);
|
||||
|
||||
const fixPath = path => path.substr(2, path.length - 5);
|
||||
|
||||
class HaGallery extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-positioning ha-style">
|
||||
:host {
|
||||
-ms-user-select: initial;
|
||||
-webkit-user-select: initial;
|
||||
-moz-user-select: initial;
|
||||
}
|
||||
app-header-layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
paper-icon-button.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.pickers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.pickers paper-card {
|
||||
width: 400px;
|
||||
display: block;
|
||||
margin: 16px 8px;
|
||||
}
|
||||
|
||||
.pickers paper-card:last-child {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.intro {
|
||||
margin: -1em 0;
|
||||
}
|
||||
|
||||
p a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
a paper-item {
|
||||
color: var(--primary-text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<app-header-layout>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<paper-icon-button
|
||||
icon="hass:arrow-left"
|
||||
on-click="_backTapped"
|
||||
class$='[[_computeHeaderButtonClass(_demo)]]'
|
||||
></paper-icon-button>
|
||||
<div main-title>[[_withDefault(_demo, "Home Assistant Gallery")]]</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
|
||||
<div class='content'>
|
||||
<div id='demo'></div>
|
||||
<template is='dom-if' if='[[!_demo]]'>
|
||||
<div class='pickers'>
|
||||
<paper-card heading="Lovelace card demos">
|
||||
<div class='card-content intro'>
|
||||
<p>
|
||||
Lovelace has many different cards. Each card allows the user to tell a different story about what is going on in their house. These cards are very customizable, as no household is the same.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This gallery helps our developers and designers to see all the different states that each card can be in.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Check <a href='https://www.home-assistant.io/lovelace'>the official website</a> for instructions on how to get started with Lovelace.</a>.
|
||||
</p>
|
||||
</div>
|
||||
<template is='dom-repeat' items='[[_lovelaceDemos]]'>
|
||||
<a href='#[[item]]'>
|
||||
<paper-item>
|
||||
<paper-item-body>{{ item }}</paper-item-body>
|
||||
<iron-icon icon="hass:chevron-right"></iron-icon>
|
||||
</paper-item>
|
||||
</a>
|
||||
</template>
|
||||
</paper-card>
|
||||
|
||||
<paper-card heading="More Info demos">
|
||||
<div class='card-content intro'>
|
||||
<p>
|
||||
More info screens show up when an entity is clicked.
|
||||
</p>
|
||||
</div>
|
||||
<template is='dom-repeat' items='[[_moreInfoDemos]]'>
|
||||
<a href='#[[item]]'>
|
||||
<paper-item>
|
||||
<paper-item-body>{{ item }}</paper-item-body>
|
||||
<iron-icon icon="hass:chevron-right"></iron-icon>
|
||||
</paper-item>
|
||||
</a>
|
||||
</template>
|
||||
</paper-card>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</app-header-layout>
|
||||
<notification-manager id='notifications'></notification-manager>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_demo: {
|
||||
type: String,
|
||||
value: document.location.hash.substr(1),
|
||||
observer: '_demoChanged',
|
||||
},
|
||||
_demos: {
|
||||
type: Array,
|
||||
value: DEMOS.keys().map(fixPath)
|
||||
},
|
||||
_lovelaceDemos: {
|
||||
type: Array,
|
||||
computed: '_computeLovelace(_demos)',
|
||||
},
|
||||
_moreInfoDemos: {
|
||||
type: Array,
|
||||
computed: '_computeMoreInfos(_demos)',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
|
||||
this.addEventListener(
|
||||
'show-notification',
|
||||
ev => this.$.notifications.showNotification(ev.detail.message)
|
||||
);
|
||||
|
||||
this.addEventListener('hass-more-info', (ev) => {
|
||||
if (ev.detail.entityId) {
|
||||
this.$.notifications.showNotification(`Showing more info for ${ev.detail.entityId}`);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('hashchange', () => { this._demo = document.location.hash.substr(1); });
|
||||
}
|
||||
|
||||
_withDefault(value, def) {
|
||||
return value || def;
|
||||
}
|
||||
|
||||
_demoChanged(demo) {
|
||||
const root = this.$.demo;
|
||||
|
||||
while (root.lastChild) root.removeChild(root.lastChild);
|
||||
|
||||
if (demo) {
|
||||
DEMOS(`./${demo}.js`);
|
||||
const el = document.createElement(demo);
|
||||
root.appendChild(el);
|
||||
}
|
||||
}
|
||||
|
||||
_computeHeaderButtonClass(demo) {
|
||||
return demo ? '' : 'invisible';
|
||||
}
|
||||
|
||||
_backTapped() {
|
||||
document.location.hash = '';
|
||||
}
|
||||
|
||||
_computeLovelace(demos) {
|
||||
return demos.filter(demo => demo.includes('hui'));
|
||||
}
|
||||
|
||||
_computeMoreInfos(demos) {
|
||||
return demos.filter(demo => demo.includes('more-info'));
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ha-gallery', HaGallery);
|
76
gallery/webpack.config.js
Normal file
@@ -0,0 +1,76 @@
|
||||
const path = require('path');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
const chunkFilename = isProd ?
|
||||
'chunk.[chunkhash].js' : '[name].chunk.js';
|
||||
const buildPath = path.resolve(__dirname, 'dist');
|
||||
const publicPath = isProd ? './' : 'http://localhost:8080/';
|
||||
|
||||
module.exports = {
|
||||
mode: isProd ? 'production' : 'development',
|
||||
// Disabled in prod while we make Home Assistant able to serve the right files.
|
||||
// Was source-map
|
||||
devtool: isProd ? 'none' : 'inline-source-map',
|
||||
entry: './src/entrypoint.js',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
plugins: [
|
||||
// Only support the syntax, Webpack will handle it.
|
||||
'syntax-dynamic-import',
|
||||
[
|
||||
'transform-react-jsx',
|
||||
{
|
||||
pragma: 'h'
|
||||
}
|
||||
],
|
||||
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(html)$/,
|
||||
use: {
|
||||
loader: 'html-loader',
|
||||
options: {
|
||||
exportAsEs6Default: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin([
|
||||
'public',
|
||||
{ from: '../public', to: 'static' },
|
||||
{ from: '../build-translations/output', to: 'static/translations' },
|
||||
{ from: '../node_modules/leaflet/dist/leaflet.css', to: 'static/images/leaflet/' },
|
||||
{ from: '../node_modules/@polymer/font-roboto-local/fonts', to: 'static/fonts' },
|
||||
{ from: '../node_modules/leaflet/dist/images', to: 'static/images/leaflet/' },
|
||||
]),
|
||||
isProd && new UglifyJsPlugin({
|
||||
extractComments: true,
|
||||
sourceMap: true,
|
||||
uglifyOptions: {
|
||||
// Disabling because it broke output
|
||||
mangle: false,
|
||||
}
|
||||
}),
|
||||
].filter(Boolean),
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
chunkFilename: chunkFilename,
|
||||
path: buildPath,
|
||||
publicPath,
|
||||
},
|
||||
devServer: {
|
||||
contentBase: './public',
|
||||
}
|
||||
};
|
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-restricted-syntax": 0
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"no-restricted-syntax": 0,
|
||||
"no-console": 0
|
||||
}
|
||||
}
|
||||
|
@@ -1,58 +0,0 @@
|
||||
const {
|
||||
Analyzer,
|
||||
FSUrlLoader
|
||||
} = require('polymer-analyzer');
|
||||
|
||||
const Bundler = require('polymer-bundler').Bundler;
|
||||
const parse5 = require('parse5');
|
||||
|
||||
const { streamFromString } = require('./stream');
|
||||
|
||||
// Bundle an HTML file and convert it to a stream
|
||||
async function bundledStreamFromHTML(path, bundlerOptions = {}) {
|
||||
const bundler = new Bundler(bundlerOptions);
|
||||
const manifest = await bundler.generateManifest([path]);
|
||||
const result = await bundler.bundle(manifest);
|
||||
return streamFromString(
|
||||
path, parse5.serialize(result.documents.get(path).ast));
|
||||
}
|
||||
|
||||
async function analyze(root, paths) {
|
||||
const analyzer = new Analyzer({
|
||||
urlLoader: new FSUrlLoader(root),
|
||||
});
|
||||
return analyzer.analyze(paths);
|
||||
}
|
||||
|
||||
async function findDependencies(root, element) {
|
||||
const deps = new Set();
|
||||
|
||||
async function resolve(files) {
|
||||
const analysis = await analyze(root, files);
|
||||
const toResolve = [];
|
||||
|
||||
for (const file of files) {
|
||||
const doc = analysis.getDocument(file);
|
||||
|
||||
for (const importEl of doc.getFeatures({ kind: 'import' })) {
|
||||
const url = importEl.url;
|
||||
if (!deps.has(url)) {
|
||||
deps.add(url);
|
||||
toResolve.push(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toResolve.length > 0) {
|
||||
return resolve(toResolve);
|
||||
}
|
||||
}
|
||||
|
||||
await resolve([element]);
|
||||
return deps;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
bundledStreamFromHTML,
|
||||
findDependencies,
|
||||
};
|
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Polymer build strategy to strip imports, even if explictely imported
|
||||
*/
|
||||
module.exports.stripImportsStrategy = function (urls) {
|
||||
return (bundles) => {
|
||||
for (const bundle of bundles) {
|
||||
for (const url of urls) {
|
||||
bundle.stripImports.add(url);
|
||||
}
|
||||
}
|
||||
return bundles;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Polymer build strategy to strip everything but the entrypoints
|
||||
* for bundles that match a specific entry point.
|
||||
*/
|
||||
module.exports.stripAllButEntrypointStrategy = function (entryPoint) {
|
||||
return (bundles) => {
|
||||
for (const bundle of bundles) {
|
||||
if (bundle.entrypoints.size === 1 &&
|
||||
bundle.entrypoints.has(entryPoint)) {
|
||||
for (const file of bundle.files) {
|
||||
if (!bundle.entrypoints.has(file)) {
|
||||
bundle.stripImports.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bundles;
|
||||
};
|
||||
};
|
@@ -1,21 +0,0 @@
|
||||
const stream = require('stream');
|
||||
|
||||
const gutil = require('gulp-util');
|
||||
|
||||
function streamFromString(filename, string) {
|
||||
var src = stream.Readable({ objectMode: true });
|
||||
src._read = function () {
|
||||
this.push(new gutil.File({
|
||||
cwd: '',
|
||||
base: '',
|
||||
path: filename,
|
||||
contents: new Buffer(string)
|
||||
}));
|
||||
this.push(null);
|
||||
};
|
||||
return src;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
streamFromString,
|
||||
};
|
@@ -1,27 +0,0 @@
|
||||
const gulpif = require('gulp-if');
|
||||
|
||||
const babel = require('gulp-babel');
|
||||
const uglify = require('gulp-uglify');
|
||||
const { gulp: cssSlam } = require('css-slam');
|
||||
const htmlMinifier = require('gulp-html-minifier');
|
||||
const { HtmlSplitter } = require('polymer-build');
|
||||
|
||||
module.exports.minifyStream = function (stream) {
|
||||
const sourcesHtmlSplitter = new HtmlSplitter();
|
||||
return stream
|
||||
.pipe(sourcesHtmlSplitter.split())
|
||||
.pipe(gulpif(/[^app]\.js$/, babel({
|
||||
sourceType: 'script',
|
||||
presets: [
|
||||
['es2015', { modules: false }]
|
||||
]
|
||||
})))
|
||||
.pipe(gulpif(/\.js$/, uglify({ sourceMap: false })))
|
||||
.pipe(gulpif(/\.css$/, cssSlam()))
|
||||
.pipe(gulpif(/\.html$/, cssSlam()))
|
||||
.pipe(gulpif(/\.html$/, htmlMinifier({
|
||||
collapseWhitespace: true,
|
||||
removeComments: true
|
||||
})))
|
||||
.pipe(sourcesHtmlSplitter.rejoin());
|
||||
};
|
@@ -1,7 +1,8 @@
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
static_dir: path.resolve(__dirname, '../..'),
|
||||
polymer_dir: path.resolve(__dirname, '..'),
|
||||
build_dir: path.resolve(__dirname, '../build'),
|
||||
output: path.resolve(__dirname, '../hass_frontend'),
|
||||
output_es5: path.resolve(__dirname, '../hass_frontend_es5'),
|
||||
};
|
||||
|
@@ -1,77 +0,0 @@
|
||||
self.addEventListener("push", function(event) {
|
||||
var data;
|
||||
if (event.data) {
|
||||
data = event.data.json();
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(data.title, data)
|
||||
.then(function(notification){
|
||||
firePushCallback({
|
||||
type: "received",
|
||||
tag: data.tag,
|
||||
data: data.data
|
||||
}, data.data.jwt);
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
self.addEventListener('notificationclick', function(event) {
|
||||
var url;
|
||||
|
||||
notificationEventCallback('clicked', event);
|
||||
|
||||
event.notification.close();
|
||||
|
||||
if (!event.notification.data || !event.notification.data.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
url = event.notification.data.url;
|
||||
|
||||
if (!url) return;
|
||||
|
||||
event.waitUntil(
|
||||
clients.matchAll({
|
||||
type: 'window',
|
||||
})
|
||||
.then(function (windowClients) {
|
||||
var i;
|
||||
var client;
|
||||
for (i = 0; i < windowClients.length; i++) {
|
||||
client = windowClients[i];
|
||||
if (client.url === url && 'focus' in client) {
|
||||
return client.focus();
|
||||
}
|
||||
}
|
||||
if (clients.openWindow) {
|
||||
return clients.openWindow(url);
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
);
|
||||
});
|
||||
self.addEventListener('notificationclose', function(event) {
|
||||
notificationEventCallback('closed', event);
|
||||
});
|
||||
|
||||
function notificationEventCallback(event_type, event){
|
||||
firePushCallback({
|
||||
action: event.action,
|
||||
data: event.notification.data,
|
||||
tag: event.notification.tag,
|
||||
type: event_type
|
||||
}, event.notification.data.jwt);
|
||||
}
|
||||
function firePushCallback(payload, jwt){
|
||||
// Don't send the JWT in the payload.data
|
||||
delete payload.data.jwt;
|
||||
// If payload.data is empty then just remove the entire payload.data object.
|
||||
if (Object.keys(payload.data).length === 0 && payload.data.constructor === Object) {
|
||||
delete payload.data;
|
||||
}
|
||||
fetch('/api/notify.html5/callback', {
|
||||
method: 'POST',
|
||||
headers: new Headers({'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer '+jwt}),
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
const filter = require('gulp-filter');
|
||||
const { PolymerProject, } = require('polymer-build');
|
||||
const {
|
||||
composeStrategies,
|
||||
generateShellMergeStrategy,
|
||||
} = require('polymer-bundler');
|
||||
const mergeStream = require('merge-stream');
|
||||
const rename = require('gulp-rename');
|
||||
|
||||
const polymerConfig = require('../../polymer');
|
||||
|
||||
const minifyStream = require('../common/transform').minifyStream;
|
||||
const {
|
||||
stripImportsStrategy,
|
||||
stripAllButEntrypointStrategy
|
||||
} = require('../common/strategy');
|
||||
|
||||
function renamePanel(path) {
|
||||
// Rename panels to be panels/* and not their subdir
|
||||
if (path.basename.substr(0, 9) === 'ha-panel-' && path.extname === '.html') {
|
||||
path.dirname = 'panels/';
|
||||
}
|
||||
|
||||
// Rename frontend
|
||||
if (path.dirname === 'src' && path.basename === 'home-assistant' &&
|
||||
path.extname === '.html') {
|
||||
path.dirname = '';
|
||||
path.basename = 'frontend';
|
||||
}
|
||||
}
|
||||
|
||||
gulp.task('build', ['ru_all', 'build-translations'], () => {
|
||||
const strategy = composeStrategies([
|
||||
generateShellMergeStrategy(polymerConfig.shell),
|
||||
stripImportsStrategy([
|
||||
'bower_components/font-roboto/roboto.html',
|
||||
'bower_components/paper-styles/color.html',
|
||||
]),
|
||||
stripAllButEntrypointStrategy('panels/hassio/ha-panel-hassio.html')
|
||||
]);
|
||||
const project = new PolymerProject(polymerConfig);
|
||||
|
||||
return mergeStream(minifyStream(project.sources()),
|
||||
minifyStream(project.dependencies()))
|
||||
.pipe(project.bundler({
|
||||
strategy,
|
||||
strip: true,
|
||||
sourcemaps: false,
|
||||
stripComments: true,
|
||||
inlineScripts: true,
|
||||
inlineCss: true,
|
||||
implicitStrip: true,
|
||||
}))
|
||||
.pipe(rename(renamePanel))
|
||||
.pipe(filter(['**', '!src/entrypoint.html']))
|
||||
.pipe(gulp.dest('build/'));
|
||||
});
|
@@ -1,6 +0,0 @@
|
||||
const del = require('del');
|
||||
const gulp = require('gulp');
|
||||
|
||||
gulp.task('clean', () => {
|
||||
return del(['build', 'build-temp']);
|
||||
});
|
@@ -1,9 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
const runSequence = require('run-sequence');
|
||||
|
||||
gulp.task('default', () => {
|
||||
return runSequence.use(gulp)(
|
||||
'clean',
|
||||
'build'
|
||||
);
|
||||
});
|
106
gulp/tasks/gen-icons.js
Normal file
@@ -0,0 +1,106 @@
|
||||
const gulp = require('gulp');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const config = require('../config');
|
||||
|
||||
const ICON_PACKAGE_PATH = path.resolve(__dirname, '../../node_modules/@mdi/svg/');
|
||||
const META_PATH = path.resolve(ICON_PACKAGE_PATH, 'meta.json');
|
||||
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, 'svg');
|
||||
const OUTPUT_DIR = path.resolve(__dirname, '../../build');
|
||||
const MDI_OUTPUT_PATH = path.resolve(OUTPUT_DIR, 'mdi.html');
|
||||
const HASS_OUTPUT_PATH = path.resolve(OUTPUT_DIR, 'hass-icons.html');
|
||||
|
||||
const BUILT_IN_PANEL_ICONS = [
|
||||
'calendar', // Calendar
|
||||
'settings', // Config
|
||||
'home-assistant', // Hass.io
|
||||
'poll-box', // History panel
|
||||
'format-list-bulleted-type', // Logbook
|
||||
'mailbox', // Mailbox
|
||||
'account-location', // Map
|
||||
'cart', // Shopping List
|
||||
];
|
||||
|
||||
// Given an icon name, load the SVG file
|
||||
function loadIcon(name) {
|
||||
const iconPath = path.resolve(ICON_PATH, `${name}.svg`);
|
||||
try {
|
||||
return fs.readFileSync(iconPath, 'utf-8');
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Given an SVG file, convert it to an iron-iconset-svg definition
|
||||
function transformXMLtoPolymer(name, xml) {
|
||||
const start = xml.indexOf('><path') + 1;
|
||||
const end = xml.length - start - 6;
|
||||
const path = xml.substr(start, end);
|
||||
return `<g id="${name}">${path}</g>`;
|
||||
}
|
||||
|
||||
// Given an iconset name and icon names, generate a polymer iconset
|
||||
function generateIconset(name, iconNames) {
|
||||
const iconDefs = iconNames.map(name => {
|
||||
const iconDef = loadIcon(name);
|
||||
if (!iconDef) {
|
||||
throw new Error(`Unknown icon referenced: ${name}`);
|
||||
}
|
||||
return transformXMLtoPolymer(name, iconDef)
|
||||
}).join('');
|
||||
return `<ha-iconset-svg name="${name}" size="24"><svg><defs>${iconDefs}</defs></svg></ha-iconset-svg>`;
|
||||
}
|
||||
|
||||
// Generate the full MDI iconset
|
||||
function genMDIIcons() {
|
||||
const meta = JSON.parse(fs.readFileSync(path.resolve(ICON_PACKAGE_PATH, META_PATH), 'UTF-8'));
|
||||
const iconNames = meta.map(iconInfo => iconInfo.name);
|
||||
fs.existsSync(OUTPUT_DIR) || fs.mkdirSync(OUTPUT_DIR);
|
||||
fs.writeFileSync(MDI_OUTPUT_PATH, generateIconset('mdi', iconNames));
|
||||
}
|
||||
|
||||
// Helper function to map recursively over files in a folder and it's subfolders
|
||||
function mapFiles(startPath, filter, mapFunc) {
|
||||
const files = fs.readdirSync(startPath);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const filename = path.join(startPath, files[i]);
|
||||
const stat = fs.lstatSync(filename);
|
||||
if (stat.isDirectory()) {
|
||||
mapFiles(filename, filter, mapFunc);
|
||||
} else if (filename.indexOf(filter) >= 0) {
|
||||
mapFunc(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find all icons used by the project.
|
||||
function findIcons(path, iconsetName) {
|
||||
const iconRegex = new RegExp(`${iconsetName}:[\\w-]+`, 'g');
|
||||
const icons = new Set();
|
||||
function processFile(filename) {
|
||||
const content = fs.readFileSync(filename);
|
||||
let match;
|
||||
// eslint-disable-next-line
|
||||
while (match = iconRegex.exec(content)) {
|
||||
// strip off "hass:" and add to set
|
||||
icons.add(match[0].substr(iconsetName.length + 1));
|
||||
}
|
||||
}
|
||||
mapFiles(path, '.js', processFile);
|
||||
return Array.from(icons);
|
||||
}
|
||||
|
||||
function genHassIcons() {
|
||||
const iconNames = findIcons('./src', 'hass').concat(BUILT_IN_PANEL_ICONS);
|
||||
fs.existsSync(OUTPUT_DIR) || fs.mkdirSync(OUTPUT_DIR);
|
||||
fs.writeFileSync(HASS_OUTPUT_PATH, generateIconset('hass', iconNames));
|
||||
}
|
||||
|
||||
gulp.task('gen-icons-mdi', () => genMDIIcons());
|
||||
gulp.task('gen-icons-hass', () => genHassIcons());
|
||||
gulp.task('gen-icons', ['gen-icons-hass', 'gen-icons-mdi'], () => {});
|
||||
|
||||
module.exports = {
|
||||
findIcons,
|
||||
generateIconset,
|
||||
};
|
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
Generate a caching service worker for HA
|
||||
|
||||
Will be called as part of build_frontend.
|
||||
|
||||
Expects home-assistant-polymer repo as submodule of HA repo.
|
||||
Creates a caching service worker based on the CURRENT content of HA repo.
|
||||
Output service worker to build/service_worker.js
|
||||
|
||||
TODO:
|
||||
- Use gulp streams
|
||||
- Fix minifying the stream
|
||||
*/
|
||||
var gulp = require('gulp');
|
||||
var crypto = require('crypto');
|
||||
var file = require('gulp-file');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var swPrecache = require('sw-precache');
|
||||
var uglifyJS = require('uglify-js');
|
||||
|
||||
const config = require('../config');
|
||||
|
||||
const DEV = !!JSON.parse(process.env.BUILD_DEV || 'true');
|
||||
|
||||
var rootDir = 'hass_frontend';
|
||||
var panelDir = path.resolve(rootDir, 'panels');
|
||||
|
||||
var dynamicUrlToDependencies = {};
|
||||
|
||||
var staticFingerprinted = [
|
||||
'frontend.html',
|
||||
'mdi.html',
|
||||
'core.js',
|
||||
'compatibility.js',
|
||||
'translations/en.json',
|
||||
];
|
||||
|
||||
// These panels will always be registered inside HA and thus can
|
||||
// be safely assumed to be able to preload.
|
||||
var panelsFingerprinted = [
|
||||
'dev-event', 'dev-info', 'dev-service', 'dev-state', 'dev-template',
|
||||
'dev-mqtt', 'kiosk',
|
||||
];
|
||||
|
||||
function md5(filename) {
|
||||
return crypto.createHash('md5')
|
||||
.update(fs.readFileSync(filename)).digest('hex');
|
||||
}
|
||||
|
||||
gulp.task('gen-service-worker', () => {
|
||||
var genPromise = null;
|
||||
if (DEV) {
|
||||
var devBase = 'console.warn("Service worker caching disabled in development")';
|
||||
genPromise = Promise.resolve(devBase);
|
||||
} else {
|
||||
// Create fingerprinted versions of our dependencies.
|
||||
staticFingerprinted.forEach(fn => {
|
||||
var parts = path.parse(fn);
|
||||
var base = parts.dir.length > 0 ? parts.dir + '/' + parts.name : parts.name;
|
||||
var hash = md5(rootDir + '/' + base + parts.ext);
|
||||
var url = '/static/' + base + '-' + hash + parts.ext;
|
||||
var fpath = rootDir + '/' + base + parts.ext;
|
||||
dynamicUrlToDependencies[url] = [fpath];
|
||||
});
|
||||
|
||||
panelsFingerprinted.forEach(panel => {
|
||||
var fpath = panelDir + '/ha-panel-' + panel + '.html';
|
||||
var hash = md5(fpath);
|
||||
var url = '/static/panels/ha-panel-' + panel + '-' + hash + '.html';
|
||||
dynamicUrlToDependencies[url] = [fpath];
|
||||
});
|
||||
var fallbackList = '(?!(?:static|api|local|service_worker.js|manifest.json))';
|
||||
|
||||
var options = {
|
||||
navigateFallback: '/',
|
||||
navigateFallbackWhitelist: [RegExp('^(?:' + fallbackList + '.)*$')],
|
||||
dynamicUrlToDependencies: dynamicUrlToDependencies,
|
||||
staticFileGlobs: [
|
||||
rootDir + '/icons/favicon.ico',
|
||||
rootDir + '/icons/favicon-192x192.png',
|
||||
rootDir + '/webcomponents-lite.min.js',
|
||||
rootDir + '/fonts/roboto/Roboto-Light.ttf',
|
||||
rootDir + '/fonts/roboto/Roboto-Medium.ttf',
|
||||
rootDir + '/fonts/roboto/Roboto-Regular.ttf',
|
||||
rootDir + '/fonts/roboto/Roboto-Bold.ttf',
|
||||
rootDir + '/images/card_media_player_bg.png',
|
||||
],
|
||||
runtimeCaching: [{
|
||||
urlPattern: /\/static\/translations\//,
|
||||
handler: 'cacheFirst',
|
||||
}, {
|
||||
urlPattern: RegExp('^[^/]*/' + fallbackList + '.'),
|
||||
handler: 'fastest',
|
||||
}],
|
||||
stripPrefix: 'hass_frontend',
|
||||
replacePrefix: 'static',
|
||||
verbose: true,
|
||||
// Allow our users to refresh to get latest version.
|
||||
clientsClaim: true,
|
||||
};
|
||||
|
||||
genPromise = swPrecache.generate(options);
|
||||
}
|
||||
|
||||
var swHass = fs.readFileSync(path.resolve(__dirname, '../service-worker.js.tmpl'), 'UTF-8');
|
||||
|
||||
// Fix this
|
||||
// if (!DEV) {
|
||||
// genPromise = genPromise.then(
|
||||
// swString => uglifyJS.minify(swString, { fromString: true }).code);
|
||||
// }
|
||||
|
||||
return genPromise.then(swString => swString + '\n' + swHass)
|
||||
.then(swString => file('service_worker.js', swString)
|
||||
.pipe(gulp.dest(config.build_dir)));
|
||||
});
|
@@ -1,47 +0,0 @@
|
||||
var gulp = require('gulp');
|
||||
const rename = require('gulp-rename');
|
||||
|
||||
const {
|
||||
stripImportsStrategy,
|
||||
} = require('../common/strategy');
|
||||
const minifyStream = require('../common/transform').minifyStream;
|
||||
const {
|
||||
bundledStreamFromHTML,
|
||||
findDependencies
|
||||
} = require('../common/html');
|
||||
|
||||
const { polymer_dir } = require('../config');
|
||||
|
||||
const DEPS_TO_STRIP = [
|
||||
'bower_components/font-roboto/roboto.html',
|
||||
'bower_components/paper-styles/color.html',
|
||||
'bower_components/iron-meta/iron-meta.html',
|
||||
];
|
||||
const DEPS_TO_STRIP_RECURSIVELY = [
|
||||
'bower_components/polymer/polymer.html',
|
||||
];
|
||||
|
||||
gulp.task(
|
||||
'hassio-panel',
|
||||
async () => {
|
||||
const toStrip = [...DEPS_TO_STRIP];
|
||||
|
||||
for (let dep of DEPS_TO_STRIP_RECURSIVELY) {
|
||||
toStrip.push(dep);
|
||||
const deps = await findDependencies(polymer_dir, dep);
|
||||
for (const importUrl of deps) {
|
||||
toStrip.push(importUrl);
|
||||
}
|
||||
}
|
||||
|
||||
const stream = await bundledStreamFromHTML(
|
||||
'panels/hassio/hassio-main.html', {
|
||||
strategy: stripImportsStrategy(toStrip)
|
||||
}
|
||||
);
|
||||
|
||||
return minifyStream(stream)
|
||||
.pipe(rename('hassio-main.html'))
|
||||
.pipe(gulp.dest('build-temp/'));
|
||||
}
|
||||
);
|
@@ -1,30 +0,0 @@
|
||||
const gulp = require('gulp');
|
||||
const rollupEach = require('gulp-rollup-each');
|
||||
const rollupConfig = require('../../rollup.config');
|
||||
|
||||
gulp.task('run_rollup', () => {
|
||||
return gulp.src([
|
||||
'js/core.js',
|
||||
'js/compatibility.js',
|
||||
'js/automation-editor/automation-editor.js',
|
||||
'js/script-editor/script-editor.js',
|
||||
'demo_data/demo_data.js',
|
||||
])
|
||||
.pipe(rollupEach(rollupConfig, rollupConfig))
|
||||
.pipe(gulp.dest('build-temp'));
|
||||
});
|
||||
|
||||
gulp.task('ru_all', ['run_rollup'], () => {
|
||||
gulp.src([
|
||||
'build-temp/core.js',
|
||||
'build-temp/compatibility.js',
|
||||
])
|
||||
.pipe(gulp.dest('build/'));
|
||||
});
|
||||
|
||||
gulp.task('watch_ru_all', ['ru_all'], () => {
|
||||
gulp.watch([
|
||||
'js/**/*.js',
|
||||
'demo_data/**/*.js'
|
||||
], ['ru_all']);
|
||||
});
|
@@ -8,76 +8,195 @@ const minify = require('gulp-jsonminify');
|
||||
const rename = require('gulp-rename');
|
||||
const transform = require('gulp-json-transform');
|
||||
|
||||
const inDir = 'translations'
|
||||
const outDir = 'build/translations';
|
||||
const inDir = 'translations';
|
||||
const workDir = 'build-translations';
|
||||
const fullDir = workDir + '/full';
|
||||
const coreDir = workDir + '/core';
|
||||
const outDir = workDir + '/output';
|
||||
|
||||
// Panel translations which should be split from the core translations. These
|
||||
// should mirror the fragment definitions in polymer.json, so that we load
|
||||
// additional resources at equivalent points.
|
||||
const TRANSLATION_FRAGMENTS = [
|
||||
'config',
|
||||
'history',
|
||||
'logbook',
|
||||
'mailbox',
|
||||
'profile',
|
||||
'shopping-list',
|
||||
'page-authorize',
|
||||
'page-onboarding',
|
||||
];
|
||||
|
||||
const tasks = [];
|
||||
|
||||
function recursive_flatten (prefix, data) {
|
||||
var output = {};
|
||||
function recursiveFlatten(prefix, data) {
|
||||
let output = {};
|
||||
Object.keys(data).forEach(function (key) {
|
||||
if (typeof(data[key]) === 'object') {
|
||||
output = Object.assign({}, output, recursive_flatten(key + '.', data[key]));
|
||||
if (typeof (data[key]) === 'object') {
|
||||
output = Object.assign({}, output, recursiveFlatten(prefix + key + '.', data[key]));
|
||||
} else {
|
||||
output[prefix + key] = data[key];
|
||||
}
|
||||
});
|
||||
return output
|
||||
return output;
|
||||
}
|
||||
|
||||
function flatten (data) {
|
||||
return recursive_flatten('', data);
|
||||
function flatten(data) {
|
||||
return recursiveFlatten('', data);
|
||||
}
|
||||
|
||||
var taskName = 'build-translation-native-names';
|
||||
gulp.task(taskName, function() {
|
||||
return gulp.src(inDir + '/*.json')
|
||||
function emptyFilter(data) {
|
||||
const newData = {};
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (data[key]) {
|
||||
if (typeof (data[key]) === 'object') {
|
||||
newData[key] = emptyFilter(data[key]);
|
||||
} else {
|
||||
newData[key] = data[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
return newData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace Lokalise key placeholders with their actual values.
|
||||
*
|
||||
* We duplicate the behavior of Lokalise here so that placeholders can
|
||||
* be included in src/translations/en.json, but still be usable while
|
||||
* developing locally.
|
||||
*
|
||||
* @link https://docs.lokalise.co/article/KO5SZWLLsy-key-referencing
|
||||
*/
|
||||
const re_key_reference = /\[%key:([^%]+)%\]/;
|
||||
function lokalise_transform (data, original) {
|
||||
const output = {};
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
if (value instanceof Object) {
|
||||
output[key] = lokalise_transform(value, original);
|
||||
} else {
|
||||
output[key] = value.replace(re_key_reference, (match, key) => {
|
||||
const replace = key.split('::').reduce((tr, k) => tr[k], original);
|
||||
if (typeof replace !== 'string') {
|
||||
throw Error(`Invalid key placeholder ${key} in src/translations/en.json`);
|
||||
}
|
||||
return replace;
|
||||
});
|
||||
}
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* This task will build a master translation file, to be used as the base for
|
||||
* all languages. This starts with src/translations/en.json, and replaces all
|
||||
* Lokalise key placeholders with their target values. Under normal circumstances,
|
||||
* this will be the same as translations/en.json However, we build it here to
|
||||
* facilitate both making changes in development mode, and to ensure that the
|
||||
* project is buildable immediately after merging new translation keys, since
|
||||
* the Lokalise update to translations/en.json will not happen immediately.
|
||||
*/
|
||||
let taskName = 'build-master-translation';
|
||||
gulp.task(taskName, function () {
|
||||
return gulp.src('src/translations/en.json')
|
||||
.pipe(transform(function(data, file) {
|
||||
// Look up the native name for each language and generate a json
|
||||
// object with all available languages and native names
|
||||
const lang = path.basename(file.relative, '.json');
|
||||
return {[lang]: {nativeName: data.language[lang]}};
|
||||
return lokalise_transform(data, data);
|
||||
}))
|
||||
.pipe(merge({
|
||||
fileName: 'translationNativeNames.json',
|
||||
}))
|
||||
.pipe(gulp.dest('build-temp'));
|
||||
.pipe(rename('translationMaster.json'))
|
||||
.pipe(gulp.dest(workDir));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
var taskName = 'build-merged-translations';
|
||||
gulp.task(taskName, function () {
|
||||
taskName = 'build-merged-translations';
|
||||
gulp.task(taskName, ['build-master-translation'], function () {
|
||||
return gulp.src(inDir + '/*.json')
|
||||
.pipe(foreach(function(stream, file) {
|
||||
// For each language generate a merged json file. It begins with en.json as
|
||||
// a failsafe for untranslated strings, and merges all parent tags into one
|
||||
// file for each specific subtag
|
||||
// For each language generate a merged json file. It begins with the master
|
||||
// translation as a failsafe for untranslated strings, and merges all parent
|
||||
// tags into one file for each specific subtag
|
||||
//
|
||||
// TODO: This is a naive interpretation of BCP47 that should be improved.
|
||||
// Will be OK for now as long as we don't have anything more complicated
|
||||
// than a base translation + region.
|
||||
const tr = path.basename(file.history[0], '.json');
|
||||
const subtags = tr.split('-');
|
||||
const src = [inDir + '/en.json']; // Start with en as a fallback for missing translations
|
||||
for (i = 1; i <= subtags.length; i++) {
|
||||
const src = [workDir + '/translationMaster.json'];
|
||||
for (let i = 1; i <= subtags.length; i++) {
|
||||
const lang = subtags.slice(0, i).join('-');
|
||||
src.push(inDir + '/' + lang + '.json');
|
||||
}
|
||||
return gulp.src(src)
|
||||
.pipe(transform(data => emptyFilter(data)))
|
||||
.pipe(merge({
|
||||
fileName: tr + '.json',
|
||||
}))
|
||||
.pipe(transform(function(data, file) {
|
||||
// Polymer.AppLocalizeBehavior requires flattened json
|
||||
return flatten(data);
|
||||
}))
|
||||
.pipe(minify())
|
||||
.pipe(gulp.dest(outDir));
|
||||
.pipe(gulp.dest(fullDir));
|
||||
}));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
var taskName = 'build-translation-fingerprints';
|
||||
gulp.task(taskName, ['build-merged-translations'], function() {
|
||||
return gulp.src(outDir + '/*.json')
|
||||
const splitTasks = [];
|
||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
||||
taskName = 'build-translation-fragment-' + fragment;
|
||||
gulp.task(taskName, ['build-merged-translations'], function () {
|
||||
// Return only the translations for this fragment.
|
||||
return gulp.src(fullDir + '/*.json')
|
||||
.pipe(transform(data => ({
|
||||
ui: {
|
||||
panel: {
|
||||
[fragment]: data.ui.panel[fragment],
|
||||
},
|
||||
},
|
||||
})))
|
||||
.pipe(gulp.dest(workDir + '/' + fragment));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
splitTasks.push(taskName);
|
||||
});
|
||||
|
||||
taskName = 'build-translation-core';
|
||||
gulp.task(taskName, ['build-merged-translations'], function () {
|
||||
// Remove the fragment translations from the core translation.
|
||||
return gulp.src(fullDir + '/*.json')
|
||||
.pipe(transform((data) => {
|
||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
||||
delete data.ui.panel[fragment];
|
||||
});
|
||||
return data;
|
||||
}))
|
||||
.pipe(gulp.dest(coreDir));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
splitTasks.push(taskName);
|
||||
|
||||
taskName = 'build-flattened-translations';
|
||||
gulp.task(taskName, splitTasks, function () {
|
||||
// Flatten the split versions of our translations, and move them into outDir
|
||||
return gulp.src(
|
||||
TRANSLATION_FRAGMENTS.map(fragment => workDir + '/' + fragment + '/*.json')
|
||||
.concat(coreDir + '/*.json'),
|
||||
{ base: workDir },
|
||||
)
|
||||
.pipe(transform(function (data) {
|
||||
// Polymer.AppLocalizeBehavior requires flattened json
|
||||
return flatten(data);
|
||||
}))
|
||||
.pipe(minify())
|
||||
.pipe(rename((filePath) => {
|
||||
if (filePath.dirname === 'core') {
|
||||
filePath.dirname = '';
|
||||
}
|
||||
}))
|
||||
.pipe(gulp.dest(outDir));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
taskName = 'build-translation-fingerprints';
|
||||
gulp.task(taskName, ['build-flattened-translations'], function () {
|
||||
return gulp.src(outDir + '/**/*.json')
|
||||
.pipe(rename({
|
||||
extname: "",
|
||||
extname: '',
|
||||
}))
|
||||
.pipe(hash({
|
||||
algorithm: 'md5',
|
||||
@@ -85,26 +204,55 @@ gulp.task(taskName, ['build-merged-translations'], function() {
|
||||
template: '<%= name %>-<%= hash %>.json',
|
||||
}))
|
||||
.pipe(hash.manifest('translationFingerprints.json'))
|
||||
.pipe(transform(function(data, file) {
|
||||
Object.keys(data).map(function(key, index) {
|
||||
data[key] = {fingerprint: data[key]};
|
||||
.pipe(transform(function (data) {
|
||||
// After generating fingerprints of our translation files, consolidate
|
||||
// all translation fragment fingerprints under the translation name key
|
||||
const newData = {};
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
const parts = key.split('/');
|
||||
let translation = key;
|
||||
if (parts.length === 2) {
|
||||
translation = parts[1];
|
||||
}
|
||||
if (!(translation in newData)) {
|
||||
newData[translation] = {
|
||||
fingerprints: {},
|
||||
};
|
||||
}
|
||||
newData[translation].fingerprints[key] = value;
|
||||
});
|
||||
return data;
|
||||
return newData;
|
||||
}))
|
||||
.pipe(gulp.dest('build-temp'));
|
||||
.pipe(gulp.dest(workDir));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
var taskName = 'build-translations';
|
||||
gulp.task(taskName, ['build-translation-fingerprints', 'build-translation-native-names'], function() {
|
||||
taskName = 'build-translations';
|
||||
gulp.task(taskName, ['build-translation-fingerprints'], function () {
|
||||
return gulp.src([
|
||||
'build-temp/translationFingerprints.json',
|
||||
'build-temp/translationNativeNames.json',
|
||||
])
|
||||
'src/translations/translationMetadata.json',
|
||||
workDir + '/translationFingerprints.json',
|
||||
])
|
||||
.pipe(merge({}))
|
||||
.pipe(insert.wrap('<script>\nwindow.translationMetadata = ', ';\n</script>'))
|
||||
.pipe(rename('translationMetadata.html'))
|
||||
.pipe(gulp.dest('build-temp'));
|
||||
.pipe(transform(function (data) {
|
||||
const newData = {};
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
// Filter out translations without native name.
|
||||
if (data[key].nativeName) {
|
||||
newData[key] = data[key];
|
||||
} else {
|
||||
console.warn(`Skipping language ${key}. Native name was not translated.`);
|
||||
}
|
||||
if (data[key]) newData[key] = value;
|
||||
});
|
||||
return newData;
|
||||
}))
|
||||
.pipe(transform(data => ({
|
||||
fragments: TRANSLATION_FRAGMENTS,
|
||||
translations: data,
|
||||
})))
|
||||
.pipe(rename('translationMetadata.json'))
|
||||
.pipe(gulp.dest(workDir));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
|
8
hassio/config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
// Target directory for the build.
|
||||
buildDir: path.resolve(__dirname, 'build'),
|
||||
// Path where the Hass.io frontend will be publicly available.
|
||||
publicPath: '/api/hassio/app',
|
||||
}
|
14
hassio/script/build_hassio
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Builds the Hass.io app for production
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
OUTPUT_DIR=build
|
||||
|
||||
rm -rf $OUTPUT_DIR
|
||||
|
||||
node script/gen-icons.js
|
||||
NODE_ENV=production ../node_modules/.bin/webpack -p --config webpack.config.js
|
12
hassio/script/develop
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# Run the Hass.io development server
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
OUTPUT_DIR=build
|
||||
|
||||
rm -rf $OUTPUT_DIR
|
||||
mkdir $OUTPUT_DIR
|
||||
node script/gen-icons.js
|
||||
../node_modules/.bin/webpack --watch --progress
|
15
hassio/script/gen-icons.js
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const {
|
||||
findIcons,
|
||||
generateIconset,
|
||||
} = require('../../gulp/tasks/gen-icons.js');
|
||||
|
||||
const MENU_BUTTON_ICON = 'menu';
|
||||
|
||||
function genHassioIcons() {
|
||||
const iconNames = findIcons('./src', 'hassio').concat(MENU_BUTTON_ICON);
|
||||
fs.writeFileSync('./hassio-icons.html', generateIconset('hassio', iconNames));
|
||||
}
|
||||
|
||||
genHassioIcons();
|
73
hassio/src/addon-store/hassio-addon-repository.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/hassio-card-content.js';
|
||||
import '../resources/hassio-style.js';
|
||||
import NavigateMixin from '../../../src/mixins/navigate-mixin.js';
|
||||
|
||||
class HassioAddonRepository extends NavigateMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style hassio-style">
|
||||
paper-card {
|
||||
cursor: pointer;
|
||||
}
|
||||
a.repo {
|
||||
display: block;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
</style>
|
||||
<template is="dom-if" if="[[addons.length]]">
|
||||
<div class="card-group">
|
||||
<div class="title">
|
||||
[[repo.name]]
|
||||
<div class="description">
|
||||
Maintained by [[repo.maintainer]]
|
||||
<a class="repo" href="[[repo.url]]" target="_blank">[[repo.url]]</a>
|
||||
</div>
|
||||
</div>
|
||||
<template is="dom-repeat" items="[[addons]]" as="addon" sort="sortAddons">
|
||||
<paper-card on-click="addonTapped">
|
||||
<div class="card-content">
|
||||
<hassio-card-content hass="[[hass]]" title="[[addon.name]]" description="[[addon.description]]" icon="[[computeIcon(addon)]]" icon-title="[[computeIconTitle(addon)]]" icon-class="[[computeIconClass(addon)]]"></hassio-card-content>
|
||||
</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
repo: Object,
|
||||
addons: Array,
|
||||
};
|
||||
}
|
||||
|
||||
sortAddons(a, b) {
|
||||
return a.name < b.name ? -1 : 1;
|
||||
}
|
||||
|
||||
computeIcon(addon) {
|
||||
return addon.installed && addon.installed !== addon.version ? 'hassio:arrow-up-bold-circle' : 'hassio:puzzle';
|
||||
}
|
||||
|
||||
computeIconTitle(addon) {
|
||||
if (addon.installed) return addon.installed !== addon.version ? 'New version available' : 'Add-on is installed';
|
||||
return 'Add-on is not installed';
|
||||
}
|
||||
|
||||
computeIconClass(addon) {
|
||||
if (addon.installed) return addon.installed !== addon.version ? 'update' : 'installed';
|
||||
return '';
|
||||
}
|
||||
|
||||
addonTapped(ev) {
|
||||
this.navigate(`/hassio/addon/${ev.model.addon.slug}`);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addon-repository', HassioAddonRepository);
|
82
hassio/src/addon-store/hassio-addon-store.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import './hassio-addon-repository.js';
|
||||
import './hassio-repositories-editor.js';
|
||||
|
||||
class HassioAddonStore extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
hassio-addon-repository {
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
<hassio-repositories-editor hass="[[hass]]" repos="[[repos]]"></hassio-repositories-editor>
|
||||
|
||||
<template is="dom-repeat" items="[[repos]]" as="repo" sort="sortRepos">
|
||||
<hassio-addon-repository hass="[[hass]]" repo="[[repo]]" addons="[[computeAddons(repo.slug)]]"></hassio-addon-repository>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
addons: Array,
|
||||
repos: Array,
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
apiCalled(ev) {
|
||||
if (ev.detail.success) {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
|
||||
sortRepos(a, b) {
|
||||
if (a.slug === 'local') {
|
||||
return -1;
|
||||
} else if (b.slug === 'local') {
|
||||
return 1;
|
||||
} else if (a.slug === 'core') {
|
||||
return -1;
|
||||
} else if (b.slug === 'core') {
|
||||
return 1;
|
||||
}
|
||||
return a.name < b.name ? -1 : 1;
|
||||
}
|
||||
|
||||
computeAddons(repo) {
|
||||
return this.addons.filter(function (addon) {
|
||||
return addon.repository === repo;
|
||||
});
|
||||
}
|
||||
|
||||
loadData() {
|
||||
this.hass.callApi('get', 'hassio/addons')
|
||||
.then((info) => {
|
||||
this.addons = info.data.addons;
|
||||
this.repos = info.data.repositories;
|
||||
}, () => {
|
||||
this.addons = [];
|
||||
this.repos = [];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
refreshData() {
|
||||
this.hass.callApi('post', 'hassio/addons/reload')
|
||||
.then(() => {
|
||||
this.loadData();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addon-store', HassioAddonStore);
|
91
hassio/src/addon-store/hassio-repositories-editor.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import '@polymer/iron-icon/iron-icon.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import '@polymer/paper-input/paper-input.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/components/buttons/ha-call-api-button.js';
|
||||
import '../components/hassio-card-content.js';
|
||||
import '../resources/hassio-style.js';
|
||||
|
||||
class HassioRepositoriesEditor extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style hassio-style">
|
||||
.add {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
iron-icon {
|
||||
color: var(--secondary-text-color);
|
||||
margin-right: 16px;
|
||||
display: inline-block;
|
||||
}
|
||||
paper-input {
|
||||
width: calc(100% - 49px);
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
<div class="card-group">
|
||||
<div class="title">
|
||||
Repositories
|
||||
<div class="description">
|
||||
Configure which add-on repositories to fetch data from:
|
||||
</div>
|
||||
</div>
|
||||
<template id="list" is="dom-repeat" items="[[repoList]]" as="repo" sort="sortRepos">
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<hassio-card-content hass="[[hass]]" title="[[repo.name]]" description="[[repo.url]]" icon="hassio:github-circle"></hassio-card-content>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/options" data="[[computeRemoveRepoData(repoList, repo.url)]]" class="warning">Remove</ha-call-api-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
<paper-card>
|
||||
<div class="card-content add">
|
||||
<iron-icon icon="hassio:github-circle"></iron-icon>
|
||||
<paper-input label="Add new repository by URL" value="{{repoUrl}}"></paper-input>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/options" data="[[computeAddRepoData(repoList, repoUrl)]]">Add</ha-call-api-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
repos: {
|
||||
type: Array,
|
||||
observer: 'reposChanged',
|
||||
},
|
||||
repoList: Array,
|
||||
repoUrl: String,
|
||||
};
|
||||
}
|
||||
|
||||
reposChanged(repos) {
|
||||
this.repoList = repos.filter(repo => repo.slug !== 'core' && repo.slug !== 'local');
|
||||
this.repoUrl = '';
|
||||
}
|
||||
|
||||
sortRepos(a, b) {
|
||||
return a.name < b.name ? -1 : 1;
|
||||
}
|
||||
|
||||
computeRemoveRepoData(repoList, url) {
|
||||
const list = repoList.filter(repo => repo.url !== url).map(repo => repo.url);
|
||||
return { addons_repositories: list };
|
||||
}
|
||||
|
||||
computeAddRepoData(repoList, url) {
|
||||
const list = repoList ? repoList.map(repo => repo.url) : [];
|
||||
list.push(url);
|
||||
return { addons_repositories: list };
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-repositories-editor', HassioRepositoriesEditor);
|
115
hassio/src/addon-view/hassio-addon-audio.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import 'web-animations-js/web-animations-next-lite.min.js';
|
||||
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import '@polymer/paper-dropdown-menu/paper-dropdown-menu.js';
|
||||
import '@polymer/paper-item/paper-item.js';
|
||||
import '@polymer/paper-listbox/paper-listbox.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/resources/ha-style.js';
|
||||
import EventsMixin from '../../../src/mixins/events-mixin.js';
|
||||
|
||||
class HassioAddonAudio extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style">
|
||||
:host,
|
||||
paper-card,
|
||||
paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
paper-item {
|
||||
width: 450px;
|
||||
}
|
||||
.card-actions {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
<paper-card heading="Audio">
|
||||
<div class="card-content">
|
||||
<template is="dom-if" if="[[error]]">
|
||||
<div class="errors">[[error]]</div>
|
||||
</template>
|
||||
|
||||
<paper-dropdown-menu label="Input">
|
||||
<paper-listbox slot="dropdown-content" attr-for-selected="device" selected="{{selectedInput}}">
|
||||
<template is="dom-repeat" items="[[inputDevices]]">
|
||||
<paper-item device\$="[[item.device]]">[[item.name]]</paper-item>
|
||||
</template>
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
<paper-dropdown-menu label="Output">
|
||||
<paper-listbox slot="dropdown-content" attr-for-selected="device" selected="{{selectedOutput}}">
|
||||
<template is="dom-repeat" items="[[outputDevices]]">
|
||||
<paper-item device\$="[[item.device]]">[[item.name]]</paper-item>
|
||||
</template>
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<paper-button on-click="_saveSettings">Save</paper-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
addon: {
|
||||
type: Object,
|
||||
observer: 'addonChanged'
|
||||
},
|
||||
inputDevices: Array,
|
||||
outputDevices: Array,
|
||||
selectedInput: String,
|
||||
selectedOutput: String,
|
||||
error: String,
|
||||
};
|
||||
}
|
||||
|
||||
addonChanged(addon) {
|
||||
this.setProperties({
|
||||
selectedInput: addon.audio_input || 'null',
|
||||
selectedOutput: addon.audio_output || 'null'
|
||||
});
|
||||
if (this.outputDevices) return;
|
||||
|
||||
const noDevice = [{ device: 'null', name: '-' }];
|
||||
this.hass.callApi('get', 'hassio/hardware/audio').then((resp) => {
|
||||
const dev = resp.data.audio;
|
||||
const input = Object.keys(dev.input).map(key => ({ device: key, name: dev.input[key] }));
|
||||
const output = Object.keys(dev.output).map(key => ({ device: key, name: dev.output[key] }));
|
||||
this.setProperties({
|
||||
inputDevices: noDevice.concat(input),
|
||||
outputDevices: noDevice.concat(output)
|
||||
});
|
||||
}, () => {
|
||||
this.setProperties({
|
||||
inputDevices: noDevice,
|
||||
outputDevices: noDevice
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_saveSettings() {
|
||||
this.error = null;
|
||||
const path = `hassio/addons/${this.addon.slug}/options`;
|
||||
this.hass.callApi('post', path, {
|
||||
audio_input: this.selectedInput === 'null' ? null : this.selectedInput,
|
||||
audio_output: this.selectedOutput === 'null' ? null : this.selectedOutput
|
||||
}).then(() => {
|
||||
this.fire('hass-api-called', { success: true, path: path });
|
||||
}, (resp) => {
|
||||
this.error = resp.body.message;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addon-audio', HassioAddonAudio);
|
98
hassio/src/addon-view/hassio-addon-config.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea.js';
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/components/buttons/ha-call-api-button.js';
|
||||
|
||||
class HassioAddonConfig extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style">
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
.card-actions {
|
||||
@apply --layout;
|
||||
@apply --layout-justified;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
iron-autogrow-textarea {
|
||||
width: 100%;
|
||||
font-family: monospace;
|
||||
}
|
||||
.syntaxerror {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
</style>
|
||||
<paper-card heading="Config">
|
||||
<div class="card-content">
|
||||
<template is="dom-if" if="[[error]]">
|
||||
<div class="errors">[[error]]</div>
|
||||
</template>
|
||||
<iron-autogrow-textarea id="config" value="{{config}}"></iron-autogrow-textarea>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/options" data="[[resetData]]">Reset to defaults</ha-call-api-button>
|
||||
<paper-button on-click="saveTapped" disabled="[[!configParsed]]">Save</paper-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
addon: {
|
||||
type: Object,
|
||||
observer: 'addonChanged',
|
||||
},
|
||||
addonSlug: String,
|
||||
config: {
|
||||
type: String,
|
||||
observer: 'configChanged',
|
||||
},
|
||||
configParsed: Object,
|
||||
error: String,
|
||||
resetData: {
|
||||
type: Object,
|
||||
value: {
|
||||
options: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
addonChanged(addon) {
|
||||
this.config = addon ? JSON.stringify(addon.options, null, 2) : '';
|
||||
}
|
||||
|
||||
configChanged(config) {
|
||||
try {
|
||||
this.$.config.classList.remove('syntaxerror');
|
||||
this.configParsed = JSON.parse(config);
|
||||
} catch (err) {
|
||||
this.$.config.classList.add('syntaxerror');
|
||||
this.configParsed = null;
|
||||
}
|
||||
}
|
||||
|
||||
saveTapped() {
|
||||
this.error = null;
|
||||
|
||||
this.hass.callApi('post', `hassio/addons/${this.addonSlug}/options`, {
|
||||
options: this.configParsed
|
||||
}).catch((resp) => {
|
||||
this.error = resp.body.message;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addon-config', HassioAddonConfig);
|
227
hassio/src/addon-view/hassio-addon-info.js
Normal file
@@ -0,0 +1,227 @@
|
||||
import '@polymer/iron-icon/iron-icon.js';
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import '@polymer/paper-toggle-button/paper-toggle-button.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/components/buttons/ha-call-api-button.js';
|
||||
import '../../../src/components/ha-markdown.js';
|
||||
import '../../../src/resources/ha-style.js';
|
||||
import EventsMixin from '../../../src/mixins/events-mixin.js';
|
||||
|
||||
import '../components/hassio-card-content.js';
|
||||
|
||||
class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style">
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
paper-card {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.addon-header {
|
||||
@apply --paper-font-headline;
|
||||
}
|
||||
.light-color {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.addon-version {
|
||||
float: right;
|
||||
font-size: 15px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.description {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.logo img {
|
||||
max-height: 60px;
|
||||
margin: 16px 0;
|
||||
display: block;
|
||||
}
|
||||
.state div{
|
||||
width: 150px;
|
||||
display: inline-block;
|
||||
}
|
||||
paper-toggle-button {
|
||||
display: inline;
|
||||
}
|
||||
iron-icon.running {
|
||||
color: var(--paper-green-400);
|
||||
}
|
||||
iron-icon.stopped {
|
||||
color: var(--google-red-300);
|
||||
}
|
||||
ha-call-api-button {
|
||||
font-weight: 500;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
ha-markdown img {
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
<template is="dom-if" if="[[computeUpdateAvailable(addon)]]">
|
||||
<paper-card heading="Update available! 🎉">
|
||||
<div class="card-content">
|
||||
<hassio-card-content hass="[[hass]]" title="[[addon.name]] [[addon.last_version]] is available" description="You are currently running version [[addon.version]]" icon="hassio:arrow-up-bold-circle" icon-class="update"></hassio-card-content>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/addons/[[addonSlug]]/update">Update</ha-call-api-button>
|
||||
<template is="dom-if" if="[[addon.changelog]]">
|
||||
<paper-button on-click="openChangelog">Changelog</paper-button>
|
||||
</template>
|
||||
</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<div class="addon-header">[[addon.name]]
|
||||
<div class="addon-version light-color">
|
||||
<template is="dom-if" if="[[addon.version]]">
|
||||
[[addon.version]]
|
||||
<template is="dom-if" if="[[isRunning]]">
|
||||
<iron-icon title="Add-on is running" class="running" icon="hassio:circle"></iron-icon>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!isRunning]]">
|
||||
<iron-icon title="Add-on is stopped" class="stopped" icon="hassio:circle"></iron-icon>
|
||||
</template>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!addon.version]]">
|
||||
[[addon.last_version]]
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="description light-color">
|
||||
[[addon.description]].<br>
|
||||
Visit <a href="[[addon.url]]" target="_blank">[[addon.name]] page</a> for details.
|
||||
</div>
|
||||
<template is="dom-if" if="[[addon.logo]]">
|
||||
<a href="[[addon.url]]" target="_blank" class="logo">
|
||||
<img src="/api/hassio/addons/[[addonSlug]]/logo">
|
||||
</a>
|
||||
</template>
|
||||
<template is="dom-if" if="[[addon.version]]">
|
||||
<div class="state">
|
||||
<div>Start on boot</div>
|
||||
<paper-toggle-button on-change="startOnBootToggled" checked="[[computeStartOnBoot(addon.boot)]]"></paper-toggle-button>
|
||||
</div>
|
||||
<div class="state">
|
||||
<div>Auto update</div>
|
||||
<paper-toggle-button on-change="autoUpdateToggled" checked="[[addon.auto_update]]"></paper-toggle-button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<template is="dom-if" if="[[addon.version]]">
|
||||
<paper-button class="warning" on-click="_unistallClicked">Uninstall</paper-button>
|
||||
<template is="dom-if" if="[[addon.build]]">
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/rebuild">Rebuild</ha-call-api-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[isRunning]]">
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/restart">Restart</ha-call-api-button>
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/stop">Stop</ha-call-api-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!isRunning]]">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/addons/[[addonSlug]]/start">Start</ha-call-api-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[computeShowWebUI(addon.webui, isRunning)]]">
|
||||
<a href="[[pathWebui(addon.webui)]]" tabindex="-1" target="_blank" class="right"><paper-button>Open web UI</paper-button></a>
|
||||
</template>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!addon.version]]">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/addons/[[addonSlug]]/install">Install</ha-call-api-button>
|
||||
</template>
|
||||
</div>
|
||||
</paper-card>
|
||||
<template is="dom-if" if="[[addon.long_description]]">
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<ha-markdown content="[[addon.long_description]]"></ha-markdown>
|
||||
</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
addon: Object,
|
||||
addonSlug: String,
|
||||
isRunning: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsRunning(addon)',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
computeIsRunning(addon) {
|
||||
return addon && addon.state === 'started';
|
||||
}
|
||||
|
||||
computeUpdateAvailable(addon) {
|
||||
return addon && !addon.detached && addon.version && addon.version !== addon.last_version;
|
||||
}
|
||||
|
||||
pathWebui(webui) {
|
||||
return webui && webui.replace('[HOST]', document.location.hostname);
|
||||
}
|
||||
|
||||
computeShowWebUI(webui, isRunning) {
|
||||
return webui && isRunning;
|
||||
}
|
||||
|
||||
computeStartOnBoot(state) {
|
||||
return state === 'auto';
|
||||
}
|
||||
|
||||
startOnBootToggled() {
|
||||
const data = { boot: this.addon.boot === 'auto' ? 'manual' : 'auto' };
|
||||
this.hass.callApi('POST', `hassio/addons/${this.addonSlug}/options`, data);
|
||||
}
|
||||
|
||||
autoUpdateToggled() {
|
||||
const data = { auto_update: !this.addon.auto_update };
|
||||
this.hass.callApi('POST', `hassio/addons/${this.addonSlug}/options`, data);
|
||||
}
|
||||
|
||||
openChangelog() {
|
||||
this.hass.callApi('get', `hassio/addons/${this.addonSlug}/changelog`)
|
||||
.then(
|
||||
resp => resp
|
||||
, () => 'Error getting changelog'
|
||||
).then((content) => {
|
||||
this.fire('hassio-markdown-dialog', {
|
||||
title: 'Changelog',
|
||||
content: content,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_unistallClicked() {
|
||||
if (!confirm('Are you sure you want to uninstall this add-on?')) {
|
||||
return;
|
||||
}
|
||||
const path = `hassio/addons/${this.addonSlug}/uninstall`;
|
||||
const eventData = {
|
||||
path: path,
|
||||
};
|
||||
this.hass.callApi('post', path).then((resp) => {
|
||||
eventData.success = true;
|
||||
eventData.response = resp;
|
||||
}, (resp) => {
|
||||
eventData.success = false;
|
||||
eventData.response = resp;
|
||||
}).then(() => {
|
||||
this.fire('hass-api-called', eventData);
|
||||
});
|
||||
}
|
||||
}
|
||||
customElements.define('hassio-addon-info', HassioAddonInfo);
|
59
hassio/src/addon-view/hassio-addon-logs.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/resources/ha-style.js';
|
||||
|
||||
class HassioAddonLogs extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style">
|
||||
:host,
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
pre {
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
||||
<paper-card heading="Log">
|
||||
<div class="card-content">
|
||||
<pre>[[log]]</pre>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<paper-button on-click="refresh">Refresh</paper-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
addonSlug: {
|
||||
type: String,
|
||||
observer: 'addonSlugChanged',
|
||||
},
|
||||
log: String,
|
||||
};
|
||||
}
|
||||
|
||||
addonSlugChanged(slug) {
|
||||
if (!this.hass) {
|
||||
setTimeout(() => { this.addonChanged(slug); }, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.hass.callApi('get', `hassio/addons/${this.addonSlug}/logs`)
|
||||
.then((info) => {
|
||||
this.log = info;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addon-logs', HassioAddonLogs);
|
108
hassio/src/addon-view/hassio-addon-network.js
Normal file
@@ -0,0 +1,108 @@
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import '@polymer/paper-input/paper-input.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/components/buttons/ha-call-api-button.js';
|
||||
import '../../../src/resources/ha-style.js';
|
||||
import EventsMixin from '../../../src/mixins/events-mixin.js';
|
||||
|
||||
class HassioAddonNetwork extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style">
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-actions {
|
||||
@apply --layout;
|
||||
@apply --layout-justified;
|
||||
}
|
||||
</style>
|
||||
<paper-card heading="Network">
|
||||
<div class="card-content">
|
||||
<template is="dom-if" if="[[error]]">
|
||||
<div class="errors">[[error]]</div>
|
||||
</template>
|
||||
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<th>Container</th>
|
||||
<th>Host</th>
|
||||
</tr>
|
||||
<template is="dom-repeat" items="[[config]]">
|
||||
<tr>
|
||||
<td>
|
||||
[[item.container]]
|
||||
</td>
|
||||
<td>
|
||||
<paper-input value="{{item.host}}" no-label-float=""></paper-input>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody></table>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/options" data="[[resetData]]">Reset to defaults</ha-call-api-button>
|
||||
<paper-button on-click="saveTapped">Save</paper-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
addonSlug: String,
|
||||
config: Object,
|
||||
addon: {
|
||||
type: Object,
|
||||
observer: 'addonChanged',
|
||||
},
|
||||
error: String,
|
||||
resetData: {
|
||||
type: Object,
|
||||
value: {
|
||||
network: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
addonChanged(addon) {
|
||||
if (!addon) return;
|
||||
|
||||
const network = addon.network || {};
|
||||
const items = Object.keys(network).map(key => ({
|
||||
container: key,
|
||||
host: network[key]
|
||||
}));
|
||||
this.config = items.sort(function (el1, el2) { return el1.host - el2.host; });
|
||||
}
|
||||
|
||||
saveTapped() {
|
||||
this.error = null;
|
||||
const data = {};
|
||||
this.config.forEach(function (item) {
|
||||
data[item.container] = parseInt(item.host);
|
||||
});
|
||||
const path = `hassio/addons/${this.addonSlug}/options`;
|
||||
|
||||
this.hass.callApi('post', path, {
|
||||
network: data
|
||||
}).then(() => {
|
||||
this.fire('hass-api-called', { success: true, path: path });
|
||||
}, (resp) => {
|
||||
this.error = resp.body.message;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addon-network', HassioAddonNetwork);
|
148
hassio/src/addon-view/hassio-addon-view.js
Normal file
@@ -0,0 +1,148 @@
|
||||
import '@polymer/app-layout/app-header-layout/app-header-layout.js';
|
||||
import '@polymer/app-layout/app-header/app-header.js';
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/app-route/app-route.js';
|
||||
import '@polymer/paper-icon-button/paper-icon-button.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/components/ha-menu-button.js';
|
||||
import '../../../src/resources/ha-style.js';
|
||||
import '../hassio-markdown-dialog.js';
|
||||
import './hassio-addon-audio.js';
|
||||
import './hassio-addon-config.js';
|
||||
import './hassio-addon-info.js';
|
||||
import './hassio-addon-logs.js';
|
||||
import './hassio-addon-network.js';
|
||||
|
||||
class HassioAddonView extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
:host {
|
||||
color: var(--primary-text-color);
|
||||
--paper-card-header-color: var(--primary-text-color);
|
||||
}
|
||||
.content {
|
||||
padding: 24px 0 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
hassio-addon-info,
|
||||
hassio-addon-network,
|
||||
hassio-addon-audio,
|
||||
hassio-addon-config {
|
||||
margin-bottom: 24px;
|
||||
width: 600px;
|
||||
}
|
||||
hassio-addon-logs {
|
||||
max-width: calc(100% - 8px);
|
||||
min-width: 600px;
|
||||
}
|
||||
@media only screen and (max-width: 600px) {
|
||||
hassio-addon-info,
|
||||
hassio-addon-network,
|
||||
hassio-addon-audio,
|
||||
hassio-addon-config,
|
||||
hassio-addon-logs {
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<app-route route="[[route]]" pattern="/addon/:slug" data="{{routeData}}" active="{{routeMatches}}"></app-route>
|
||||
<app-header-layout has-scrolling-region="">
|
||||
<app-header fixed="" slot="header">
|
||||
<app-toolbar>
|
||||
<ha-menu-button hassio narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button>
|
||||
<paper-icon-button icon="hassio:arrow-left" on-click="backTapped"></paper-icon-button>
|
||||
<div main-title="">Hass.io: add-on details</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
<div class="content">
|
||||
<hassio-addon-info hass="[[hass]]" addon="[[addon]]" addon-slug="[[routeData.slug]]"></hassio-addon-info>
|
||||
|
||||
<template is="dom-if" if="[[addon.version]]">
|
||||
<hassio-addon-config hass="[[hass]]" addon="[[addon]]" addon-slug="[[routeData.slug]]"></hassio-addon-config>
|
||||
|
||||
<template is="dom-if" if="[[addon.audio]]">
|
||||
<hassio-addon-audio hass="[[hass]]" addon="[[addon]]"></hassio-addon-audio>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[addon.network]]">
|
||||
<hassio-addon-network hass="[[hass]]" addon="[[addon]]" addon-slug="[[routeData.slug]]"></hassio-addon-network>
|
||||
</template>
|
||||
|
||||
<hassio-addon-logs hass="[[hass]]" addon-slug="[[routeData.slug]]"></hassio-addon-logs>
|
||||
</template>
|
||||
</div>
|
||||
</app-header-layout>
|
||||
|
||||
<hassio-markdown-dialog title="[[markdownTitle]]" content="[[markdownContent]]"></hassio-markdown-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
showMenu: Boolean,
|
||||
narrow: Boolean,
|
||||
route: Object,
|
||||
routeData: {
|
||||
type: Object,
|
||||
observer: 'routeDataChanged',
|
||||
},
|
||||
routeMatches: Boolean,
|
||||
addon: Object,
|
||||
|
||||
markdownTitle: String,
|
||||
markdownContent: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
|
||||
this.addEventListener('hassio-markdown-dialog', ev => this.openMarkdown(ev));
|
||||
}
|
||||
|
||||
apiCalled(ev) {
|
||||
const path = ev.detail.path;
|
||||
|
||||
if (!path) return;
|
||||
|
||||
if (path.substr(path.lastIndexOf('/') + 1) === 'uninstall') {
|
||||
this.backTapped();
|
||||
} else {
|
||||
this.routeDataChanged(this.routeData);
|
||||
}
|
||||
}
|
||||
|
||||
routeDataChanged(routeData) {
|
||||
if (!this.routeMatches || !routeData || !routeData.slug) return;
|
||||
this.hass.callApi('get', `hassio/addons/${routeData.slug}/info`)
|
||||
.then((info) => {
|
||||
this.addon = info.data;
|
||||
}, () => {
|
||||
this.addon = null;
|
||||
});
|
||||
}
|
||||
|
||||
backTapped() {
|
||||
history.back();
|
||||
}
|
||||
|
||||
openMarkdown(ev) {
|
||||
this.setProperties({
|
||||
markdownTitle: ev.detail.title,
|
||||
markdownContent: ev.detail.content,
|
||||
});
|
||||
this.shadowRoot.querySelector('hassio-markdown-dialog').openDialog();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addon-view', HassioAddonView);
|
75
hassio/src/components/hassio-card-content.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import '@polymer/iron-icon/iron-icon.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/components/ha-relative-time.js';
|
||||
|
||||
class HassioCardContent extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
iron-icon {
|
||||
margin-right: 16px;
|
||||
margin-top: 16px;
|
||||
float: left;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
iron-icon.update {
|
||||
color: var(--paper-orange-400);
|
||||
}
|
||||
iron-icon.running,
|
||||
iron-icon.installed {
|
||||
color: var(--paper-green-400);
|
||||
}
|
||||
iron-icon.hassupdate,
|
||||
iron-icon.snapshot {
|
||||
color: var(--paper-item-icon-color);
|
||||
}
|
||||
.title {
|
||||
color: var(--primary-text-color);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.addition {
|
||||
color: var(--secondary-text-color);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
height: 2.4em;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
ha-relative-time {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<iron-icon icon="[[icon]]" class\$="[[iconClass]]" title="[[iconTitle]]"></iron-icon>
|
||||
<div>
|
||||
<div class="title">[[title]]</div>
|
||||
<div class="addition">
|
||||
<template is="dom-if" if="[[description]]">
|
||||
[[description]]
|
||||
</template>
|
||||
<template is="dom-if" if="[[datetime]]">
|
||||
<ha-relative-time hass="[[hass]]" class="addition" datetime="[[datetime]]"></ha-relative-time>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
title: String,
|
||||
description: String,
|
||||
datetime: String,
|
||||
icon: {
|
||||
type: String,
|
||||
value: 'hass:help-circle'
|
||||
},
|
||||
iconTitle: String,
|
||||
iconClass: String,
|
||||
};
|
||||
}
|
||||
}
|
||||
customElements.define('hassio-card-content', HassioCardContent);
|
73
hassio/src/dashboard/hassio-addons.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/hassio-card-content.js';
|
||||
import '../resources/hassio-style.js';
|
||||
import NavigateMixin from '../../../src/mixins/navigate-mixin.js';
|
||||
|
||||
class HassioAddons extends NavigateMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style hassio-style">
|
||||
paper-card {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<div class="content card-group">
|
||||
<div class="title">Add-ons</div>
|
||||
<template is="dom-if" if="[[!addons.length]]">
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
You don't have any add-ons installed yet. Head over to <a href="#" on-click="openStore">the add-on store</a> to get started!
|
||||
</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
<template is="dom-repeat" items="[[addons]]" as="addon" sort="sortAddons">
|
||||
<paper-card on-click="addonTapped">
|
||||
<div class="card-content">
|
||||
<hassio-card-content hass="[[hass]]" title="[[addon.name]]" description="[[addon.description]]" icon="[[computeIcon(addon)]]" icon-title="[[computeIconTitle(addon)]]" icon-class="[[computeIconClass(addon)]]"></hassio-card-content>
|
||||
</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
addons: Array,
|
||||
};
|
||||
}
|
||||
|
||||
sortAddons(a, b) {
|
||||
return a.name < b.name ? -1 : 1;
|
||||
}
|
||||
|
||||
computeIcon(addon) {
|
||||
return addon.installed !== addon.version ? 'hassio:arrow-up-bold-circle' : 'hassio:puzzle';
|
||||
}
|
||||
|
||||
computeIconTitle(addon) {
|
||||
if (addon.installed !== addon.version) return 'New version available';
|
||||
return addon.state === 'started' ? 'Add-on is running' : 'Add-on is stopped';
|
||||
}
|
||||
|
||||
computeIconClass(addon) {
|
||||
if (addon.installed !== addon.version) return 'update';
|
||||
return addon.state === 'started' ? 'running' : '';
|
||||
}
|
||||
|
||||
addonTapped(ev) {
|
||||
this.navigate('/hassio/addon/' + ev.model.addon.slug);
|
||||
ev.target.blur();
|
||||
}
|
||||
|
||||
openStore(ev) {
|
||||
this.navigate('/hassio/store');
|
||||
ev.target.blur();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addons', HassioAddons);
|
32
hassio/src/dashboard/hassio-dashboard.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import './hassio-addons.js';
|
||||
import './hassio-hass-update.js';
|
||||
import EventsMixin from '../../../src/mixins/events-mixin.js';
|
||||
|
||||
class HassioDashboard extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
.content {
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
<div class="content">
|
||||
<hassio-hass-update hass="[[hass]]" hass-info="[[hassInfo]]"></hassio-hass-update>
|
||||
<hassio-addons hass="[[hass]]" addons="[[supervisorInfo.addons]]"></hassio-addons>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
supervisorInfo: Object,
|
||||
hassInfo: Object,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-dashboard', HassioDashboard);
|
78
hassio/src/dashboard/hassio-hass-update.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/components/buttons/ha-call-api-button.js';
|
||||
import '../components/hassio-card-content.js';
|
||||
import '../resources/hassio-style.js';
|
||||
|
||||
class HassioHassUpdate extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style hassio-style">
|
||||
paper-card {
|
||||
display: block;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
<template is="dom-if" if="[[computeUpdateAvailable(hassInfo)]]">
|
||||
<div class="content">
|
||||
<div class="card-group">
|
||||
<div class="title">Update available! 🎉</div>
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<hassio-card-content hass="[[hass]]" title="Home Assistant [[hassInfo.last_version]] is available" description="You are currently running version [[hassInfo.version]]" icon="hassio:home-assistant" icon-class="hassupdate"></hassio-card-content>
|
||||
<template is="dom-if" if="[[error]]">
|
||||
<div class="error">Error: [[error]]</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/homeassistant/update">Update</ha-call-api-button>
|
||||
<a href="https://github.com/home-assistant/home-assistant/releases" target="_blank"><paper-button>Release notes</paper-button></a>
|
||||
</div>
|
||||
</paper-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
hassInfo: Object,
|
||||
error: String,
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
|
||||
}
|
||||
|
||||
apiCalled(ev) {
|
||||
if (ev.detail.success) {
|
||||
this.errors = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const response = ev.detail.response;
|
||||
|
||||
if (typeof response.body === 'object') {
|
||||
this.errors = response.body.message || 'Unknown error';
|
||||
} else {
|
||||
this.errors = response.body;
|
||||
}
|
||||
}
|
||||
|
||||
computeUpdateAvailable(hassInfo) {
|
||||
return hassInfo.version !== hassInfo.last_version;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-hass-update', HassioHassUpdate);
|
5
hassio/src/entrypoint.js
Normal file
@@ -0,0 +1,5 @@
|
||||
window.loadES5Adapter().then(() => {
|
||||
import(/* webpackChunkName: "hassio-icons" */ './resources/hassio-icons.js');
|
||||
import(/* webpackChunkName: "hassio-main" */ './hassio-main.js');
|
||||
});
|
||||
|
46
hassio/src/hassio-app.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import './hassio-main.js';
|
||||
import './resources/hassio-icons.js';
|
||||
|
||||
class HassioApp extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<template is="dom-if" if="[[hass]]">
|
||||
<hassio-main hass="[[hass]]" narrow="[[narrow]]" show-menu="[[showMenu]]" route="[[route]]"></hassio-main>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
narrow: Boolean,
|
||||
showMenu: Boolean,
|
||||
route: Object,
|
||||
hassioPanel: {
|
||||
type: Object,
|
||||
value: window.parent.hassioPanel,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
window.setProperties = this.setProperties.bind(this);
|
||||
this.addEventListener('location-changed', () => this._locationChanged());
|
||||
this.addEventListener('hass-open-menu', () => this._menuEvent(true));
|
||||
this.addEventListener('hass-close-menu', () => this._menuEvent(false));
|
||||
}
|
||||
|
||||
_menuEvent(shouldOpen) {
|
||||
this.hassioPanel.fire(shouldOpen ? 'hass-open-menu' : 'hass-close-menu');
|
||||
}
|
||||
|
||||
_locationChanged() {
|
||||
this.hassioPanel.navigate(window.location.pathname);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-app', HassioApp);
|
@@ -1,26 +1,22 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
|
||||
|
||||
<script>
|
||||
class HassioData extends Polymer.Element {
|
||||
static get is() { return 'hassio-data'; }
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
class HassioData extends PolymerElement {
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
|
||||
supervisor: {
|
||||
type: Object,
|
||||
value: {},
|
||||
notify: true,
|
||||
},
|
||||
|
||||
host: {
|
||||
type: Object,
|
||||
value: {},
|
||||
notify: true,
|
||||
},
|
||||
|
||||
homeassistant: {
|
||||
type: Object,
|
||||
value: {},
|
||||
notify: true,
|
||||
},
|
||||
};
|
||||
@@ -41,25 +37,24 @@ class HassioData extends Polymer.Element {
|
||||
|
||||
fetchSupervisorInfo() {
|
||||
return this.hass.callApi('get', 'hassio/supervisor/info')
|
||||
.then(function (info) {
|
||||
.then((info) => {
|
||||
this.supervisor = info.data;
|
||||
}.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
fetchHostInfo() {
|
||||
return this.hass.callApi('get', 'hassio/host/info')
|
||||
.then(function (info) {
|
||||
.then((info) => {
|
||||
this.host = info.data;
|
||||
}.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
fetchHassInfo() {
|
||||
return this.hass.callApi('get', 'hassio/homeassistant/info')
|
||||
.then(function (info) {
|
||||
.then((info) => {
|
||||
this.homeassistant = info.data;
|
||||
}.bind(this));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(HassioData.is, HassioData);
|
||||
</script>
|
||||
customElements.define('hassio-data', HassioData);
|
103
hassio/src/hassio-main.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import '@polymer/app-route/app-route.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../src/layouts/hass-loading-screen.js';
|
||||
import './addon-view/hassio-addon-view.js';
|
||||
import './hassio-data.js';
|
||||
import './hassio-pages-with-tabs.js';
|
||||
|
||||
import applyThemesOnElement from '../../src/common/dom/apply_themes_on_element.js';
|
||||
import NavigateMixin from '../../src/mixins/navigate-mixin.js';
|
||||
|
||||
class HassioMain extends NavigateMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<app-route route="[[route]]" pattern="/:page" data="{{routeData}}"></app-route>
|
||||
<hassio-data id="data" hass="[[hass]]" supervisor="{{supervisorInfo}}" homeassistant="{{hassInfo}}" host="{{hostInfo}}"></hassio-data>
|
||||
|
||||
<template is="dom-if" if="[[!loaded]]">
|
||||
<hass-loading-screen narrow="[[narrow]]" show-menu="[[showMenu]]"></hass-loading-screen>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[loaded]]">
|
||||
<template is="dom-if" if="[[!equalsAddon(routeData.page)]]">
|
||||
<hassio-pages-with-tabs hass="[[hass]]" narrow="[[narrow]]" show-menu="[[showMenu]]" page="[[routeData.page]]" supervisor-info="[[supervisorInfo]]" hass-info="[[hassInfo]]" host-info="[[hostInfo]]"></hassio-pages-with-tabs>
|
||||
</template>
|
||||
<template is="dom-if" if="[[equalsAddon(routeData.page)]]">
|
||||
<hassio-addon-view hass="[[hass]]" narrow="[[narrow]]" show-menu="[[showMenu]]" route="[[route]]"></hassio-addon-view>
|
||||
</template>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
narrow: Boolean,
|
||||
showMenu: Boolean,
|
||||
route: {
|
||||
type: Object,
|
||||
// Fake route object
|
||||
value: {
|
||||
prefix: '/hassio',
|
||||
path: '/dashboard',
|
||||
__queryParams: {}
|
||||
},
|
||||
observer: 'routeChanged',
|
||||
},
|
||||
routeData: Object,
|
||||
supervisorInfo: Object,
|
||||
hostInfo: Object,
|
||||
hassInfo: Object,
|
||||
loaded: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsLoaded(supervisorInfo, hostInfo, hassInfo)',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
applyThemesOnElement(this, this.hass.themes, this.hass.selectedTheme, true);
|
||||
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.routeChanged(this.route);
|
||||
}
|
||||
|
||||
apiCalled(ev) {
|
||||
if (ev.detail.success) {
|
||||
let tries = 1;
|
||||
|
||||
const tryUpdate = () => {
|
||||
this.$.data.refresh().catch(function () {
|
||||
tries += 1;
|
||||
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
|
||||
});
|
||||
};
|
||||
|
||||
tryUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
computeIsLoaded(supervisorInfo, hostInfo, hassInfo) {
|
||||
return (supervisorInfo !== null &&
|
||||
hostInfo !== null &&
|
||||
hassInfo !== null);
|
||||
}
|
||||
|
||||
routeChanged(route) {
|
||||
if (route.path === '' && route.prefix === '/hassio') {
|
||||
this.navigate('/hassio/dashboard', true);
|
||||
}
|
||||
}
|
||||
|
||||
equalsAddon(page) {
|
||||
return page && page === 'addon';
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-main', HassioMain);
|
76
hassio/src/hassio-markdown-dialog.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js';
|
||||
import '@polymer/paper-dialog/paper-dialog.js';
|
||||
import '@polymer/paper-icon-button/paper-icon-button.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../src/components/ha-markdown.js';
|
||||
import '../../src/resources/ha-style.js';
|
||||
|
||||
class HassioMarkdownDialog extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style-dialog">
|
||||
paper-dialog {
|
||||
min-width: 350px;
|
||||
font-size: 14px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
app-toolbar {
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
color: var(--primary-text-color);
|
||||
background-color: var(--secondary-background-color);
|
||||
}
|
||||
app-toolbar [main-title] {
|
||||
margin-left: 16px;
|
||||
}
|
||||
paper-checkbox {
|
||||
display: block;
|
||||
margin: 4px;
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
paper-dialog {
|
||||
max-height: 100%;
|
||||
}
|
||||
paper-dialog::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
z-index: -1;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
background-color: inherit;
|
||||
}
|
||||
app-toolbar {
|
||||
color: var(--text-primary-color);
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<paper-dialog id="dialog" with-backdrop="">
|
||||
<app-toolbar>
|
||||
<paper-icon-button icon="hassio:close" dialog-dismiss=""></paper-icon-button>
|
||||
<div main-title="">[[title]]</div>
|
||||
</app-toolbar>
|
||||
<paper-dialog-scrollable>
|
||||
<ha-markdown content="[[content]]"></ha-markdown>
|
||||
</paper-dialog-scrollable>
|
||||
</paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
title: String,
|
||||
content: String,
|
||||
};
|
||||
}
|
||||
|
||||
openDialog() {
|
||||
this.$.dialog.open();
|
||||
}
|
||||
}
|
||||
customElements.define('hassio-markdown-dialog', HassioMarkdownDialog);
|
133
hassio/src/hassio-pages-with-tabs.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import '@polymer/app-layout/app-header-layout/app-header-layout.js';
|
||||
import '@polymer/app-layout/app-header/app-header.js';
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/paper-icon-button/paper-icon-button.js';
|
||||
import '@polymer/paper-tabs/paper-tab.js';
|
||||
import '@polymer/paper-tabs/paper-tabs.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../src/components/ha-menu-button.js';
|
||||
import '../../src/resources/ha-style.js';
|
||||
import './addon-store/hassio-addon-store.js';
|
||||
import './dashboard/hassio-dashboard.js';
|
||||
import './hassio-markdown-dialog.js';
|
||||
import './snapshots/hassio-snapshot.js';
|
||||
import './snapshots/hassio-snapshots.js';
|
||||
import './system/hassio-system.js';
|
||||
|
||||
import scrollToTarget from '../../src/common/dom/scroll-to-target.js';
|
||||
|
||||
import NavigateMixin from '../../src/mixins/navigate-mixin.js';
|
||||
|
||||
class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex iron-positioning ha-style">
|
||||
:host {
|
||||
color: var(--primary-text-color);
|
||||
--paper-card-header-color: var(--primary-text-color);
|
||||
}
|
||||
paper-tabs {
|
||||
margin-left: 12px;
|
||||
--paper-tabs-selection-bar-color: #FFF;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
||||
<app-header-layout id="layout" has-scrolling-region>
|
||||
<app-header fixed slot="header">
|
||||
<app-toolbar>
|
||||
<ha-menu-button hassio narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button>
|
||||
<div main-title>Hass.io</div>
|
||||
<template is="dom-if" if="[[showRefreshButton(page)]]">
|
||||
<paper-icon-button icon="hassio:refresh" on-click="refreshClicked"></paper-icon-button>
|
||||
</template>
|
||||
</app-toolbar>
|
||||
<paper-tabs scrollable="" selected="[[page]]" attr-for-selected="page-name" on-iron-activate="handlePageSelected">
|
||||
<paper-tab page-name="dashboard">Dashboard</paper-tab>
|
||||
<paper-tab page-name="snapshots">Snapshots</paper-tab>
|
||||
<paper-tab page-name="store">Add-on store</paper-tab>
|
||||
<paper-tab page-name="system">System</paper-tab>
|
||||
</paper-tabs>
|
||||
</app-header>
|
||||
<template is="dom-if" if="[[equals(page, "dashboard")]]">
|
||||
<hassio-dashboard hass="[[hass]]" supervisor-info="[[supervisorInfo]]" hass-info="[[hassInfo]]"></hassio-dashboard>
|
||||
</template>
|
||||
<template is="dom-if" if="[[equals(page, "snapshots")]]">
|
||||
<hassio-snapshots hass="[[hass]]" installed-addons="[[supervisorInfo.addons]]" snapshot-slug="{{snapshotSlug}}" snapshot-deleted="{{snapshotDeleted}}"></hassio-snapshots>
|
||||
</template>
|
||||
<template is="dom-if" if="[[equals(page, "store")]]">
|
||||
<hassio-addon-store hass="[[hass]]"></hassio-addon-store>
|
||||
</template>
|
||||
<template is="dom-if" if="[[equals(page, "system")]]">
|
||||
<hassio-system hass="[[hass]]" supervisor-info="[[supervisorInfo]]" host-info="[[hostInfo]]"></hassio-system>
|
||||
</template>
|
||||
</app-header-layout>
|
||||
|
||||
<hassio-markdown-dialog title="[[markdownTitle]]" content="[[markdownContent]]"></hassio-markdown-dialog>
|
||||
|
||||
<template is="dom-if" if="[[equals(page, "snapshots")]]">
|
||||
<hassio-snapshot hass="[[hass]]" snapshot-slug="{{snapshotSlug}}" snapshot-deleted="{{snapshotDeleted}}"></hassio-snapshot>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
showMenu: Boolean,
|
||||
narrow: Boolean,
|
||||
page: String,
|
||||
supervisorInfo: Object,
|
||||
hostInfo: Object,
|
||||
hassInfo: Object,
|
||||
snapshotSlug: String,
|
||||
snapshotDeleted: Boolean,
|
||||
|
||||
markdownTitle: String,
|
||||
markdownContent: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('hassio-markdown-dialog', ev => this.openMarkdown(ev));
|
||||
}
|
||||
|
||||
handlePageSelected(ev) {
|
||||
const newPage = ev.detail.item.getAttribute('page-name');
|
||||
if (newPage !== this.page) {
|
||||
this.navigate(`/hassio/${newPage}`);
|
||||
}
|
||||
scrollToTarget(this, this.$.layout.header.scrollTarget);
|
||||
}
|
||||
|
||||
equals(a, b) {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
showRefreshButton(page) {
|
||||
return page === 'store' || page === 'snapshots';
|
||||
}
|
||||
|
||||
refreshClicked() {
|
||||
if (this.page === 'snapshots') {
|
||||
this.shadowRoot.querySelector('hassio-snapshots').refreshData();
|
||||
} else {
|
||||
this.shadowRoot.querySelector('hassio-addon-store').refreshData();
|
||||
}
|
||||
}
|
||||
|
||||
openMarkdown(ev) {
|
||||
this.setProperties({
|
||||
markdownTitle: ev.detail.title,
|
||||
markdownContent: ev.detail.content,
|
||||
});
|
||||
this.shadowRoot.querySelector('hassio-markdown-dialog').openDialog();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-pages-with-tabs', HassioPagesWithTabs);
|
7
hassio/src/resources/hassio-icons.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import '../../../src/components/ha-iconset-svg.js';
|
||||
import iconSetContent from '../../hassio-icons.html';
|
||||
|
||||
const documentContainer = document.createElement('template');
|
||||
documentContainer.setAttribute('style', 'display: none;');
|
||||
documentContainer.innerHTML = iconSetContent;
|
||||
document.head.appendChild(documentContainer.content);
|
58
hassio/src/resources/hassio-style.js
Normal file
@@ -0,0 +1,58 @@
|
||||
const documentContainer = document.createElement('template');
|
||||
documentContainer.setAttribute('style', 'display: none;');
|
||||
|
||||
documentContainer.innerHTML = `<dom-module id="hassio-style">
|
||||
<template>
|
||||
<style>
|
||||
.card-group {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.card-group .title {
|
||||
color: var(--primary-text-color);
|
||||
font-size: 2em;
|
||||
padding-left: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.card-group .description {
|
||||
font-size: 0.5em;
|
||||
font-weight: 500;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.card-group paper-card {
|
||||
--card-group-columns: 4;
|
||||
width: calc((100% - 12px * var(--card-group-columns)) / var(--card-group-columns));
|
||||
margin: 4px;
|
||||
vertical-align: top;
|
||||
}
|
||||
@media screen and (max-width: 1200px) and (min-width: 901px) {
|
||||
.card-group paper-card {
|
||||
--card-group-columns: 3;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 900px) and (min-width: 601px) {
|
||||
.card-group paper-card {
|
||||
--card-group-columns: 2;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 600px) and (min-width: 0) {
|
||||
.card-group paper-card {
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
}
|
||||
.content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
ha-call-api-button {
|
||||
font-weight: 500;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
</dom-module>`;
|
||||
|
||||
document.head.appendChild(documentContainer.content);
|
255
hassio/src/snapshots/hassio-snapshot.js
Normal file
@@ -0,0 +1,255 @@
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-checkbox/paper-checkbox.js';
|
||||
import '@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js';
|
||||
import '@polymer/paper-dialog/paper-dialog.js';
|
||||
import '@polymer/paper-icon-button/paper-icon-button.js';
|
||||
import '@polymer/paper-input/paper-input.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/resources/ha-style.js';
|
||||
|
||||
class HassioSnapshot extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style-dialog">
|
||||
paper-dialog {
|
||||
min-width: 350px;
|
||||
font-size: 14px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
app-toolbar {
|
||||
margin: 0;
|
||||
padding: 0 16px;
|
||||
color: var(--primary-text-color);
|
||||
background-color: var(--secondary-background-color);
|
||||
}
|
||||
app-toolbar [main-title] {
|
||||
margin-left: 16px;
|
||||
}
|
||||
paper-dialog-scrollable {
|
||||
margin: 0;
|
||||
}
|
||||
paper-checkbox {
|
||||
display: block;
|
||||
margin: 4px;
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
paper-dialog {
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
app-toolbar {
|
||||
color: var(--text-primary-color);
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
.details {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.download {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.warning,
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
</style>
|
||||
<paper-dialog id="dialog" with-backdrop="" on-iron-overlay-closed="_dialogClosed">
|
||||
<app-toolbar>
|
||||
<paper-icon-button icon="hassio:close" dialog-dismiss=""></paper-icon-button>
|
||||
<div main-title="">[[_computeName(snapshot)]]</div>
|
||||
</app-toolbar>
|
||||
<div class="details">
|
||||
[[_computeType(snapshot.type)]] ([[_computeSize(snapshot.size)]])<br>
|
||||
[[_formatDatetime(snapshot.date)]]
|
||||
</div>
|
||||
<div>Home Assistant:</div>
|
||||
<paper-checkbox checked="{{restoreHass}}">
|
||||
Home Assistant [[snapshot.homeassistant]]
|
||||
</paper-checkbox>
|
||||
<template is="dom-if" if="[[snapshot.addons.length]]">
|
||||
<div>Folders:</div>
|
||||
<template is="dom-repeat" items="[[snapshot.folders]]">
|
||||
<paper-checkbox checked="{{item.checked}}">
|
||||
[[item.name]]
|
||||
</paper-checkbox>
|
||||
</template>
|
||||
</template>
|
||||
<template is="dom-if" if="[[snapshot.addons.length]]">
|
||||
<div>Add-ons:</div>
|
||||
<paper-dialog-scrollable>
|
||||
<template is="dom-repeat" items="[[snapshot.addons]]" sort="_sortAddons">
|
||||
<paper-checkbox checked="{{item.checked}}">
|
||||
[[item.name]]
|
||||
<span class="details">([[item.version]])</span>
|
||||
</paper-checkbox>
|
||||
</template>
|
||||
</paper-dialog-scrollable>
|
||||
</template>
|
||||
<template is="dom-if" if="[[snapshot.protected]]">
|
||||
<paper-input autofocus="" label="Password" type="password" value="{{snapshotPassword}}"></paper-input>
|
||||
</template>
|
||||
<template is="dom-if" if="[[error]]">
|
||||
<p class="error">Error: [[error]]</p>
|
||||
</template>
|
||||
<div class="buttons">
|
||||
<paper-icon-button icon="hassio:delete" on-click="_deleteClicked" class="warning" title="Delete snapshot"></paper-icon-button>
|
||||
<a href="[[_computeDownloadUrl(snapshotSlug)]]" download="[[_computeDownloadName(snapshot)]]">
|
||||
<paper-icon-button icon="hassio:download" class="download" title="Download snapshot"></paper-icon-button>
|
||||
</a>
|
||||
<paper-button on-click="_partialRestoreClicked">Restore selected</paper-button>
|
||||
<template is="dom-if" if="[[_isFullSnapshot(snapshot.type)]]">
|
||||
<paper-button on-click="_fullRestoreClicked">Wipe & restore</paper-button>
|
||||
</template>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
snapshotSlug: {
|
||||
type: String,
|
||||
notify: true,
|
||||
observer: '_snapshotSlugChanged',
|
||||
},
|
||||
snapshotDeleted: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
},
|
||||
snapshot: Object,
|
||||
restoreHass: {
|
||||
type: Boolean,
|
||||
value: true,
|
||||
},
|
||||
snapshotPassword: String,
|
||||
error: String,
|
||||
};
|
||||
}
|
||||
|
||||
_snapshotSlugChanged(snapshotSlug) {
|
||||
if (!snapshotSlug || snapshotSlug === 'update') return;
|
||||
this.hass.callApi('get', `hassio/snapshots/${snapshotSlug}/info`)
|
||||
.then((info) => {
|
||||
info.data.folders = this._computeFolders(info.data.folders);
|
||||
info.data.addons = this._computeAddons(info.data.addons);
|
||||
this.snapshot = info.data;
|
||||
this.$.dialog.open();
|
||||
}, () => {
|
||||
this.snapshot = null;
|
||||
});
|
||||
}
|
||||
|
||||
_computeFolders(folders) {
|
||||
const list = [];
|
||||
if (folders.includes('homeassistant')) list.push({ slug: 'homeassistant', name: 'Home Assistant configuration', checked: true });
|
||||
if (folders.includes('ssl')) list.push({ slug: 'ssl', name: 'SSL', checked: true });
|
||||
if (folders.includes('share')) list.push({ slug: 'share', name: 'Share', checked: true });
|
||||
if (folders.includes('addons/local')) list.push({ slug: 'addons/local', name: 'Local add-ons', checked: true });
|
||||
return list;
|
||||
}
|
||||
|
||||
_computeAddons(addons) {
|
||||
return addons.map(addon => (
|
||||
{ slug: addon.slug, name: addon.name, version: addon.version, checked: true }));
|
||||
}
|
||||
|
||||
_isFullSnapshot(type) {
|
||||
return type === 'full';
|
||||
}
|
||||
|
||||
_partialRestoreClicked() {
|
||||
if (!confirm('Are you sure you want to restore this snapshot?')) {
|
||||
return;
|
||||
}
|
||||
const addons = this.snapshot.addons.filter(addon => addon.checked).map(addon => addon.slug);
|
||||
const folders =
|
||||
this.snapshot.folders.filter(folder => folder.checked).map(folder => folder.slug);
|
||||
|
||||
const data = {
|
||||
homeassistant: this.restoreHass,
|
||||
addons: addons,
|
||||
folders: folders
|
||||
};
|
||||
if (this.snapshot.protected) data.password = this.snapshotPassword;
|
||||
|
||||
this.hass.callApi('post', `hassio/snapshots/${this.snapshotSlug}/restore/partial`, data).then(() => {
|
||||
alert('Snapshot restored!');
|
||||
this.$.dialog.close();
|
||||
}, (error) => {
|
||||
this.error = error.body.message;
|
||||
});
|
||||
}
|
||||
|
||||
_fullRestoreClicked() {
|
||||
if (!confirm('Are you sure you want to restore this snapshot?')) {
|
||||
return;
|
||||
}
|
||||
const data = this.snapshot.protected ? { password: this.snapshotPassword } : null;
|
||||
this.hass.callApi('post', `hassio/snapshots/${this.snapshotSlug}/restore/full`, data)
|
||||
.then(() => {
|
||||
alert('Snapshot restored!');
|
||||
this.$.dialog.close();
|
||||
}, (error) => {
|
||||
this.error = error.body.message;
|
||||
});
|
||||
}
|
||||
|
||||
_deleteClicked() {
|
||||
if (!confirm('Are you sure you want to delete this snapshot?')) {
|
||||
return;
|
||||
}
|
||||
this.hass.callApi('post', `hassio/snapshots/${this.snapshotSlug}/remove`)
|
||||
.then(() => {
|
||||
this.$.dialog.close();
|
||||
this.snapshotDeleted = true;
|
||||
}, (error) => {
|
||||
this.error = error.body.message;
|
||||
});
|
||||
}
|
||||
|
||||
_computeDownloadUrl(snapshotSlug) {
|
||||
const password = encodeURIComponent(this.hass.connection.options.authToken);
|
||||
return `/api/hassio/snapshots/${snapshotSlug}/download?api_password=${password}`;
|
||||
}
|
||||
|
||||
_computeDownloadName(snapshot) {
|
||||
const name = this._computeName(snapshot).replace(/[^a-z0-9]+/gi, '_');
|
||||
return `Hass_io_${name}.tar`;
|
||||
}
|
||||
|
||||
_computeName(snapshot) {
|
||||
return snapshot.name || snapshot.slug;
|
||||
}
|
||||
|
||||
_computeType(type) {
|
||||
return type === 'full' ? 'Full snapshot' : 'Partial snapshot';
|
||||
}
|
||||
|
||||
_computeSize(size) {
|
||||
return (Math.ceil(size * 10) / 10) + ' MB';
|
||||
}
|
||||
|
||||
_sortAddons(a, b) {
|
||||
return a.name < b.name ? -1 : 1;
|
||||
}
|
||||
|
||||
_formatDatetime(datetime) {
|
||||
return new Date(datetime).toLocaleDateString(navigator.language, {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
_dialogClosed() {
|
||||
this.snapshotSlug = null;
|
||||
}
|
||||
}
|
||||
customElements.define('hassio-snapshot', HassioSnapshot);
|
262
hassio/src/snapshots/hassio-snapshots.js
Normal file
@@ -0,0 +1,262 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import '@polymer/paper-checkbox/paper-checkbox.js';
|
||||
import '@polymer/paper-input/paper-input.js';
|
||||
import '@polymer/paper-radio-button/paper-radio-button.js';
|
||||
import '@polymer/paper-radio-group/paper-radio-group.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/hassio-card-content.js';
|
||||
import '../resources/hassio-style.js';
|
||||
import EventsMixin from '../../../src/mixins/events-mixin.js';
|
||||
|
||||
class HassioSnapshots extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style hassio-style">
|
||||
paper-radio-group {
|
||||
display: block;
|
||||
}
|
||||
paper-radio-button {
|
||||
padding: 0 0 2px 2px;
|
||||
}
|
||||
paper-radio-button,
|
||||
paper-checkbox,
|
||||
paper-input[type="password"] {
|
||||
display: block;
|
||||
margin: 4px 0 4px 48px;
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<div class="content">
|
||||
<div class="card-group">
|
||||
<div class="title">
|
||||
Create snapshot
|
||||
<div class="description">
|
||||
Snapshots allow you to easily backup and
|
||||
restore all data of your Hass.io instance.
|
||||
</div>
|
||||
</div>
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<paper-input autofocus="" label="Name" value="{{snapshotName}}"></paper-input>
|
||||
Type:
|
||||
<paper-radio-group selected="{{snapshotType}}">
|
||||
<paper-radio-button name="full">
|
||||
Full snapshot
|
||||
</paper-radio-button>
|
||||
<paper-radio-button name="partial">
|
||||
Partial snapshot
|
||||
</paper-radio-button>
|
||||
</paper-radio-group>
|
||||
<template is="dom-if" if="[[!_fullSelected(snapshotType)]]">
|
||||
Folders:
|
||||
<template is="dom-repeat" items="[[folderList]]">
|
||||
<paper-checkbox checked="{{item.checked}}">
|
||||
[[item.name]]
|
||||
</paper-checkbox>
|
||||
</template>
|
||||
Add-ons:
|
||||
<template is="dom-repeat" items="[[addonList]]" sort="_sortAddons">
|
||||
<paper-checkbox checked="{{item.checked}}">
|
||||
[[item.name]]
|
||||
</paper-checkbox>
|
||||
</template>
|
||||
</template>
|
||||
Security:
|
||||
<paper-checkbox checked="{{snapshotHasPassword}}">Password protection</paper-checkbox>
|
||||
<template is="dom-if" if="[[snapshotHasPassword]]">
|
||||
<paper-input label="Password" type="password" value="{{snapshotPassword}}"></paper-input>
|
||||
</template>
|
||||
<template is="dom-if" if="[[error]]">
|
||||
<p class="error">[[error]]</p>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<paper-button disabled="[[creatingSnapshot]]" on-click="_createSnapshot">Create</paper-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
</div>
|
||||
|
||||
<div class="card-group">
|
||||
<div class="title">Available snapshots</div>
|
||||
<template is="dom-if" if="[[!snapshots.length]]">
|
||||
<paper-card>
|
||||
<div class="card-content">You don't have any snapshots yet.</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
<template is="dom-repeat" items="[[snapshots]]" as="snapshot" sort="_sortSnapshots">
|
||||
<paper-card class="pointer" on-click="_snapshotClicked">
|
||||
<div class="card-content">
|
||||
<hassio-card-content hass="[[hass]]" title="[[_computeName(snapshot)]]" description="[[_computeDetails(snapshot)]]" datetime="[[snapshot.date]]" icon="[[_computeIcon(snapshot.type)]]" icon-class="snapshot"></hassio-card-content>
|
||||
</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
snapshotName: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
snapshotPassword: {
|
||||
type: String,
|
||||
value: '',
|
||||
},
|
||||
snapshotHasPassword: Boolean,
|
||||
snapshotType: {
|
||||
type: String,
|
||||
value: 'full',
|
||||
},
|
||||
snapshots: {
|
||||
type: Array,
|
||||
value: [],
|
||||
},
|
||||
installedAddons: {
|
||||
type: Array,
|
||||
observer: '_installedAddonsChanged',
|
||||
},
|
||||
addonList: Array,
|
||||
folderList: {
|
||||
type: Array,
|
||||
value: [
|
||||
{ slug: 'homeassistant', name: 'Home Assistant configuration', checked: true },
|
||||
{ slug: 'ssl', name: 'SSL', checked: true },
|
||||
{ slug: 'share', name: 'Share', checked: true },
|
||||
{ slug: 'addons/local', name: 'Local add-ons', checked: true },
|
||||
],
|
||||
},
|
||||
snapshotSlug: {
|
||||
type: String,
|
||||
notify: true,
|
||||
},
|
||||
snapshotDeleted: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
observer: '_snapshotDeletedChanged',
|
||||
},
|
||||
creatingSnapshot: Boolean,
|
||||
dialogOpened: Boolean,
|
||||
error: String,
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('hass-api-called', ev => this._apiCalled(ev));
|
||||
this._updateSnapshots();
|
||||
}
|
||||
|
||||
_apiCalled(ev) {
|
||||
if (ev.detail.success) {
|
||||
this._updateSnapshots();
|
||||
}
|
||||
}
|
||||
|
||||
_updateSnapshots() {
|
||||
this.hass.callApi('get', 'hassio/snapshots')
|
||||
.then((result) => {
|
||||
this.snapshots = result.data.snapshots;
|
||||
}, (error) => {
|
||||
this.error = error.message;
|
||||
});
|
||||
}
|
||||
|
||||
_createSnapshot() {
|
||||
this.error = '';
|
||||
if (this.snapshotHasPassword && !this.snapshotPassword.length) {
|
||||
this.error = 'Please enter a password.';
|
||||
return;
|
||||
}
|
||||
this.creatingSnapshot = true;
|
||||
let name = this.snapshotName;
|
||||
if (!name.length) {
|
||||
name = new Date().toLocaleDateString(navigator.language, {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric' });
|
||||
}
|
||||
let data;
|
||||
let path;
|
||||
if (this.snapshotType === 'full') {
|
||||
data = { name: name };
|
||||
path = 'hassio/snapshots/new/full';
|
||||
} else {
|
||||
const addons = this.addonList.filter(addon => addon.checked).map(addon => addon.slug);
|
||||
const folders = this.folderList.filter(folder => folder.checked).map(folder => folder.slug);
|
||||
|
||||
data = { name: name, folders: folders, addons: addons };
|
||||
path = 'hassio/snapshots/new/partial';
|
||||
}
|
||||
if (this.snapshotHasPassword) {
|
||||
data.password = this.snapshotPassword;
|
||||
}
|
||||
|
||||
this.hass.callApi('post', path, data)
|
||||
.then(() => {
|
||||
this.creatingSnapshot = false;
|
||||
this.fire('hass-api-called', { success: true });
|
||||
}, (error) => {
|
||||
this.creatingSnapshot = false;
|
||||
this.error = error.message;
|
||||
});
|
||||
}
|
||||
|
||||
_installedAddonsChanged(addons) {
|
||||
this.addonList = addons.map(addon => ({ slug: addon.slug, name: addon.name, checked: true }));
|
||||
}
|
||||
|
||||
_sortAddons(a, b) {
|
||||
return a.name < b.name ? -1 : 1;
|
||||
}
|
||||
|
||||
_sortSnapshots(a, b) {
|
||||
return a.date < b.date ? 1 : -1;
|
||||
}
|
||||
|
||||
_computeName(snapshot) {
|
||||
return snapshot.name || snapshot.slug;
|
||||
}
|
||||
|
||||
_computeDetails(snapshot) {
|
||||
const type = snapshot.type === 'full' ? 'Full snapshot' : 'Partial snapshot';
|
||||
return snapshot.protected ? `${type}, password protected` : type;
|
||||
}
|
||||
|
||||
_computeIcon(type) {
|
||||
return type === 'full' ? 'hassio:package-variant-closed' : 'hassio:package-variant';
|
||||
}
|
||||
|
||||
_snapshotClicked(ev) {
|
||||
this.snapshotSlug = ev.model.snapshot.slug;
|
||||
}
|
||||
|
||||
_fullSelected(type) {
|
||||
return type === 'full';
|
||||
}
|
||||
|
||||
_snapshotDeletedChanged(snapshotDeleted) {
|
||||
if (snapshotDeleted) {
|
||||
this._updateSnapshots();
|
||||
this.snapshotDeleted = false;
|
||||
}
|
||||
}
|
||||
|
||||
refreshData() {
|
||||
this.hass.callApi('post', 'hassio/snapshots/reload')
|
||||
.then(() => {
|
||||
this._updateSnapshots();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-snapshots', HassioSnapshots);
|
186
hassio/src/system/hassio-host-info.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/components/buttons/ha-call-api-button.js';
|
||||
import EventsMixin from '../../../src/mixins/events-mixin.js';
|
||||
|
||||
class HassioHostInfo extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
paper-card {
|
||||
display: inline-block;
|
||||
width: 400px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.card-content {
|
||||
height: 200px;
|
||||
}
|
||||
@media screen and (max-width: 830px) {
|
||||
paper-card {
|
||||
margin-top: 8px;
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.card-content {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.info {
|
||||
width: 100%;
|
||||
}
|
||||
.info td:nth-child(2) {
|
||||
text-align: right;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
margin-top: 16px;
|
||||
}
|
||||
paper-button.info {
|
||||
max-width: calc(50% - 12px);
|
||||
}
|
||||
</style>
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<h2>Host system</h2>
|
||||
<table class="info">
|
||||
<tbody><tr>
|
||||
<td>Hostname</td>
|
||||
<td>[[data.hostname]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>System</td>
|
||||
<td>[[data.operating_system]]</td>
|
||||
</tr>
|
||||
<template is="dom-if" if="[[data.deployment]]">
|
||||
<tr>
|
||||
<td>Deployment</td>
|
||||
<td>[[data.deployment]]</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody></table>
|
||||
<paper-button raised on-click="_showHardware" class="info">
|
||||
Hardware
|
||||
</paper-button>
|
||||
<template is="dom-if" if="[[_featureAvailable(data, 'hostname')]]">
|
||||
<paper-button raised on-click="_changeHostnameClicked" class="info">
|
||||
Change hostname
|
||||
</paper-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[errors]]">
|
||||
<div class="errors">Error: [[errors]]</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<template is="dom-if" if="[[_featureAvailable(data, 'reboot')]]">
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/host/reboot">Reboot</ha-call-api-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_featureAvailable(data, 'shutdown')]]">
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/host/shutdown">Shutdown</ha-call-api-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_featureAvailable(data, 'hassos')]]">
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/hassos/config/sync" title="Load HassOS configs or updates from USB">Import from USB</ha-call-api-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_computeUpdateAvailable(_hassOs)]]">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/hassos/update">Update</ha-call-api-button>
|
||||
</template>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
data: {
|
||||
type: Object,
|
||||
observer: '_dataChanged'
|
||||
},
|
||||
errors: String,
|
||||
_hassOs: Object
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
|
||||
}
|
||||
|
||||
apiCalled(ev) {
|
||||
if (ev.detail.success) {
|
||||
this.errors = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var response = ev.detail.response;
|
||||
|
||||
if (typeof response.body === 'object') {
|
||||
this.errors = response.body.message || 'Unknown error';
|
||||
} else {
|
||||
this.errors = response.body;
|
||||
}
|
||||
}
|
||||
|
||||
_dataChanged(data) {
|
||||
if (data.features && data.features.includes('hassos')) {
|
||||
this.hass.callApi('get', 'hassio/hassos/info')
|
||||
.then((resp) => {
|
||||
this._hassOs = resp.data;
|
||||
});
|
||||
} else {
|
||||
this._hassOs = {};
|
||||
}
|
||||
}
|
||||
|
||||
_computeUpdateAvailable(data) {
|
||||
return data && data.version !== data.version_latest;
|
||||
}
|
||||
|
||||
_featureAvailable(data, feature) {
|
||||
return data && data.features && data.features.includes(feature);
|
||||
}
|
||||
|
||||
_showHardware() {
|
||||
this.hass.callApi('get', 'hassio/hardware/info')
|
||||
.then(
|
||||
resp => this._objectToMarkdown(resp.data)
|
||||
, () => 'Error getting hardware info'
|
||||
).then((content) => {
|
||||
this.fire('hassio-markdown-dialog', {
|
||||
title: 'Hardware',
|
||||
content: content,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_objectToMarkdown(obj, indent = '') {
|
||||
let data = '';
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (typeof obj[key] !== 'object') {
|
||||
data += `${indent}- ${key}: ${obj[key]}\n`;
|
||||
} else {
|
||||
data += `${indent}- ${key}:\n`;
|
||||
if (Array.isArray(obj[key])) {
|
||||
if (obj[key].length) {
|
||||
data += `${indent} - ` + obj[key].join(`\n${indent} - `) + '\n';
|
||||
}
|
||||
} else {
|
||||
data += this._objectToMarkdown(obj[key], ` ${indent}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
_changeHostnameClicked() {
|
||||
const curHostname = this.data.hostname;
|
||||
const hostname = prompt('Please enter a new hostname:', curHostname);
|
||||
if (hostname && hostname !== curHostname) {
|
||||
this.hass.callApi('post', 'hassio/host/options', { hostname });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-host-info', HassioHostInfo);
|
153
hassio/src/system/hassio-supervisor-info.js
Normal file
@@ -0,0 +1,153 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/components/buttons/ha-call-api-button.js';
|
||||
import EventsMixin from '../../../src/mixins/events-mixin.js';
|
||||
|
||||
class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
paper-card {
|
||||
display: inline-block;
|
||||
width: 400px;
|
||||
}
|
||||
.card-content {
|
||||
height: 200px;
|
||||
}
|
||||
@media screen and (max-width: 830px) {
|
||||
paper-card {
|
||||
width: 100%;
|
||||
}
|
||||
.card-content {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.info {
|
||||
width: 100%;
|
||||
}
|
||||
.info td:nth-child(2) {
|
||||
text-align: right;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<h2>Hass.io supervisor</h2>
|
||||
<table class="info">
|
||||
<tbody><tr>
|
||||
<td>Version</td>
|
||||
<td>
|
||||
[[data.version]]
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Latest version</td>
|
||||
<td>[[data.last_version]]</td>
|
||||
</tr>
|
||||
<template is="dom-if" if="[[!_equals(data.channel, "stable")]]">
|
||||
<tr>
|
||||
<td>Channel</td>
|
||||
<td>[[data.channel]]</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody></table>
|
||||
<template is="dom-if" if="[[errors]]">
|
||||
<div class="errors">Error: [[errors]]</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/reload">Reload</ha-call-api-button>
|
||||
<template is="dom-if" if="[[computeUpdateAvailable(data)]]">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/update">Update</ha-call-api-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equals(data.channel, "beta")]]">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/options" data="[[leaveBeta]]">Leave beta channel</ha-call-api-button>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equals(data.channel, "stable")]]">
|
||||
<paper-button on-click="_joinBeta" class="warning" title="Get beta updates for Home Assistant (RCs), supervisor and host">Join beta channel</paper-button>
|
||||
</template>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
data: Object,
|
||||
errors: String,
|
||||
leaveBeta: {
|
||||
type: Object,
|
||||
value: { channel: 'stable' },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
|
||||
}
|
||||
|
||||
apiCalled(ev) {
|
||||
if (ev.detail.success) {
|
||||
this.errors = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var response = ev.detail.response;
|
||||
|
||||
if (typeof response.body === 'object') {
|
||||
this.errors = response.body.message || 'Unknown error';
|
||||
} else {
|
||||
this.errors = response.body;
|
||||
}
|
||||
}
|
||||
|
||||
computeUpdateAvailable(data) {
|
||||
return data.version !== data.last_version;
|
||||
}
|
||||
|
||||
_equals(a, b) {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
_joinBeta() {
|
||||
if (!confirm(`WARNING:
|
||||
Beta releases are for testers and early adopters and can contain unstable code changes. Make sure you have backups of your data before you activate this feature.
|
||||
|
||||
This inludes beta releases for:
|
||||
- Home Assistant (Release Candidates)
|
||||
- Hass.io supervisor
|
||||
- Host system`)) {
|
||||
return;
|
||||
}
|
||||
const method = 'post';
|
||||
const path = 'hassio/supervisor/options';
|
||||
const data = { channel: 'beta' };
|
||||
|
||||
const eventData = {
|
||||
method: method,
|
||||
path: path,
|
||||
data: data,
|
||||
};
|
||||
|
||||
this.hass.callApi(method, path, data)
|
||||
.then((resp) => {
|
||||
eventData.success = true;
|
||||
eventData.response = resp;
|
||||
}, (resp) => {
|
||||
eventData.success = false;
|
||||
eventData.response = resp;
|
||||
}).then(() => {
|
||||
this.fire('hass-api-called', eventData);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-supervisor-info', HassioSupervisorInfo);
|
54
hassio/src/system/hassio-supervisor-log.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
class HassioSupervisorLog extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style">
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
pre {
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<pre>[[log]]</pre>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<paper-button on-click="refreshTapped">Refresh</paper-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
log: String,
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
loadData() {
|
||||
this.hass.callApi('get', 'hassio/supervisor/logs')
|
||||
.then((info) => {
|
||||
this.log = info;
|
||||
}, () => {
|
||||
this.log = 'Error fetching logs';
|
||||
});
|
||||
}
|
||||
|
||||
refreshTapped() {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-supervisor-log', HassioSupervisorLog);
|
43
hassio/src/system/hassio-system.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import './hassio-host-info.js';
|
||||
import './hassio-supervisor-info.js';
|
||||
import './hassio-supervisor-log.js';
|
||||
|
||||
class HassioSystem extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
.content {
|
||||
margin: 4px;
|
||||
}
|
||||
.title {
|
||||
margin-top: 24px;
|
||||
color: var(--primary-text-color);
|
||||
font-size: 2em;
|
||||
padding-left: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
<div class="content">
|
||||
<div class="title">Information</div>
|
||||
<hassio-supervisor-info hass="[[hass]]" data="[[supervisorInfo]]"></hassio-supervisor-info>
|
||||
<hassio-host-info hass="[[hass]]" data="[[hostInfo]]"></hassio-host-info>
|
||||
<div class="title">System log</div>
|
||||
<hassio-supervisor-log hass="[[hass]]"></hassio-supervisor-log>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
supervisorInfo: Object,
|
||||
hostInfo: Object,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-system', HassioSystem);
|
69
hassio/webpack.config.js
Normal file
@@ -0,0 +1,69 @@
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
const CompressionPlugin = require("compression-webpack-plugin");
|
||||
const config = require('./config.js');
|
||||
|
||||
const isProdBuild = process.env.NODE_ENV === 'production'
|
||||
const chunkFilename = isProdBuild ?
|
||||
'chunk.[chunkhash].js' : '[name].chunk.js';
|
||||
|
||||
module.exports = {
|
||||
mode: isProdBuild ? 'production' : 'development',
|
||||
devtool: isProdBuild ? 'source-map' : 'inline-source-map',
|
||||
entry: {
|
||||
entrypoint: './src/entrypoint.js',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: [
|
||||
[require('babel-preset-env').default, { modules: false }]
|
||||
],
|
||||
plugins: [
|
||||
// Only support the syntax, Webpack will handle it.
|
||||
"syntax-dynamic-import",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(html)$/,
|
||||
use: {
|
||||
loader: 'html-loader',
|
||||
options: {
|
||||
exportAsEs6Default: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
isProdBuild && new UglifyJsPlugin({
|
||||
extractComments: true,
|
||||
sourceMap: true,
|
||||
uglifyOptions: {
|
||||
// Disabling because it broke output
|
||||
mangle: false,
|
||||
}
|
||||
}),
|
||||
isProdBuild && new CompressionPlugin({
|
||||
cache: true,
|
||||
exclude: [
|
||||
/\.js\.map$/,
|
||||
/\.LICENSE$/,
|
||||
/\.py$/,
|
||||
/\.txt$/,
|
||||
]
|
||||
}),
|
||||
].filter(Boolean),
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
chunkFilename,
|
||||
path: config.buildDir,
|
||||
publicPath: `${config.publicPath}/`,
|
||||
}
|
||||
};
|