Compare commits
2087 Commits
20191119.1
...
quick-bar-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3fccd63bd3 | ||
![]() |
611202c905 | ||
![]() |
e553f35a68 | ||
![]() |
673649a603 | ||
![]() |
c4ed743370 | ||
![]() |
682fa0d3eb | ||
![]() |
30f34eee22 | ||
![]() |
eab76bf85b | ||
![]() |
bcf405bf9d | ||
![]() |
3c4b0d4a74 | ||
![]() |
fb9bd0eb7d | ||
![]() |
7e2dc04123 | ||
![]() |
54ec37994c | ||
![]() |
4a5935ee36 | ||
![]() |
01b9a07320 | ||
![]() |
0fcf0dcd18 | ||
![]() |
80481f142a | ||
![]() |
2be08ce7ab | ||
![]() |
37eb5af3d4 | ||
![]() |
8c8151be92 | ||
![]() |
baf31d1c1e | ||
![]() |
af2250835a | ||
![]() |
6f2a759ba3 | ||
![]() |
5065901196 | ||
![]() |
41b59e6e11 | ||
![]() |
43afdaadc6 | ||
![]() |
83c5151792 | ||
![]() |
0880ab67c6 | ||
![]() |
c0b2143c7c | ||
![]() |
c1de162c99 | ||
![]() |
a7ef8aba68 | ||
![]() |
3ee4c11a99 | ||
![]() |
990ae10dc2 | ||
![]() |
52b2fd046b | ||
![]() |
9f41f80a91 | ||
![]() |
eec4a91ad8 | ||
![]() |
7c51001c3c | ||
![]() |
a4ea4b1f5f | ||
![]() |
19fc37539e | ||
![]() |
ce7acb0feb | ||
![]() |
105b7678b8 | ||
![]() |
b67575586e | ||
![]() |
3dc6898673 | ||
![]() |
a73754c1b5 | ||
![]() |
1ebf1c00d6 | ||
![]() |
7dac7d757e | ||
![]() |
b1f3192b95 | ||
![]() |
16984d18bb | ||
![]() |
e603893d77 | ||
![]() |
a7998b30c6 | ||
![]() |
3277a4e8c3 | ||
![]() |
7e769d0e14 | ||
![]() |
713e0579f8 | ||
![]() |
6e130cc020 | ||
![]() |
eb036a12d9 | ||
![]() |
534d1f5055 | ||
![]() |
cbef909657 | ||
![]() |
874f3b32b3 | ||
![]() |
2fd017cf73 | ||
![]() |
5740b018a7 | ||
![]() |
288bf6805a | ||
![]() |
02d37a369a | ||
![]() |
1d316c3258 | ||
![]() |
a56ce62f1a | ||
![]() |
c268f42851 | ||
![]() |
7251e802ab | ||
![]() |
5b1a2d10c2 | ||
![]() |
2dd7f292b1 | ||
![]() |
213c53e307 | ||
![]() |
ce07dfd8ac | ||
![]() |
c1dba462e8 | ||
![]() |
47f0d74812 | ||
![]() |
ce80285f8d | ||
![]() |
d2dd1a43dd | ||
![]() |
12d73fe90d | ||
![]() |
c2741638b2 | ||
![]() |
4a7fb3d509 | ||
![]() |
f6ff652ca4 | ||
![]() |
6165cb0f83 | ||
![]() |
1f361b7b10 | ||
![]() |
5269ff978b | ||
![]() |
55595493a9 | ||
![]() |
ad3ff0aba7 | ||
![]() |
ce48546cef | ||
![]() |
35b3bc995e | ||
![]() |
63f60019d1 | ||
![]() |
0d741b6275 | ||
![]() |
0df9080bbb | ||
![]() |
ddcf89e6a2 | ||
![]() |
5de225d5d4 | ||
![]() |
5cddb482f1 | ||
![]() |
c000d724de | ||
![]() |
504055f331 | ||
![]() |
7f6880f40e | ||
![]() |
02e4e3c892 | ||
![]() |
993d73c359 | ||
![]() |
97ca0b818e | ||
![]() |
44166f76d4 | ||
![]() |
557d6d37a1 | ||
![]() |
d3ad56a307 | ||
![]() |
0641022ec5 | ||
![]() |
80c7a8473a | ||
![]() |
d9a954ca91 | ||
![]() |
c219f64322 | ||
![]() |
f7a9ecff21 | ||
![]() |
2b3126ae04 | ||
![]() |
934c227545 | ||
![]() |
cc0515c217 | ||
![]() |
55ba75f2bc | ||
![]() |
c220228566 | ||
![]() |
26b476ab3c | ||
![]() |
b8a67d530f | ||
![]() |
b08c96d2db | ||
![]() |
4773c39a57 | ||
![]() |
892843b290 | ||
![]() |
733244531e | ||
![]() |
66633273e2 | ||
![]() |
0405adcd16 | ||
![]() |
426a7ac8dd | ||
![]() |
3bf6205ff7 | ||
![]() |
c7f4986e61 | ||
![]() |
0f0a3fdaf7 | ||
![]() |
7d6911b140 | ||
![]() |
b8777539d7 | ||
![]() |
b5b1849ab3 | ||
![]() |
0e10c81025 | ||
![]() |
5fc0eaef1a | ||
![]() |
113718c3c1 | ||
![]() |
701bea6cae | ||
![]() |
8d516ed12a | ||
![]() |
667c5744f2 | ||
![]() |
80b7c840e2 | ||
![]() |
919c86796f | ||
![]() |
c90c88ecbf | ||
![]() |
d9ba0e2c46 | ||
![]() |
45b2fc590b | ||
![]() |
17ffdb0247 | ||
![]() |
c2fba15fc6 | ||
![]() |
5937be695f | ||
![]() |
a076fcde84 | ||
![]() |
ede9931903 | ||
![]() |
722e01608c | ||
![]() |
af926370d6 | ||
![]() |
5971aee02e | ||
![]() |
cce7ad449a | ||
![]() |
d437dd5919 | ||
![]() |
f1980730d2 | ||
![]() |
47773e9cae | ||
![]() |
60969b0916 | ||
![]() |
ecc7925d03 | ||
![]() |
6d3010dcc7 | ||
![]() |
0164bafbf1 | ||
![]() |
3940606167 | ||
![]() |
da9faccada | ||
![]() |
7e708b3bf7 | ||
![]() |
05630c9896 | ||
![]() |
369c56db73 | ||
![]() |
9873459169 | ||
![]() |
7776b3766b | ||
![]() |
29c9004654 | ||
![]() |
601c909004 | ||
![]() |
72aa9a3b62 | ||
![]() |
2ecf7bca97 | ||
![]() |
cbdfaccdb2 | ||
![]() |
93d1b9a2d5 | ||
![]() |
bfb5ee794e | ||
![]() |
9ae8bd238b | ||
![]() |
98a64e3114 | ||
![]() |
0171f3aec7 | ||
![]() |
2c827bab9a | ||
![]() |
ec920093d4 | ||
![]() |
4289ff6652 | ||
![]() |
cd32ef60da | ||
![]() |
6ef3d091e1 | ||
![]() |
0f59496778 | ||
![]() |
795b670b3c | ||
![]() |
4ad65e3d69 | ||
![]() |
f48b1150cf | ||
![]() |
198db8bc68 | ||
![]() |
3ae447ca44 | ||
![]() |
a2ec878ef0 | ||
![]() |
d43b5d3337 | ||
![]() |
11e3503dc2 | ||
![]() |
accf44b769 | ||
![]() |
b612c0e0d6 | ||
![]() |
771c7518e6 | ||
![]() |
4f2bad034a | ||
![]() |
0456669aeb | ||
![]() |
2eb71321e7 | ||
![]() |
d2e9e22e4e | ||
![]() |
e2427c8dce | ||
![]() |
26162815c8 | ||
![]() |
d0c86ea2f7 | ||
![]() |
4f5b4d4472 | ||
![]() |
10d71587e7 | ||
![]() |
9ac777d687 | ||
![]() |
7d1f9f3981 | ||
![]() |
a214ab463e | ||
![]() |
2f21f6ef8a | ||
![]() |
590cd8500d | ||
![]() |
f48a28264f | ||
![]() |
51953bce09 | ||
![]() |
c445d4b839 | ||
![]() |
9dabce1dd7 | ||
![]() |
6b77f08d86 | ||
![]() |
50e03d41ab | ||
![]() |
4759c89628 | ||
![]() |
458ab9b1e0 | ||
![]() |
dfb2a7153b | ||
![]() |
e9141d82f3 | ||
![]() |
d9f42712e4 | ||
![]() |
da0658f3d9 | ||
![]() |
412a5a6d8e | ||
![]() |
9cf9e6edd8 | ||
![]() |
28050fc9fb | ||
![]() |
ea9f227fa8 | ||
![]() |
05164e90fb | ||
![]() |
97156ccf8a | ||
![]() |
003d55968e | ||
![]() |
98dd21b75c | ||
![]() |
a349e34bc2 | ||
![]() |
245c825cbf | ||
![]() |
4ba6698c4b | ||
![]() |
dac6edea90 | ||
![]() |
b2e2dbe0c2 | ||
![]() |
f822fa0d57 | ||
![]() |
bbee94edba | ||
![]() |
c39c408332 | ||
![]() |
dde4a40e69 | ||
![]() |
1afa4d2868 | ||
![]() |
d25f371d6c | ||
![]() |
948261d1d1 | ||
![]() |
04b7749f85 | ||
![]() |
8668f0ff5b | ||
![]() |
b7d8ff6c99 | ||
![]() |
0894a30f13 | ||
![]() |
bd511887a7 | ||
![]() |
0304c0eca0 | ||
![]() |
6d5c6e2fbc | ||
![]() |
e7e7912519 | ||
![]() |
b9c41d8f99 | ||
![]() |
25a2264ac3 | ||
![]() |
ad64657c74 | ||
![]() |
106f18a73a | ||
![]() |
0fb38140a6 | ||
![]() |
60133941ae | ||
![]() |
76db64c073 | ||
![]() |
24b390ba03 | ||
![]() |
b4d34d9085 | ||
![]() |
f80bea27a9 | ||
![]() |
4b846964be | ||
![]() |
e6357e2eef | ||
![]() |
d538b6145f | ||
![]() |
229ba3a75c | ||
![]() |
672b867847 | ||
![]() |
ca1dacd621 | ||
![]() |
26abb4301d | ||
![]() |
045dac59af | ||
![]() |
1ffa87ea23 | ||
![]() |
357df2bf68 | ||
![]() |
24f3db3e3f | ||
![]() |
4bb51b0120 | ||
![]() |
32b9c1eba1 | ||
![]() |
40bffc1d25 | ||
![]() |
ed2c4e2642 | ||
![]() |
b3353e220a | ||
![]() |
7dc917babf | ||
![]() |
00dbba04a2 | ||
![]() |
3efd1a0451 | ||
![]() |
b7d7ca4014 | ||
![]() |
854a54e9c6 | ||
![]() |
4f4edb109f | ||
![]() |
265bfeb889 | ||
![]() |
96110637d9 | ||
![]() |
ad34f98e6d | ||
![]() |
a8a1563586 | ||
![]() |
9b25a54a47 | ||
![]() |
4b8c96c769 | ||
![]() |
c62ff85e73 | ||
![]() |
7d5a27ec0f | ||
![]() |
d6aba040dd | ||
![]() |
ca4757db5b | ||
![]() |
c917b67cbd | ||
![]() |
9659c97978 | ||
![]() |
7d862d6f2a | ||
![]() |
9c80776d8c | ||
![]() |
d5cd288fe8 | ||
![]() |
239e817779 | ||
![]() |
1986215919 | ||
![]() |
239f5f1a2f | ||
![]() |
3bca32c6d5 | ||
![]() |
183eff745d | ||
![]() |
4392d78ff6 | ||
![]() |
858196ab53 | ||
![]() |
fb75d8c1f2 | ||
![]() |
7628569579 | ||
![]() |
8a9d5f7753 | ||
![]() |
cdcccf5089 | ||
![]() |
de95c92e2d | ||
![]() |
3030b8d476 | ||
![]() |
92ed14c0e4 | ||
![]() |
5b94a4de9a | ||
![]() |
709112c498 | ||
![]() |
e465ec8835 | ||
![]() |
f6eb31bf9d | ||
![]() |
426f939982 | ||
![]() |
fab6cebf0d | ||
![]() |
ff081dd0f1 | ||
![]() |
868399ed6f | ||
![]() |
1bc9b95289 | ||
![]() |
9af805ab5e | ||
![]() |
6b88081360 | ||
![]() |
667c828359 | ||
![]() |
50d37ce4f6 | ||
![]() |
af0246cd27 | ||
![]() |
857e4e49d8 | ||
![]() |
c1afed7f98 | ||
![]() |
5480e54185 | ||
![]() |
99d0a0a6fd | ||
![]() |
8a998369d6 | ||
![]() |
8b490c5047 | ||
![]() |
7e70ba6ab2 | ||
![]() |
90e09fc384 | ||
![]() |
266f2e763d | ||
![]() |
c979cfb912 | ||
![]() |
8ee29b1e43 | ||
![]() |
26fbc07cac | ||
![]() |
f01fe65be4 | ||
![]() |
3fdd6a80f9 | ||
![]() |
da1de8db1d | ||
![]() |
dd1bf7b49d | ||
![]() |
f18913b5a0 | ||
![]() |
a2cd227f1a | ||
![]() |
78e64e1f60 | ||
![]() |
23a9b79320 | ||
![]() |
76394ce341 | ||
![]() |
1935df1faa | ||
![]() |
5af4ce28ab | ||
![]() |
ce8ee569c4 | ||
![]() |
b0508f430e | ||
![]() |
2139a80a7a | ||
![]() |
aa4bc2ce03 | ||
![]() |
fa65f84e09 | ||
![]() |
c06357a351 | ||
![]() |
092a02a624 | ||
![]() |
b9699f745f | ||
![]() |
7fa9f10c30 | ||
![]() |
7bf0655dae | ||
![]() |
96c5fdcbeb | ||
![]() |
c2e6d40382 | ||
![]() |
810d2a1ceb | ||
![]() |
af74f21af9 | ||
![]() |
cdf7558a8e | ||
![]() |
41b86e6c10 | ||
![]() |
085c6f8bdd | ||
![]() |
3039c678a5 | ||
![]() |
498882d014 | ||
![]() |
6c2b8c2abb | ||
![]() |
e955cc4378 | ||
![]() |
eb96dd4803 | ||
![]() |
e0bdef98a6 | ||
![]() |
1130007d14 | ||
![]() |
d99d092784 | ||
![]() |
e3b18a33ca | ||
![]() |
1890aab1e6 | ||
![]() |
42bf350034 | ||
![]() |
5ff52ea113 | ||
![]() |
432e3ba636 | ||
![]() |
f7ab52fe9a | ||
![]() |
ad8430049d | ||
![]() |
2dffe7ba9e | ||
![]() |
5b8f97e0f6 | ||
![]() |
b3a763a48d | ||
![]() |
07569f10b5 | ||
![]() |
7c5a78a1cf | ||
![]() |
5cca5bfe86 | ||
![]() |
0e021e7d7d | ||
![]() |
b30ee884a7 | ||
![]() |
869b7c85ca | ||
![]() |
4d0d1ed2a1 | ||
![]() |
291983e4c3 | ||
![]() |
909cff2158 | ||
![]() |
4e676b1dba | ||
![]() |
9149bb9333 | ||
![]() |
4631994f20 | ||
![]() |
82e9178320 | ||
![]() |
67b4688168 | ||
![]() |
6e0e169b6e | ||
![]() |
100ba8edfa | ||
![]() |
d7448ecb95 | ||
![]() |
8b1801f378 | ||
![]() |
01a4d57566 | ||
![]() |
7edc9064d9 | ||
![]() |
30c47a65f4 | ||
![]() |
0889f42a00 | ||
![]() |
4999f1ad51 | ||
![]() |
f15fbe53cf | ||
![]() |
046f7b5153 | ||
![]() |
5339fe6e06 | ||
![]() |
de7ffb10cb | ||
![]() |
80224e6974 | ||
![]() |
0c7c536f73 | ||
![]() |
e5c386c39a | ||
![]() |
bb2462483e | ||
![]() |
d5bc498373 | ||
![]() |
979b7ae651 | ||
![]() |
c73330a466 | ||
![]() |
efe8eca4e3 | ||
![]() |
a37aad18b7 | ||
![]() |
cfa0c45213 | ||
![]() |
509481ef06 | ||
![]() |
9aa8175e23 | ||
![]() |
76f59d99a2 | ||
![]() |
bd66bd6cf0 | ||
![]() |
d69333dea4 | ||
![]() |
3fd7899b93 | ||
![]() |
8f8a2cea56 | ||
![]() |
879011c8e9 | ||
![]() |
d5794c3e2e | ||
![]() |
61dbae8b8b | ||
![]() |
fcc22ba560 | ||
![]() |
2adeb88fe6 | ||
![]() |
e63a78bcdb | ||
![]() |
b065f002a4 | ||
![]() |
349a5f52b1 | ||
![]() |
aa5e20df05 | ||
![]() |
793b9f238c | ||
![]() |
9c4fdaa4f3 | ||
![]() |
d1a9cb488a | ||
![]() |
faee2c3e1b | ||
![]() |
b7845c318e | ||
![]() |
426a0727c3 | ||
![]() |
584e509a9c | ||
![]() |
f3639c2663 | ||
![]() |
1431e75f8b | ||
![]() |
be8812e0af | ||
![]() |
fd6436d490 | ||
![]() |
fd1342f9d1 | ||
![]() |
5fa0012195 | ||
![]() |
9dbb67ef01 | ||
![]() |
d16e2f37d4 | ||
![]() |
d9e8b53ffe | ||
![]() |
1997e63b7c | ||
![]() |
6f673359ff | ||
![]() |
45dfbff10a | ||
![]() |
348ee96274 | ||
![]() |
8edee32e77 | ||
![]() |
6d8d263ca6 | ||
![]() |
35923709e2 | ||
![]() |
fdd4d53448 | ||
![]() |
06419f662e | ||
![]() |
57763ef032 | ||
![]() |
8e506f7749 | ||
![]() |
c7f8fe1468 | ||
![]() |
4156a4e36d | ||
![]() |
ba3cc7df0f | ||
![]() |
0c212d39eb | ||
![]() |
3bd2e8dbf5 | ||
![]() |
5292119e6e | ||
![]() |
994a397231 | ||
![]() |
353b71f803 | ||
![]() |
eb12afe8cc | ||
![]() |
4a176f1b43 | ||
![]() |
8e228baa82 | ||
![]() |
154b53b0d8 | ||
![]() |
a3f680d80c | ||
![]() |
0d75fe6b81 | ||
![]() |
4070380ded | ||
![]() |
41195dcef0 | ||
![]() |
78a1e45be2 | ||
![]() |
d8e88bc58d | ||
![]() |
448e9b71b8 | ||
![]() |
2e178164cc | ||
![]() |
9f2e3f05a1 | ||
![]() |
405bd29ebd | ||
![]() |
b39b54e0ac | ||
![]() |
119c5c9071 | ||
![]() |
7a4c9b128c | ||
![]() |
dc5b92030f | ||
![]() |
db0a010d7c | ||
![]() |
a117a19bdf | ||
![]() |
5f46fdb406 | ||
![]() |
f0201de4cc | ||
![]() |
6cd51a318b | ||
![]() |
c1a4b27bc7 | ||
![]() |
5113222050 | ||
![]() |
90f12eea5e | ||
![]() |
2403743701 | ||
![]() |
3e6a759309 | ||
![]() |
35a430e9f4 | ||
![]() |
b644f7d23d | ||
![]() |
7702a05464 | ||
![]() |
493af5fe82 | ||
![]() |
ac66a59cec | ||
![]() |
e10c8faa47 | ||
![]() |
9b7d17433c | ||
![]() |
a40eb1ff43 | ||
![]() |
04df6c3e9e | ||
![]() |
1b970e5a66 | ||
![]() |
75406c2d01 | ||
![]() |
64d3511fbc | ||
![]() |
c610f54977 | ||
![]() |
090ad34f78 | ||
![]() |
358c5205d2 | ||
![]() |
5503cd0589 | ||
![]() |
dae42b1bd9 | ||
![]() |
06a25284e8 | ||
![]() |
5989560f15 | ||
![]() |
63c995e5da | ||
![]() |
dc5607f554 | ||
![]() |
d49302c032 | ||
![]() |
63fef9bd4b | ||
![]() |
6599351d45 | ||
![]() |
47e9531972 | ||
![]() |
3ba31483f4 | ||
![]() |
f4ca94f2e1 | ||
![]() |
67f9be2b77 | ||
![]() |
e2fd155e1b | ||
![]() |
931068dede | ||
![]() |
bc4c9cc40d | ||
![]() |
294665fbe8 | ||
![]() |
e8f6a79c8f | ||
![]() |
5fd8b5c5b9 | ||
![]() |
226b2a73af | ||
![]() |
42d421a6fc | ||
![]() |
a90203f256 | ||
![]() |
c3ef79caa9 | ||
![]() |
1439afcd9c | ||
![]() |
d263b19910 | ||
![]() |
1e477226ea | ||
![]() |
026fc1d2e3 | ||
![]() |
2d4bd9857a | ||
![]() |
8f48f5b45c | ||
![]() |
22210b7400 | ||
![]() |
7d05855ee0 | ||
![]() |
b2460cbc3d | ||
![]() |
4561957e56 | ||
![]() |
3367fadc3a | ||
![]() |
d7e409b042 | ||
![]() |
a0b28e8ad1 | ||
![]() |
f928a8e58e | ||
![]() |
0bc4b3d0fa | ||
![]() |
e352768388 | ||
![]() |
6835b73e49 | ||
![]() |
f1503f871b | ||
![]() |
c4d8aba5c8 | ||
![]() |
39f24c41ad | ||
![]() |
21644ec889 | ||
![]() |
613470b44d | ||
![]() |
6c918e346b | ||
![]() |
bce8539572 | ||
![]() |
aab86e00ec | ||
![]() |
2a58726caf | ||
![]() |
4163b35b32 | ||
![]() |
9c6dac8180 | ||
![]() |
80fc37724b | ||
![]() |
77b25f5132 | ||
![]() |
684f098450 | ||
![]() |
d09f74d30f | ||
![]() |
3d973b112e | ||
![]() |
96986164a4 | ||
![]() |
78152c20a9 | ||
![]() |
2bb64e9e2f | ||
![]() |
746844dfc8 | ||
![]() |
41b613a2d7 | ||
![]() |
3b3aeea224 | ||
![]() |
71c592a0ce | ||
![]() |
15193fcf5f | ||
![]() |
a31f53395f | ||
![]() |
283b134d84 | ||
![]() |
271eb614cd | ||
![]() |
16167bef07 | ||
![]() |
1eac9fa1cd | ||
![]() |
7f819f0020 | ||
![]() |
dec1f99a5f | ||
![]() |
c705e74fc8 | ||
![]() |
01df10f93e | ||
![]() |
9877f08cf4 | ||
![]() |
3dc4b1d775 | ||
![]() |
02791c51ae | ||
![]() |
49683326e6 | ||
![]() |
947773a82e | ||
![]() |
2a229df624 | ||
![]() |
e605ad5e46 | ||
![]() |
0d4f43472b | ||
![]() |
b30e467685 | ||
![]() |
a56c0b52d5 | ||
![]() |
c17ebfd279 | ||
![]() |
5400b1da96 | ||
![]() |
69f4a618b2 | ||
![]() |
16b8b6698c | ||
![]() |
b29a700d40 | ||
![]() |
bbb1468439 | ||
![]() |
72f9d6a8d3 | ||
![]() |
3ec8da1f17 | ||
![]() |
dbea3848df | ||
![]() |
33871435e1 | ||
![]() |
f1f22b43dc | ||
![]() |
2fb9a56e0b | ||
![]() |
14e8f66ed7 | ||
![]() |
e6f5072462 | ||
![]() |
a64f50fa72 | ||
![]() |
bb5f6e88d0 | ||
![]() |
6991403203 | ||
![]() |
410bd22f8a | ||
![]() |
b81d823602 | ||
![]() |
bd5115f9aa | ||
![]() |
7bcbed80d7 | ||
![]() |
8fb62ebf5f | ||
![]() |
209dd9923f | ||
![]() |
c75207e391 | ||
![]() |
d957f36927 | ||
![]() |
9ac459b6d9 | ||
![]() |
e08b2817ba | ||
![]() |
4ca13c409b | ||
![]() |
0d515e2303 | ||
![]() |
a2153bc6aa | ||
![]() |
ca171afe6f | ||
![]() |
bf4e97bd48 | ||
![]() |
8c59a12a03 | ||
![]() |
89569355be | ||
![]() |
3a41b3bdcf | ||
![]() |
12bd7037b3 | ||
![]() |
ca4f573be0 | ||
![]() |
07fceeab5a | ||
![]() |
3aa376e912 | ||
![]() |
92d30a8896 | ||
![]() |
83876fb9da | ||
![]() |
29bdf7877c | ||
![]() |
29199e2782 | ||
![]() |
68e1378615 | ||
![]() |
cf7efb5bfc | ||
![]() |
8634ee536d | ||
![]() |
632d3cda24 | ||
![]() |
29b6a907d4 | ||
![]() |
7474d09e5d | ||
![]() |
fc7bcd7e00 | ||
![]() |
f6fb2e4b1d | ||
![]() |
8c8673a272 | ||
![]() |
4404a1173b | ||
![]() |
e08c10315e | ||
![]() |
16473c9177 | ||
![]() |
235fd5603f | ||
![]() |
d07d5832f5 | ||
![]() |
ef8be5d559 | ||
![]() |
ccafdc6e1f | ||
![]() |
11827aa4c0 | ||
![]() |
6b0589d343 | ||
![]() |
cec1eed99e | ||
![]() |
d7e1e9e284 | ||
![]() |
cae46453a7 | ||
![]() |
a6e948c808 | ||
![]() |
7638020bfc | ||
![]() |
10a62ca17c | ||
![]() |
0afc7c184f | ||
![]() |
168e26aeb4 | ||
![]() |
e6b9389b33 | ||
![]() |
377c37425e | ||
![]() |
4af26602bb | ||
![]() |
c6624e5cb6 | ||
![]() |
f7ae5b91bf | ||
![]() |
07e68496c0 | ||
![]() |
d5a947e2cc | ||
![]() |
3f920767f1 | ||
![]() |
3e14d27a1e | ||
![]() |
cfa4c14108 | ||
![]() |
209056dbe1 | ||
![]() |
10356a7496 | ||
![]() |
d4ae74de44 | ||
![]() |
88d5e7dd5e | ||
![]() |
06c7b0b82e | ||
![]() |
689febda60 | ||
![]() |
80bc6fda8b | ||
![]() |
346eb78c4e | ||
![]() |
2df02f1b09 | ||
![]() |
92915eddc2 | ||
![]() |
cddbf460f8 | ||
![]() |
3c63c23e5a | ||
![]() |
ba67b1291f | ||
![]() |
7bced28327 | ||
![]() |
db2b60700c | ||
![]() |
9034822c44 | ||
![]() |
e8254f9aae | ||
![]() |
a14179b81a | ||
![]() |
427c5db7f4 | ||
![]() |
fcb5865468 | ||
![]() |
41370be2b8 | ||
![]() |
d7d8dd8986 | ||
![]() |
a0f596e419 | ||
![]() |
0a8894feb7 | ||
![]() |
1db9eea0f8 | ||
![]() |
489783c398 | ||
![]() |
be62f327ee | ||
![]() |
32359adb6d | ||
![]() |
2e198af8c3 | ||
![]() |
d154fcbd71 | ||
![]() |
21e277b8a2 | ||
![]() |
f98cdd0749 | ||
![]() |
e60e306426 | ||
![]() |
ec36d396d9 | ||
![]() |
135232d880 | ||
![]() |
9c42ca0315 | ||
![]() |
9ad9c569a6 | ||
![]() |
a9071d7920 | ||
![]() |
1b4a10fac1 | ||
![]() |
d340f3b383 | ||
![]() |
f8c5eeab5d | ||
![]() |
9cd2d0df93 | ||
![]() |
78914091b1 | ||
![]() |
e12c324613 | ||
![]() |
7cf396b518 | ||
![]() |
8b3b40e627 | ||
![]() |
90e14762e3 | ||
![]() |
d1dd8231cd | ||
![]() |
e70a3e09bf | ||
![]() |
98656b0044 | ||
![]() |
a48aa3c778 | ||
![]() |
05d7b98ba0 | ||
![]() |
f291ea6647 | ||
![]() |
5d6e332044 | ||
![]() |
acb471fbe5 | ||
![]() |
894f4379e6 | ||
![]() |
1c73007ae6 | ||
![]() |
2f7d744228 | ||
![]() |
e2cba90f8d | ||
![]() |
352214ba0a | ||
![]() |
bd9b72fb22 | ||
![]() |
50c9a667b3 | ||
![]() |
3d32e6310d | ||
![]() |
3bc54aa9e0 | ||
![]() |
def1ec3518 | ||
![]() |
077802f972 | ||
![]() |
914b47f340 | ||
![]() |
b2a78fd063 | ||
![]() |
7d1835e59c | ||
![]() |
833ccf3637 | ||
![]() |
51be916f39 | ||
![]() |
e375408777 | ||
![]() |
5078dc1cbf | ||
![]() |
875148366e | ||
![]() |
c9ec4b4e24 | ||
![]() |
efa2b2db27 | ||
![]() |
8ce120b74d | ||
![]() |
26e678a97d | ||
![]() |
e71dd7409e | ||
![]() |
58ffc2c6ca | ||
![]() |
d3f29362b9 | ||
![]() |
b429fe8254 | ||
![]() |
e1cb549b28 | ||
![]() |
65a22257cc | ||
![]() |
e2f753eaa7 | ||
![]() |
c7127b65bf | ||
![]() |
0c58c3572a | ||
![]() |
26ae5fd728 | ||
![]() |
370d92213b | ||
![]() |
6e8321a22a | ||
![]() |
a8e8c1ce5d | ||
![]() |
a8a8cafd2b | ||
![]() |
b609890f28 | ||
![]() |
aac09ae092 | ||
![]() |
f1ff872944 | ||
![]() |
b195d2980a | ||
![]() |
d11736181f | ||
![]() |
3e84486dd5 | ||
![]() |
a674ce36e4 | ||
![]() |
f6569a2625 | ||
![]() |
da10da79b3 | ||
![]() |
f236b76d5c | ||
![]() |
a71c22bedd | ||
![]() |
cc528e41cf | ||
![]() |
351962475f | ||
![]() |
6c73392a57 | ||
![]() |
072ad87831 | ||
![]() |
370a1f0574 | ||
![]() |
9ca7aca4b7 | ||
![]() |
0f2e9f66b1 | ||
![]() |
d03c3ab713 | ||
![]() |
57c0b34ae9 | ||
![]() |
06a94f0f28 | ||
![]() |
750e7b1262 | ||
![]() |
19e32752bb | ||
![]() |
89c0729964 | ||
![]() |
fd07152aea | ||
![]() |
b656f189b6 | ||
![]() |
9ac8d70152 | ||
![]() |
8cc0b46335 | ||
![]() |
1f15094da7 | ||
![]() |
b881adb853 | ||
![]() |
4bfc3a5629 | ||
![]() |
ae6c0bfe40 | ||
![]() |
4ce9c71521 | ||
![]() |
ec48323a7d | ||
![]() |
7d9bae16cd | ||
![]() |
163ff3d4e4 | ||
![]() |
43fbf97e10 | ||
![]() |
71faaf2ab1 | ||
![]() |
7b0e743eca | ||
![]() |
31a0c53855 | ||
![]() |
e8996063dd | ||
![]() |
00842a3354 | ||
![]() |
d33f18ecb7 | ||
![]() |
fb7f620316 | ||
![]() |
712e0d3e3b | ||
![]() |
1870dc29c0 | ||
![]() |
da6fdc74d8 | ||
![]() |
515e39154a | ||
![]() |
ff7731d063 | ||
![]() |
e9a3666dd5 | ||
![]() |
55c56d53f4 | ||
![]() |
e4d55e6842 | ||
![]() |
d8661cf2db | ||
![]() |
8815b126b5 | ||
![]() |
2cd367f29f | ||
![]() |
7395d19489 | ||
![]() |
d55cb95479 | ||
![]() |
68ece7d363 | ||
![]() |
6e4a8ac6df | ||
![]() |
790629849f | ||
![]() |
f0443a43b2 | ||
![]() |
3041eb5ce0 | ||
![]() |
c69247f190 | ||
![]() |
27d6a62e67 | ||
![]() |
6e7fc914aa | ||
![]() |
65d587843b | ||
![]() |
c54792af22 | ||
![]() |
7b7e023103 | ||
![]() |
7637d36146 | ||
![]() |
6c62afb123 | ||
![]() |
44210ce6f2 | ||
![]() |
e21efc0a5c | ||
![]() |
09a965022f | ||
![]() |
7534ecd2f2 | ||
![]() |
46bf5cf830 | ||
![]() |
7ba7761a57 | ||
![]() |
5268afabdb | ||
![]() |
3ea7506003 | ||
![]() |
ee14d206c8 | ||
![]() |
b65f4b9af6 | ||
![]() |
6d000a3f9a | ||
![]() |
9ba0de67f5 | ||
![]() |
d22eaa1318 | ||
![]() |
3f4bfab7fe | ||
![]() |
9292f217c5 | ||
![]() |
3b779bf423 | ||
![]() |
ea410d3af1 | ||
![]() |
4e71c2c500 | ||
![]() |
454ddf366a | ||
![]() |
d0ba5696d1 | ||
![]() |
c53fd0d1e1 | ||
![]() |
7bbecfde2b | ||
![]() |
a06f378582 | ||
![]() |
b3b42b741d | ||
![]() |
020f115d7c | ||
![]() |
2cc9d70915 | ||
![]() |
b242c6651a | ||
![]() |
14a51799a6 | ||
![]() |
79a6dacd2f | ||
![]() |
6891f1df1c | ||
![]() |
497494620d | ||
![]() |
7a13242077 | ||
![]() |
b9d6973a79 | ||
![]() |
ed0e8c5e8d | ||
![]() |
d8f530f8ac | ||
![]() |
a46874b7ff | ||
![]() |
cf68f25a03 | ||
![]() |
a496563b5c | ||
![]() |
a763ad5bf1 | ||
![]() |
f0b0200932 | ||
![]() |
342f22e6a1 | ||
![]() |
372ecc6557 | ||
![]() |
16c604937e | ||
![]() |
5cbffb23fd | ||
![]() |
6de38d3b85 | ||
![]() |
b34ce577d9 | ||
![]() |
4b9fcd7de7 | ||
![]() |
cc71ccaafa | ||
![]() |
9ff2eece3a | ||
![]() |
2bc97cc9c8 | ||
![]() |
a87570cf5a | ||
![]() |
9ffd25e3a0 | ||
![]() |
61fdab294a | ||
![]() |
d4e137bb58 | ||
![]() |
c251e4f241 | ||
![]() |
a55d0f347b | ||
![]() |
f15cc0b424 | ||
![]() |
4e17875011 | ||
![]() |
256b64b6b3 | ||
![]() |
8c0c0592e2 | ||
![]() |
f53f81dbc4 | ||
![]() |
d6c85719c9 | ||
![]() |
c51c80bf47 | ||
![]() |
544832756d | ||
![]() |
ca8586789a | ||
![]() |
1afc2b3518 | ||
![]() |
17352ea5bd | ||
![]() |
6f5e3c2711 | ||
![]() |
bee21cd3fe | ||
![]() |
c4340e05d2 | ||
![]() |
6d6eef4e97 | ||
![]() |
71137032df | ||
![]() |
ffff4dc03d | ||
![]() |
4033131f2e | ||
![]() |
65b16c763e | ||
![]() |
33af3de4a3 | ||
![]() |
a822c1eb2f | ||
![]() |
4eb46bc275 | ||
![]() |
ccc9b73f9b | ||
![]() |
e9ffdeff19 | ||
![]() |
a0ab4dffc9 | ||
![]() |
f5f8ad0e02 | ||
![]() |
82957ff6ef | ||
![]() |
0864aeb9c6 | ||
![]() |
cda6310373 | ||
![]() |
26a87e9280 | ||
![]() |
0b16a4880a | ||
![]() |
db68c5852c | ||
![]() |
256aec5308 | ||
![]() |
20dd3ca21c | ||
![]() |
25cc76e022 | ||
![]() |
168cc607aa | ||
![]() |
edc4601f8e | ||
![]() |
8f86a7ad8c | ||
![]() |
4d7ad0dc51 | ||
![]() |
34ac80567e | ||
![]() |
23bdc03f29 | ||
![]() |
3099748a6d | ||
![]() |
e384f76ac1 | ||
![]() |
7050d19be7 | ||
![]() |
ca678330d3 | ||
![]() |
10a5b3f9c3 | ||
![]() |
0fcedc5046 | ||
![]() |
f558f7fb8c | ||
![]() |
06207defe7 | ||
![]() |
986f9d7633 | ||
![]() |
81277fd12e | ||
![]() |
30442b25c0 | ||
![]() |
67ac3b4ba3 | ||
![]() |
f819e2cf8d | ||
![]() |
a376f4525b | ||
![]() |
5c1553286a | ||
![]() |
2c5d3f7492 | ||
![]() |
58f01ba11a | ||
![]() |
3fdf9a2e28 | ||
![]() |
4cbd8e7673 | ||
![]() |
20ca642e51 | ||
![]() |
05ad7ea011 | ||
![]() |
faea8c9f4c | ||
![]() |
0d4c51f26e | ||
![]() |
2c1b25b00b | ||
![]() |
407f305d21 | ||
![]() |
5d5d6b247f | ||
![]() |
77bd7c37c1 | ||
![]() |
783ea0f5c8 | ||
![]() |
33f1b45f30 | ||
![]() |
e1df50ad3b | ||
![]() |
6a9a4cf65f | ||
![]() |
443634ecf0 | ||
![]() |
2a17870d6d | ||
![]() |
486ed7dcaa | ||
![]() |
19c13096dc | ||
![]() |
49b4271a47 | ||
![]() |
404d0b8d05 | ||
![]() |
fe63c12cd9 | ||
![]() |
44023c3db7 | ||
![]() |
6242997849 | ||
![]() |
ec5d7508c9 | ||
![]() |
7461d8f806 | ||
![]() |
362236d7df | ||
![]() |
ba384b9eee | ||
![]() |
d94df728e5 | ||
![]() |
d0a53d1760 | ||
![]() |
2e5ec1f0c1 | ||
![]() |
6c8aedfb8b | ||
![]() |
f354e1eb0f | ||
![]() |
49b1e5897e | ||
![]() |
1a58c17180 | ||
![]() |
7fb852893c | ||
![]() |
aa9a354746 | ||
![]() |
78f5429c92 | ||
![]() |
79de8e0f32 | ||
![]() |
70f59eeec6 | ||
![]() |
b75792a3bf | ||
![]() |
5cb7117656 | ||
![]() |
3a5bd7474b | ||
![]() |
304fad3f49 | ||
![]() |
fb929a089c | ||
![]() |
6076a0cdc4 | ||
![]() |
a3736683eb | ||
![]() |
413f1c31bb | ||
![]() |
20baff380b | ||
![]() |
582d159884 | ||
![]() |
9c43c5806d | ||
![]() |
8ad2bf5401 | ||
![]() |
66409f0fa5 | ||
![]() |
68172e006f | ||
![]() |
cf5e808a96 | ||
![]() |
3faebaeb4b | ||
![]() |
6b8cfe661c | ||
![]() |
2fd64af737 | ||
![]() |
050cdf3783 | ||
![]() |
d73b3d77ea | ||
![]() |
acc024bcf9 | ||
![]() |
deb179ad38 | ||
![]() |
bfb11790a2 | ||
![]() |
af23110074 | ||
![]() |
b8e71609db | ||
![]() |
1876b3827f | ||
![]() |
38d3b8d087 | ||
![]() |
c5ef33cc78 | ||
![]() |
7427b209a7 | ||
![]() |
71397e5199 | ||
![]() |
1bc3b3befc | ||
![]() |
872e46a076 | ||
![]() |
ad386c0e22 | ||
![]() |
7e281f66c2 | ||
![]() |
7daafcbe1b | ||
![]() |
c5b223988a | ||
![]() |
6cc9ce573b | ||
![]() |
23192226dd | ||
![]() |
736444201b | ||
![]() |
785f49b005 | ||
![]() |
55dd1f4aa1 | ||
![]() |
6d0490d7d9 | ||
![]() |
06667455ae | ||
![]() |
3640960486 | ||
![]() |
4ad3dbf3e2 | ||
![]() |
10957deb1f | ||
![]() |
0a128db269 | ||
![]() |
6bb3b84377 | ||
![]() |
b8d2c551e0 | ||
![]() |
34e06351fb | ||
![]() |
e179404a9e | ||
![]() |
9c574995ac | ||
![]() |
3f35c603d2 | ||
![]() |
8e0688140e | ||
![]() |
5f81a204f2 | ||
![]() |
df3b70a533 | ||
![]() |
0c57c05a22 | ||
![]() |
c6be3be45a | ||
![]() |
9689db9605 | ||
![]() |
e4607735ff | ||
![]() |
389b7def0b | ||
![]() |
e35bd30ed3 | ||
![]() |
4b8f7e1fe9 | ||
![]() |
5cc4e2bb16 | ||
![]() |
e165a96689 | ||
![]() |
91a655a81e | ||
![]() |
612811e2c2 | ||
![]() |
11bd72915c | ||
![]() |
339221e793 | ||
![]() |
9be864b45e | ||
![]() |
3a453f5843 | ||
![]() |
28e0384b55 | ||
![]() |
dcd6c6f06f | ||
![]() |
28d26065e4 | ||
![]() |
300c8d06c4 | ||
![]() |
d2a1d11d16 | ||
![]() |
6ae717bbfe | ||
![]() |
21296b4224 | ||
![]() |
fafad302ba | ||
![]() |
0c94ad46b2 | ||
![]() |
d9bb40f934 | ||
![]() |
bbc16b6bc8 | ||
![]() |
16154e9d8b | ||
![]() |
61bd536d7b | ||
![]() |
02c798a8bc | ||
![]() |
c37a691b9b | ||
![]() |
23c68d17e8 | ||
![]() |
0a7f610ad3 | ||
![]() |
c9e8bd2e5d | ||
![]() |
a66d2ca1b9 | ||
![]() |
d38a0f0366 | ||
![]() |
29759de021 | ||
![]() |
264759ddf0 | ||
![]() |
91b0bd5b5e | ||
![]() |
a0e2cc7a3a | ||
![]() |
9e1eb41cbe | ||
![]() |
c37eb023b0 | ||
![]() |
840948ba4a | ||
![]() |
8fbdd88b24 | ||
![]() |
512d35d2e0 | ||
![]() |
0321e55a42 | ||
![]() |
a1ee9ad48b | ||
![]() |
1ad1fd28f1 | ||
![]() |
007f8b50b9 | ||
![]() |
6fe8a87cca | ||
![]() |
5f46679d94 | ||
![]() |
18a3f212f3 | ||
![]() |
67a3f5d87b | ||
![]() |
c88439ba2f | ||
![]() |
67e17d4016 | ||
![]() |
b61cf60faf | ||
![]() |
5503853445 | ||
![]() |
259726f5be | ||
![]() |
bec42d941b | ||
![]() |
dcbbaf08f9 | ||
![]() |
349355584a | ||
![]() |
7ce0b34774 | ||
![]() |
dd894758a4 | ||
![]() |
2aa1eb97fd | ||
![]() |
4c43ae7b2f | ||
![]() |
34e516e0be | ||
![]() |
7bd3427e76 | ||
![]() |
6853db693a | ||
![]() |
252ce1e467 | ||
![]() |
221c12bd61 | ||
![]() |
12edd68874 | ||
![]() |
f469753fb1 | ||
![]() |
c894ecd0e6 | ||
![]() |
2153bc536c | ||
![]() |
86bbac430c | ||
![]() |
16ad8a3c01 | ||
![]() |
b2af91c83e | ||
![]() |
4c0810f530 | ||
![]() |
f70130e21f | ||
![]() |
6bb7b01d00 | ||
![]() |
51e7aaa805 | ||
![]() |
54704e53b3 | ||
![]() |
cc46797576 | ||
![]() |
7f1fb6f75f | ||
![]() |
2c2a1d204b | ||
![]() |
581fafdcc9 | ||
![]() |
10358abbec | ||
![]() |
de1ffe67b1 | ||
![]() |
eb2b24d57c | ||
![]() |
825db8a56a | ||
![]() |
577a21fc5c | ||
![]() |
1b2841eef9 | ||
![]() |
845511e322 | ||
![]() |
96ab057853 | ||
![]() |
f85cf0a238 | ||
![]() |
84a2676a9c | ||
![]() |
466a1af902 | ||
![]() |
ebbe7e805f | ||
![]() |
c861ee025e | ||
![]() |
6d0823328d | ||
![]() |
60be14dc77 | ||
![]() |
2d627819d9 | ||
![]() |
cf575f83f5 | ||
![]() |
79935b2d4c | ||
![]() |
d1cceb2013 | ||
![]() |
f5da130d51 | ||
![]() |
8768304ec5 | ||
![]() |
f10a5dcdbe | ||
![]() |
a27428ebcd | ||
![]() |
d19acf17c2 | ||
![]() |
1a0bf861ee | ||
![]() |
db906ad4d0 | ||
![]() |
75ed0f2f99 | ||
![]() |
29ed1144d5 | ||
![]() |
fa445d4066 | ||
![]() |
d10be4ef2d | ||
![]() |
7f66d5b8e9 | ||
![]() |
0c8cd680c2 | ||
![]() |
a7ba1977b4 | ||
![]() |
a960b39235 | ||
![]() |
3febf059ec | ||
![]() |
20203f7bdb | ||
![]() |
a399d76d06 | ||
![]() |
1ca097c5a0 | ||
![]() |
e83ede245d | ||
![]() |
05ac275780 | ||
![]() |
966c0bc06c | ||
![]() |
abf136dd63 | ||
![]() |
b6309cfd16 | ||
![]() |
b69d5e0fa3 | ||
![]() |
5084cde6b9 | ||
![]() |
ca1cc7ed0d | ||
![]() |
808a31db2b | ||
![]() |
44ad75aead | ||
![]() |
0961c9d05e | ||
![]() |
56754b4d43 | ||
![]() |
661779ad4e | ||
![]() |
cf37ebb652 | ||
![]() |
82741e490b | ||
![]() |
5fed28808e | ||
![]() |
321c0cfc84 | ||
![]() |
eba7cedaa6 | ||
![]() |
71492d0467 | ||
![]() |
9630a58ea7 | ||
![]() |
89f6f16ba2 | ||
![]() |
2d646da97f | ||
![]() |
39b5460598 | ||
![]() |
636429ccfa | ||
![]() |
e5abb95f5c | ||
![]() |
c631554eb0 | ||
![]() |
db07eeb916 | ||
![]() |
3216a46e76 | ||
![]() |
ae6243b7bf | ||
![]() |
df002d7a67 | ||
![]() |
e8a0632108 | ||
![]() |
0a92c28bac | ||
![]() |
d419547463 | ||
![]() |
da392912b3 | ||
![]() |
9ebee02727 | ||
![]() |
0bdcfcc42f | ||
![]() |
43623a30bc | ||
![]() |
99e73054a9 | ||
![]() |
108233f3b8 | ||
![]() |
8f2a7c95b3 | ||
![]() |
7fdd525dac | ||
![]() |
7ed24137eb | ||
![]() |
07cd30eaca | ||
![]() |
df8cf66e02 | ||
![]() |
f067bcd877 | ||
![]() |
e11ef55dd8 | ||
![]() |
fa8bd30e83 | ||
![]() |
04a2ff7506 | ||
![]() |
bc68e20041 | ||
![]() |
786da25b9f | ||
![]() |
c3832d56c3 | ||
![]() |
79d1a2f458 | ||
![]() |
f16a674a39 | ||
![]() |
6e38a80efc | ||
![]() |
6a3a1297ad | ||
![]() |
5ca63a8052 | ||
![]() |
8b04df093c | ||
![]() |
d2a5494335 | ||
![]() |
de545e90e2 | ||
![]() |
57ab7e829b | ||
![]() |
6847830575 | ||
![]() |
2084ecc4c6 | ||
![]() |
b0c27e587e | ||
![]() |
1687d90d02 | ||
![]() |
dfd9bf3c64 | ||
![]() |
5a25b9c2f1 | ||
![]() |
bf68101754 | ||
![]() |
487bd8d3fc | ||
![]() |
3ba9c931b9 | ||
![]() |
8484f7595a | ||
![]() |
ee889d59d4 | ||
![]() |
ae10330844 | ||
![]() |
f3e88f6f2e | ||
![]() |
2abfd0392d | ||
![]() |
462c1f94d6 | ||
![]() |
f4710891d0 | ||
![]() |
1fdb6b8034 | ||
![]() |
8029c3d672 | ||
![]() |
61fdee14c6 | ||
![]() |
117e4e468f | ||
![]() |
869ec113f4 | ||
![]() |
00e7b93011 | ||
![]() |
03e581870a | ||
![]() |
b04fe141ac | ||
![]() |
b0168fbb85 | ||
![]() |
75ba343b5e | ||
![]() |
88217473f7 | ||
![]() |
01e5dfc9b3 | ||
![]() |
58869677bd | ||
![]() |
032b01aec1 | ||
![]() |
a47c3fa854 | ||
![]() |
7c6ba1a782 | ||
![]() |
0d08f5413b | ||
![]() |
949cc17a9e | ||
![]() |
607c1a1ef0 | ||
![]() |
db2dab8227 | ||
![]() |
eb4ba4fc78 | ||
![]() |
bf888b1547 | ||
![]() |
c468aab9b2 | ||
![]() |
a0dae802f2 | ||
![]() |
14330fbd93 | ||
![]() |
bf55be7f7f | ||
![]() |
e10987a705 | ||
![]() |
355f40d740 | ||
![]() |
2503fabe1d | ||
![]() |
32c7c0b4f0 | ||
![]() |
301a964a65 | ||
![]() |
49f2fd2af7 | ||
![]() |
4f518cac2c | ||
![]() |
8420f73919 | ||
![]() |
1ef2d3c7f2 | ||
![]() |
fc20fd32f1 | ||
![]() |
13a8bf6993 | ||
![]() |
b2f424a6f8 | ||
![]() |
a9d927551c | ||
![]() |
fdf7b516a0 | ||
![]() |
9a00078169 | ||
![]() |
1dfb632fc4 | ||
![]() |
1c1f9a6a89 | ||
![]() |
2b76b3887e | ||
![]() |
a49c84032b | ||
![]() |
c72bb5b22b | ||
![]() |
24a844dddc | ||
![]() |
ae903973a7 | ||
![]() |
82442bb5ec | ||
![]() |
8786302190 | ||
![]() |
d27a17cf8e | ||
![]() |
b4b90ca59d | ||
![]() |
ecd967a68d | ||
![]() |
8812340768 | ||
![]() |
659da7bf80 | ||
![]() |
91eabf3c38 | ||
![]() |
e355c81e41 | ||
![]() |
d45a674652 | ||
![]() |
f91b46e88c | ||
![]() |
f57754212c | ||
![]() |
97c454aa0d | ||
![]() |
b516f10a35 | ||
![]() |
0ccc788148 | ||
![]() |
5c941e0afb | ||
![]() |
492e4d2df4 | ||
![]() |
66f33ad497 | ||
![]() |
ff81536463 | ||
![]() |
ba0cba1a2b | ||
![]() |
fc7771ec13 | ||
![]() |
4c3069e5b7 | ||
![]() |
ed54a185e4 | ||
![]() |
4f81085d0f | ||
![]() |
8383caf6a6 | ||
![]() |
1b9f224569 | ||
![]() |
05495af74f | ||
![]() |
d62da5e924 | ||
![]() |
57d72bd80f | ||
![]() |
681c05c323 | ||
![]() |
3d20d8b208 | ||
![]() |
4f9d77b906 | ||
![]() |
82f80db558 | ||
![]() |
b17490f0de | ||
![]() |
d33c0b4bd1 | ||
![]() |
7f528c90c3 | ||
![]() |
c4c4782429 | ||
![]() |
5414092309 | ||
![]() |
b632ea6f86 | ||
![]() |
bd98ce8c25 | ||
![]() |
0c1714ef78 | ||
![]() |
ee278f111f | ||
![]() |
47e9eb5a0d | ||
![]() |
baf5dcbd03 | ||
![]() |
2e2915ec09 | ||
![]() |
5f3a6740c1 | ||
![]() |
b17074d68b | ||
![]() |
ac4b2ea70f | ||
![]() |
4e2a9e3d7b | ||
![]() |
75525b9866 | ||
![]() |
1e39f22009 | ||
![]() |
7ba55ab666 | ||
![]() |
0c06517dcc | ||
![]() |
cd1bfe1b20 | ||
![]() |
35c2d8966b | ||
![]() |
cb255ef137 | ||
![]() |
f9d78a95ad | ||
![]() |
14eb496b1d | ||
![]() |
b390e7bed1 | ||
![]() |
903f92f94a | ||
![]() |
ebb20abee0 | ||
![]() |
2253275640 | ||
![]() |
7ddfe3c80b | ||
![]() |
4388d82076 | ||
![]() |
4f70ec7dc2 | ||
![]() |
a71f42366a | ||
![]() |
e9bb9fdafe | ||
![]() |
dd49ea6f20 | ||
![]() |
92de202e0b | ||
![]() |
1d02b69f52 | ||
![]() |
1dcd913c04 | ||
![]() |
471a5f8407 | ||
![]() |
7b97ac2f6f | ||
![]() |
e673d90b3f | ||
![]() |
764234f744 | ||
![]() |
6ca944b7ef | ||
![]() |
c574556deb | ||
![]() |
c04e1adea2 | ||
![]() |
5eb11349fc | ||
![]() |
b3beb7ef85 | ||
![]() |
ec95000fbc | ||
![]() |
53f0c01073 | ||
![]() |
42d5349db7 | ||
![]() |
f36a1bbf4a | ||
![]() |
e9945abf2f | ||
![]() |
71ba192c38 | ||
![]() |
e8a7671c25 | ||
![]() |
17138f78b8 | ||
![]() |
46c6f86d6a | ||
![]() |
ae1c519d3a | ||
![]() |
fd9299cb3d | ||
![]() |
a9be870fd3 | ||
![]() |
34f61fd6dc | ||
![]() |
e568e2ae21 | ||
![]() |
0656343a91 | ||
![]() |
fa2773af9b | ||
![]() |
a438439ce0 | ||
![]() |
da73c9d83d | ||
![]() |
a8f9f7ac7a | ||
![]() |
006d989943 | ||
![]() |
82d6909957 | ||
![]() |
12a7fc9337 | ||
![]() |
0241334656 | ||
![]() |
b217291b04 | ||
![]() |
aa211df0ad | ||
![]() |
8eebf51447 | ||
![]() |
f5d834d130 | ||
![]() |
0f2eae4091 | ||
![]() |
81d2334f48 | ||
![]() |
45384205eb | ||
![]() |
4150ac045d | ||
![]() |
793a704871 | ||
![]() |
9e2606f1c8 | ||
![]() |
4c4549eb37 | ||
![]() |
96f7a4231b | ||
![]() |
c4cf248a1e | ||
![]() |
50717b4d19 | ||
![]() |
da80a3896d | ||
![]() |
cc374e0478 | ||
![]() |
26d27f8be9 | ||
![]() |
4767fcbdfc | ||
![]() |
e1b48dd2a2 | ||
![]() |
087492fc0b | ||
![]() |
525703d376 | ||
![]() |
236bc6aefa | ||
![]() |
ec4c29c52c | ||
![]() |
d5ed1c4c41 | ||
![]() |
dfe808cfb4 | ||
![]() |
da229d9b65 | ||
![]() |
e54d904e4c | ||
![]() |
2e17c96866 | ||
![]() |
ddeb16463d | ||
![]() |
465efa460e | ||
![]() |
c351402556 | ||
![]() |
5f765e8b96 | ||
![]() |
6f556f69d6 | ||
![]() |
2439827eff | ||
![]() |
5f467f82e0 | ||
![]() |
6692fa439a | ||
![]() |
0535247bb3 | ||
![]() |
a0a4fcaf5f | ||
![]() |
454d81facc | ||
![]() |
0bfa8260fa | ||
![]() |
f2124f1c95 | ||
![]() |
451bc2370a | ||
![]() |
0b17642c31 | ||
![]() |
214dc25576 | ||
![]() |
158eddfd44 | ||
![]() |
5daa6dbd25 | ||
![]() |
645ef3e61f | ||
![]() |
3be4b9d79b | ||
![]() |
5dcea51712 | ||
![]() |
6995968d50 | ||
![]() |
c4cb42f3c2 | ||
![]() |
0d4a7a2b3e | ||
![]() |
20cc9c9b42 | ||
![]() |
8a6bd04543 | ||
![]() |
263138a388 | ||
![]() |
e645342131 | ||
![]() |
6e4c707f9e | ||
![]() |
fca286d6c0 | ||
![]() |
5a2e08647f | ||
![]() |
f6dac98abd | ||
![]() |
ddb525f6cd | ||
![]() |
f7ee712456 | ||
![]() |
ff873e2f71 | ||
![]() |
54b57e6222 | ||
![]() |
375abfb95e | ||
![]() |
30a38fa6d1 | ||
![]() |
554c0b692d | ||
![]() |
61ac831882 | ||
![]() |
59e89a0daf | ||
![]() |
1e3950cd1d | ||
![]() |
f514ea453c | ||
![]() |
cc046478e5 | ||
![]() |
a17c1052cd | ||
![]() |
2408f9b8fa | ||
![]() |
6aae1b3378 | ||
![]() |
ed51223226 | ||
![]() |
013808b7f5 | ||
![]() |
af584e1d12 | ||
![]() |
3763d7a1d0 | ||
![]() |
ce92add096 | ||
![]() |
40c94b6596 | ||
![]() |
c894bc2e40 | ||
![]() |
415a4fa1af | ||
![]() |
b9367a33a8 | ||
![]() |
7170f06c08 | ||
![]() |
f2578a58b4 | ||
![]() |
1950656bd5 | ||
![]() |
eed3263c70 | ||
![]() |
02e01626f5 | ||
![]() |
41a2d9604e | ||
![]() |
7d6f188bfc | ||
![]() |
15a144f17a | ||
![]() |
c54f2b66da | ||
![]() |
685a0807d8 | ||
![]() |
0d404e0e37 | ||
![]() |
39bb859f57 | ||
![]() |
90e32b7e45 | ||
![]() |
63a2d9dd18 | ||
![]() |
4982693883 | ||
![]() |
f4211e3fa3 | ||
![]() |
eacf58b5a5 | ||
![]() |
f9349bc731 | ||
![]() |
3840671764 | ||
![]() |
fd62cf02d6 | ||
![]() |
254744cd7d | ||
![]() |
595d04c922 | ||
![]() |
a3969fe2c8 | ||
![]() |
220e4134b7 | ||
![]() |
56e176a6f1 | ||
![]() |
780c15d6b3 | ||
![]() |
55ff848b78 | ||
![]() |
dbe829bc7d | ||
![]() |
8d0508f320 | ||
![]() |
2741bb8b38 | ||
![]() |
16cadd53cf | ||
![]() |
a8d21c6112 | ||
![]() |
6b2e707653 | ||
![]() |
205b7451fa | ||
![]() |
1d3aeec0de | ||
![]() |
ac911dcd31 | ||
![]() |
89a94b3efc | ||
![]() |
71793dcfa5 | ||
![]() |
1e527a8350 | ||
![]() |
262b12eb93 | ||
![]() |
7fb1e699ae | ||
![]() |
9ee647329b | ||
![]() |
cd6dcec644 | ||
![]() |
d8f248c60e | ||
![]() |
2925b930ad | ||
![]() |
127aaba47b | ||
![]() |
8bc8761af6 | ||
![]() |
a88321d243 | ||
![]() |
4e19232960 | ||
![]() |
5b95bdb6b7 | ||
![]() |
0fc59ccb16 | ||
![]() |
a9daf9835a | ||
![]() |
2110d9c3b9 | ||
![]() |
b77e0b8125 | ||
![]() |
5197f102ea | ||
![]() |
447d4604c6 | ||
![]() |
5a84e34f93 | ||
![]() |
fb6d3cccdc | ||
![]() |
fad3cb185b | ||
![]() |
f61ce395f5 | ||
![]() |
17f3299152 | ||
![]() |
17e04589d0 | ||
![]() |
4b2edde81b | ||
![]() |
5d3d766f56 | ||
![]() |
94e2a0dea0 | ||
![]() |
21fe68add0 | ||
![]() |
af6ebea4a3 | ||
![]() |
3c17ee03b6 | ||
![]() |
0c3c007faf | ||
![]() |
330eb0957b | ||
![]() |
02923475e6 | ||
![]() |
01ff97b366 | ||
![]() |
f0a4a99654 | ||
![]() |
02d2368654 | ||
![]() |
e19d07e434 | ||
![]() |
669ed5cb28 | ||
![]() |
785ae4a83d | ||
![]() |
d327045802 | ||
![]() |
785ef19cce | ||
![]() |
f5653d0da5 | ||
![]() |
e0a6d2efe5 | ||
![]() |
9971e2e934 | ||
![]() |
558802c7dd | ||
![]() |
91edcf9b52 | ||
![]() |
f401aa2897 | ||
![]() |
1d0389327f | ||
![]() |
06cd7556f3 | ||
![]() |
3b1f9a5dab | ||
![]() |
f54cd18da4 | ||
![]() |
73e0fd614e | ||
![]() |
9b220cc6ce | ||
![]() |
c7a5f63e33 | ||
![]() |
11192e6065 | ||
![]() |
d83b308100 | ||
![]() |
f67bf6908f | ||
![]() |
2784edc689 | ||
![]() |
8f41e99464 | ||
![]() |
7c2b37e8ca | ||
![]() |
dbdbad2deb | ||
![]() |
e5db86363c | ||
![]() |
b2026c1cd7 | ||
![]() |
b12f29afad | ||
![]() |
04b23388b5 | ||
![]() |
7309a937e8 | ||
![]() |
5dbcd1f726 | ||
![]() |
3338459139 | ||
![]() |
35f17fc1d4 | ||
![]() |
e062940639 | ||
![]() |
ff4d5265c5 | ||
![]() |
906f417436 | ||
![]() |
1c75fe3bb8 | ||
![]() |
283e858576 | ||
![]() |
2b4ab6320b | ||
![]() |
2085260ce7 | ||
![]() |
1f143176ad | ||
![]() |
dd2163a837 | ||
![]() |
0e1eca8a3e | ||
![]() |
9da32880ec | ||
![]() |
15aee6a66a | ||
![]() |
aba74f074a | ||
![]() |
75860508de | ||
![]() |
52160a367a | ||
![]() |
5651a61604 | ||
![]() |
959d8c3181 | ||
![]() |
64ee7456dc | ||
![]() |
814fcf63a8 | ||
![]() |
b72d8cf7d7 | ||
![]() |
8e7ef58715 | ||
![]() |
56bfa01c56 | ||
![]() |
4a0fc3e087 | ||
![]() |
f3c371996f | ||
![]() |
e5467181cb | ||
![]() |
0b3d2ea4ad | ||
![]() |
9648aa3588 | ||
![]() |
1b92cbbf74 | ||
![]() |
9979c046b3 | ||
![]() |
9ad121f9e6 | ||
![]() |
a0900afba3 | ||
![]() |
1cb614c8a8 | ||
![]() |
e63723f39e | ||
![]() |
720bd03173 | ||
![]() |
9e07cf67a5 | ||
![]() |
503dec7345 | ||
![]() |
5a2649a65b | ||
![]() |
1599dc9e16 | ||
![]() |
84dc8188c4 | ||
![]() |
74657ae815 | ||
![]() |
1a3b747d17 | ||
![]() |
802db71400 | ||
![]() |
4f98524258 | ||
![]() |
b17ea09b8b | ||
![]() |
8abbc71e91 | ||
![]() |
1db31fb0f7 | ||
![]() |
e9b5725d7b | ||
![]() |
d3105b6846 | ||
![]() |
196540afc7 | ||
![]() |
2b8b9f8311 | ||
![]() |
b4f0fce600 | ||
![]() |
c6f101a487 | ||
![]() |
54739c7ccd | ||
![]() |
aa2e632df3 | ||
![]() |
f3445d99cf | ||
![]() |
7e48b21767 | ||
![]() |
1d1688093a | ||
![]() |
d392695ab7 | ||
![]() |
5066560411 | ||
![]() |
7fa6686e8c | ||
![]() |
d74fe6ed52 | ||
![]() |
319a3b4943 | ||
![]() |
226e6e9f59 | ||
![]() |
42f311a457 | ||
![]() |
e50ec2e2e2 | ||
![]() |
b72a3361c0 | ||
![]() |
7b057eaa77 | ||
![]() |
d7aaed05b7 | ||
![]() |
c5fe5565bb | ||
![]() |
a1a1763897 | ||
![]() |
724357683c | ||
![]() |
0d6de9fe73 | ||
![]() |
5646045e9e | ||
![]() |
17c7a3bbac | ||
![]() |
8d65eb1fdf | ||
![]() |
2298a55b16 | ||
![]() |
33d65bcefc | ||
![]() |
3cc7deda04 | ||
![]() |
e2de660bec | ||
![]() |
6b1e5a525f | ||
![]() |
93565f0ed9 | ||
![]() |
143d1162b6 | ||
![]() |
788d616fa2 | ||
![]() |
0de9471a5d | ||
![]() |
b229071248 | ||
![]() |
6d145730a5 | ||
![]() |
f02bb67485 | ||
![]() |
52ded635ff | ||
![]() |
a6d73828b8 | ||
![]() |
1d052fa5bb | ||
![]() |
38d758b52f | ||
![]() |
9162e9c318 | ||
![]() |
189ea00768 | ||
![]() |
25d6427aed | ||
![]() |
8a61442cf2 | ||
![]() |
106d405699 | ||
![]() |
1f23e9062f | ||
![]() |
231b498ea5 | ||
![]() |
a256e5abfa | ||
![]() |
028b370ead | ||
![]() |
18abc6adf7 | ||
![]() |
95aa29d6ca | ||
![]() |
5d2242dd16 | ||
![]() |
de8bca6967 | ||
![]() |
12234de20e | ||
![]() |
b41369a2ad | ||
![]() |
6e35c79c14 | ||
![]() |
22e4c0512e | ||
![]() |
3606b8077f | ||
![]() |
3a90a65ba8 | ||
![]() |
e59987a8ed | ||
![]() |
22d8ce0fd9 | ||
![]() |
01eae3876b | ||
![]() |
2e43f390a4 | ||
![]() |
65421fa551 | ||
![]() |
fc88922ce3 | ||
![]() |
52609dded9 | ||
![]() |
6d54496187 | ||
![]() |
2a6c38066d | ||
![]() |
924c7804c9 | ||
![]() |
23f34fa7ae | ||
![]() |
7046cba1f7 | ||
![]() |
4be1040a14 | ||
![]() |
68baeb83cb | ||
![]() |
aa94e45582 | ||
![]() |
2c58a9f802 | ||
![]() |
0a41a4f066 | ||
![]() |
e265d9581c | ||
![]() |
4675579f79 | ||
![]() |
52ae01ea74 | ||
![]() |
099430238c | ||
![]() |
af3626b215 | ||
![]() |
52ea3a5ce8 | ||
![]() |
2ab2ade642 | ||
![]() |
1cc3936ec3 | ||
![]() |
322eef1c0f | ||
![]() |
0964130782 | ||
![]() |
be9ec50e3a | ||
![]() |
da1dd45169 | ||
![]() |
9a7f7f119d | ||
![]() |
b1a414c840 | ||
![]() |
2718ada9f9 | ||
![]() |
46a596ce34 | ||
![]() |
7036cefa72 | ||
![]() |
fb7fbf2dac | ||
![]() |
49b0c8d549 | ||
![]() |
8f9a6bd544 | ||
![]() |
24e4b0b772 | ||
![]() |
1c86bd2f8b | ||
![]() |
363f548f13 | ||
![]() |
51ce481e77 | ||
![]() |
30e5611812 | ||
![]() |
67706a312d | ||
![]() |
f4eb3380b4 | ||
![]() |
73934afc7d | ||
![]() |
9d2a0c0502 | ||
![]() |
9ec75531a8 | ||
![]() |
91bdb8f742 | ||
![]() |
d8ae3439de | ||
![]() |
2d018fff6c | ||
![]() |
7d37dc6cde | ||
![]() |
c60033027d | ||
![]() |
3f7c29a6f6 | ||
![]() |
b2243f480c | ||
![]() |
f5384e8bc8 | ||
![]() |
ecc6fcf862 | ||
![]() |
46cc2aec94 | ||
![]() |
c62a5a6dcd | ||
![]() |
f6b10232ec | ||
![]() |
87559c0938 | ||
![]() |
7903541689 | ||
![]() |
c93e1b0123 | ||
![]() |
e261fafdb3 | ||
![]() |
485e2fde25 | ||
![]() |
6feaf64c90 | ||
![]() |
6b115bf06a | ||
![]() |
f45785fafe | ||
![]() |
ec046bc925 | ||
![]() |
ab5733718b | ||
![]() |
1077fb2945 | ||
![]() |
b7a84cdd60 | ||
![]() |
78102f5882 | ||
![]() |
4ea11bd928 | ||
![]() |
785aefa028 | ||
![]() |
5c2004bcc1 | ||
![]() |
156d944ca1 | ||
![]() |
97a6354a72 | ||
![]() |
49422c3f63 | ||
![]() |
0b8700f725 | ||
![]() |
c5aa000a97 | ||
![]() |
4cdc4765f7 | ||
![]() |
a95290235d | ||
![]() |
fb9d7ac2d8 | ||
![]() |
d48a4e0ac6 | ||
![]() |
d33e035db7 | ||
![]() |
1437b4c4b6 | ||
![]() |
9fce60065b | ||
![]() |
d052b9ede8 | ||
![]() |
8cee5c729e | ||
![]() |
88bdf7c7ec | ||
![]() |
2c006e99f2 | ||
![]() |
e7e8dff0ec | ||
![]() |
981c798e22 | ||
![]() |
4613d8b1f6 | ||
![]() |
ba4e1949c4 | ||
![]() |
cc6686a790 | ||
![]() |
f791412f73 | ||
![]() |
0c8ac17dcb | ||
![]() |
9e11fe868e | ||
![]() |
7d91515bf5 | ||
![]() |
e0565c35ab | ||
![]() |
e5387e5806 | ||
![]() |
8a4c52aeb7 | ||
![]() |
15e7b8117c | ||
![]() |
d1703ba3e8 | ||
![]() |
c977f22047 | ||
![]() |
2e47aa1905 | ||
![]() |
c72105dca3 | ||
![]() |
e01f1cfcac | ||
![]() |
2e4c73c087 | ||
![]() |
c7f7ef28bf | ||
![]() |
aac7dbab58 | ||
![]() |
8518f774d4 | ||
![]() |
cb0d91d124 | ||
![]() |
107f428dd3 | ||
![]() |
7758ddba56 | ||
![]() |
e0376c803f | ||
![]() |
788c490bbc | ||
![]() |
cdf6e9eb75 | ||
![]() |
4aa49f66bc | ||
![]() |
50d0671abe | ||
![]() |
e176357fbf | ||
![]() |
de1b127ac2 | ||
![]() |
1dad7c81da | ||
![]() |
e980e93969 | ||
![]() |
57fc56f836 | ||
![]() |
05113e1809 | ||
![]() |
1bf82f216a | ||
![]() |
004ff58c21 | ||
![]() |
f1a1654371 | ||
![]() |
862044ca23 | ||
![]() |
c54b474838 | ||
![]() |
42cbe863bb | ||
![]() |
ccc42dad79 | ||
![]() |
82ff444cec | ||
![]() |
24c591fbf3 | ||
![]() |
ad676d7fd3 | ||
![]() |
cbe4782d78 | ||
![]() |
3fdcc1c0ea | ||
![]() |
f9d64e51c4 | ||
![]() |
b082828a75 | ||
![]() |
25f5bf0042 | ||
![]() |
f5dec3c6d5 | ||
![]() |
3215437bb8 | ||
![]() |
33176d8f3d | ||
![]() |
f82b62f45c | ||
![]() |
edfdd0da89 | ||
![]() |
33d9bf4660 | ||
![]() |
1479ce9d56 | ||
![]() |
1912bda60d | ||
![]() |
2e25db4d1b | ||
![]() |
ec08b2ef65 | ||
![]() |
2c740cedb8 | ||
![]() |
e3984d7bf9 | ||
![]() |
2f86b6ec3d | ||
![]() |
d10045eac1 | ||
![]() |
41da68290e | ||
![]() |
593a2de07b | ||
![]() |
59540bdf63 | ||
![]() |
616df070a4 | ||
![]() |
ef62f1956b | ||
![]() |
b41f25ef12 | ||
![]() |
ce8caa34f5 | ||
![]() |
3ed538276e | ||
![]() |
c1a29e8091 | ||
![]() |
4e8bf434f1 | ||
![]() |
dd8c568a2c | ||
![]() |
7ab9257f5e | ||
![]() |
7021fd5809 | ||
![]() |
adec2fc2df | ||
![]() |
27ebcc1bda | ||
![]() |
2fb7a31c76 | ||
![]() |
65994e7280 | ||
![]() |
036eedc69d | ||
![]() |
7937714ce6 | ||
![]() |
cdbd51f6f7 | ||
![]() |
d5a8105718 | ||
![]() |
1c9eab7ca0 | ||
![]() |
6e624b394b | ||
![]() |
1cb10694e7 | ||
![]() |
d496b9742f | ||
![]() |
72e5375795 | ||
![]() |
04f8f0f74f | ||
![]() |
82fb622904 | ||
![]() |
95ba1fd0cb | ||
![]() |
c7b3a517e8 | ||
![]() |
523dc881bb | ||
![]() |
1123adc584 | ||
![]() |
15be1688ad | ||
![]() |
0c0e82a3ba | ||
![]() |
8abe8d7615 | ||
![]() |
f95ba4c04c | ||
![]() |
6874788cc0 | ||
![]() |
f77bd79387 | ||
![]() |
67a91b7c19 | ||
![]() |
30211fe61d | ||
![]() |
9de80b2947 | ||
![]() |
b7a3fe6e91 | ||
![]() |
1f38d13b3b | ||
![]() |
ef6e468a7f | ||
![]() |
729a5e385f | ||
![]() |
32fd7a51f4 | ||
![]() |
f3f32c800e | ||
![]() |
1f44b4b5a9 | ||
![]() |
971538e9c2 | ||
![]() |
db9924bd87 | ||
![]() |
74c6b9077a | ||
![]() |
23865c31e6 | ||
![]() |
8641a701cb | ||
![]() |
3ca99e5c90 | ||
![]() |
753804f463 | ||
![]() |
9aedeab4fa | ||
![]() |
fc4e3e90b2 | ||
![]() |
ae8a9940ed | ||
![]() |
a544295167 | ||
![]() |
8a9e149d33 | ||
![]() |
49611e285f | ||
![]() |
def0c51669 | ||
![]() |
572215b359 | ||
![]() |
0f8cf574d3 | ||
![]() |
312f1df368 | ||
![]() |
5bfacb3bf0 | ||
![]() |
2103866c48 | ||
![]() |
61cf1bf1eb | ||
![]() |
9c407caf2c | ||
![]() |
323cf72be3 | ||
![]() |
eeced628b3 | ||
![]() |
38be488f86 | ||
![]() |
83756a338a | ||
![]() |
b9415cb5f0 | ||
![]() |
fd49e26120 | ||
![]() |
3474e92eb7 | ||
![]() |
913299998c | ||
![]() |
d9e522e4d7 | ||
![]() |
22c8e4a455 | ||
![]() |
02fe5144d8 | ||
![]() |
9d333fb557 | ||
![]() |
ef2ca4a07f | ||
![]() |
f74ee76ae2 | ||
![]() |
6fb9a7636d | ||
![]() |
56249110d6 | ||
![]() |
1a2ebabd22 | ||
![]() |
28511d0cbf | ||
![]() |
b01bdec973 | ||
![]() |
ecee5980af | ||
![]() |
3e1b85e6b5 | ||
![]() |
ce94d1ea7c | ||
![]() |
62654ec598 | ||
![]() |
fbe4550c78 | ||
![]() |
a082667c24 | ||
![]() |
fc29b519ae | ||
![]() |
c391b19c0e | ||
![]() |
8c49e3c4ef | ||
![]() |
67022b9ffc | ||
![]() |
eb9023595d | ||
![]() |
8262d1617f | ||
![]() |
581a803cc4 | ||
![]() |
5ff8fe68ba | ||
![]() |
a2a039ebc5 | ||
![]() |
1064aed1b0 | ||
![]() |
7025592e8e | ||
![]() |
4966354b62 | ||
![]() |
68d6faf4af | ||
![]() |
e3346483b9 | ||
![]() |
e8fb79e5ce | ||
![]() |
d612162ab1 | ||
![]() |
86f8ef3a70 | ||
![]() |
0e43435362 | ||
![]() |
aaefe0b09f | ||
![]() |
bc731a9dc3 | ||
![]() |
da25701dca | ||
![]() |
21ae483dc9 | ||
![]() |
38b6e9ca10 | ||
![]() |
d31245866c | ||
![]() |
4e08d8f3b3 | ||
![]() |
1e717ab33e | ||
![]() |
995fb4974e | ||
![]() |
ffb76132f8 | ||
![]() |
acba3af54b | ||
![]() |
40ac456937 | ||
![]() |
5c32413bf7 | ||
![]() |
22792c70c5 | ||
![]() |
a8ed87298a | ||
![]() |
b15270dfe2 | ||
![]() |
58ad949bc8 | ||
![]() |
adce40de56 | ||
![]() |
0f487ae4bf | ||
![]() |
2848e3a63b | ||
![]() |
5a172a64c5 | ||
![]() |
433aa16ea6 | ||
![]() |
50cb8cf3cc | ||
![]() |
4e5406b27b | ||
![]() |
80eb80619a | ||
![]() |
bf71b3a869 | ||
![]() |
ff270c4b7d | ||
![]() |
8b659498b6 | ||
![]() |
5415068917 | ||
![]() |
357a67c00d | ||
![]() |
cbe4269320 | ||
![]() |
fbd5185ce2 | ||
![]() |
a33cf97e2c | ||
![]() |
7e7da26543 | ||
![]() |
79058e893b | ||
![]() |
e9231fc17e | ||
![]() |
2eb548bb74 | ||
![]() |
08baf8a757 | ||
![]() |
f02fa6a94b | ||
![]() |
2ed6d0e73c | ||
![]() |
35d9b2ac3c | ||
![]() |
18d09c6f04 | ||
![]() |
70b81de49d | ||
![]() |
f0808c1f54 | ||
![]() |
e779f0747e | ||
![]() |
bdd18775c3 | ||
![]() |
711d51c022 | ||
![]() |
1b0d8bba29 | ||
![]() |
2988cc512f | ||
![]() |
a2f8e5f3e7 | ||
![]() |
680bf06a4b | ||
![]() |
ff0b1881e2 | ||
![]() |
de653e1f7b | ||
![]() |
bb41170765 | ||
![]() |
0ed2bc93aa | ||
![]() |
04770f8ee2 | ||
![]() |
15a2790b9f | ||
![]() |
83880791b1 | ||
![]() |
4dca3289f6 | ||
![]() |
083a3ebfc4 | ||
![]() |
6117c4e989 | ||
![]() |
609763e658 | ||
![]() |
2c57ab60f1 | ||
![]() |
dd17a153d2 | ||
![]() |
c2d551bb7c | ||
![]() |
e0b1921108 | ||
![]() |
fcf39ceb96 | ||
![]() |
3cc979a077 | ||
![]() |
9972973774 | ||
![]() |
20ae32bc26 | ||
![]() |
a29892023b | ||
![]() |
b283fec482 | ||
![]() |
e0116a8236 | ||
![]() |
d1990a4bac | ||
![]() |
cbba1849e2 | ||
![]() |
43393d1647 | ||
![]() |
b47ee1051c | ||
![]() |
393adacc9e | ||
![]() |
073428849e | ||
![]() |
e6ac0258e3 | ||
![]() |
d7e7798a55 | ||
![]() |
2557414b11 | ||
![]() |
f7065fbce9 | ||
![]() |
016564eee9 | ||
![]() |
ff3087c39c | ||
![]() |
239438ee5d | ||
![]() |
5458cda31f | ||
![]() |
36f49e66fd | ||
![]() |
2bafd38ea8 | ||
![]() |
73b3262491 | ||
![]() |
808cde033f | ||
![]() |
fa8f6b7b91 | ||
![]() |
94c120cdb1 | ||
![]() |
7b2be54f8f | ||
![]() |
4b56db5255 | ||
![]() |
93165c9111 | ||
![]() |
caa604d5ca | ||
![]() |
e7e9e2cf85 | ||
![]() |
daa04e9973 | ||
![]() |
5355269f5d | ||
![]() |
2665a75250 | ||
![]() |
8a39d18323 | ||
![]() |
b8a026397b | ||
![]() |
bd5fe302eb | ||
![]() |
de0f1b2b65 | ||
![]() |
defaa2b276 | ||
![]() |
60efe00a1f | ||
![]() |
fe93b993db | ||
![]() |
f6afc92d3c | ||
![]() |
e4c635c855 | ||
![]() |
a3e59e168f | ||
![]() |
e56355b406 | ||
![]() |
8ef15c50b4 | ||
![]() |
81588469b8 | ||
![]() |
70a920af3c | ||
![]() |
1329e60c89 | ||
![]() |
9b7c095080 | ||
![]() |
654ff99cd1 | ||
![]() |
0511bc360e | ||
![]() |
ea9e8cc392 | ||
![]() |
8433678371 | ||
![]() |
757bc00854 | ||
![]() |
2551393821 | ||
![]() |
0acd41b7f0 | ||
![]() |
85ca73db84 | ||
![]() |
444cbd00d9 | ||
![]() |
15b500886c | ||
![]() |
3aac834e72 | ||
![]() |
6edf23b91f | ||
![]() |
1249c0eea9 | ||
![]() |
3133118870 |
@@ -1,4 +0,0 @@
|
||||
node_modules
|
||||
hass_frontend
|
||||
hass_frontend_es5
|
||||
.git
|
@@ -1,80 +0,0 @@
|
||||
{
|
||||
"extends": ["airbnb-base", "prettier"],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "2020",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true,
|
||||
"modules": true
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "h",
|
||||
"version": "15.0"
|
||||
},
|
||||
"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,
|
||||
"mocha": true
|
||||
},
|
||||
"rules": {
|
||||
"class-methods-use-this": 0,
|
||||
"new-cap": 0,
|
||||
"prefer-template": 0,
|
||||
"object-shorthand": 0,
|
||||
"func-names": 0,
|
||||
"prefer-arrow-callback": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-var": 0,
|
||||
"strict": 0,
|
||||
"prefer-spread": 0,
|
||||
"no-plusplus": 0,
|
||||
"no-bitwise": 0,
|
||||
"comma-dangle": 0,
|
||||
"vars-on-top": 0,
|
||||
"no-continue": 0,
|
||||
"no-param-reassign": 0,
|
||||
"no-multi-assign": 0,
|
||||
"radix": 0,
|
||||
"no-alert": 0,
|
||||
"no-return-await": 0,
|
||||
"prefer-destructuring": 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,
|
||||
"react/prefer-es6-class": 2,
|
||||
"react/no-string-refs": 2,
|
||||
"react/require-render-return": 2,
|
||||
"react/no-find-dom-node": 2,
|
||||
"react/no-is-mounted": 2,
|
||||
"react/jsx-no-comment-textnodes": 2,
|
||||
"react/jsx-no-undef": 2,
|
||||
"react/jsx-uses-react": 2,
|
||||
"react/jsx-uses-vars": 2,
|
||||
"no-restricted-syntax": [0, "ForOfStatement"],
|
||||
"prettier/prettier": "error"
|
||||
},
|
||||
"plugins": ["react", "prettier"]
|
||||
}
|
@@ -1,12 +1,91 @@
|
||||
{
|
||||
"extends": "./.eslintrc-hound.json",
|
||||
"plugins": ["react"],
|
||||
"extends": [
|
||||
"airbnb-typescript/base",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:wc/recommended",
|
||||
"plugin:lit/recommended",
|
||||
"prettier",
|
||||
"prettier/@typescript-eslint"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"ecmaFeatures": {
|
||||
"modules": true
|
||||
},
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"settings": {
|
||||
"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,
|
||||
"es6": true
|
||||
},
|
||||
"rules": {
|
||||
"import/no-unresolved": 2,
|
||||
"linebreak-style": 0,
|
||||
"implicit-arrow-linebreak": 0
|
||||
}
|
||||
"class-methods-use-this": 0,
|
||||
"new-cap": 0,
|
||||
"prefer-template": 0,
|
||||
"object-shorthand": 0,
|
||||
"func-names": 0,
|
||||
"prefer-arrow-callback": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"strict": 0,
|
||||
"prefer-spread": 0,
|
||||
"no-plusplus": 0,
|
||||
"no-bitwise": 2,
|
||||
"comma-dangle": 0,
|
||||
"vars-on-top": 0,
|
||||
"no-continue": 0,
|
||||
"no-param-reassign": 0,
|
||||
"no-multi-assign": 0,
|
||||
"no-console": 2,
|
||||
"radix": 0,
|
||||
"no-alert": 0,
|
||||
"no-return-await": 0,
|
||||
"no-nested-ternary": 0,
|
||||
"prefer-destructuring": 0,
|
||||
"no-restricted-globals": [2, "event"],
|
||||
"prefer-promise-reject-errors": 0,
|
||||
"import/order": 0,
|
||||
"import/prefer-default-export": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"import/no-cycle": 0,
|
||||
"import/extensions": [
|
||||
2,
|
||||
"ignorePackages",
|
||||
{ "ts": "never", "js": "never" }
|
||||
],
|
||||
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
|
||||
"object-curly-newline": 0,
|
||||
"default-case": 0,
|
||||
"wc/no-self-class": 0,
|
||||
"no-shadow": 0,
|
||||
"@typescript-eslint/camelcase": 0,
|
||||
"@typescript-eslint/ban-ts-comment": 0,
|
||||
"@typescript-eslint/no-use-before-define": 0,
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/no-unused-vars": 0,
|
||||
"@typescript-eslint/explicit-function-return-type": 0,
|
||||
"@typescript-eslint/explicit-module-boundary-types": 0,
|
||||
"@typescript-eslint/no-shadow": ["error"]
|
||||
},
|
||||
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
|
||||
"processor": "disable/disable"
|
||||
}
|
||||
|
1
.gitattributes
vendored
@@ -11,3 +11,4 @@
|
||||
*.mp3 binary
|
||||
|
||||
demo/public/api/camera_proxy_stream/* binary
|
||||
demo/public/api/media_player_proxy/* binary
|
||||
|
100
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
name: Report a bug with the UI, Frontend or Lovelace
|
||||
about: Report an issue related to the Home Assistant frontend.
|
||||
labels: bug
|
||||
---
|
||||
|
||||
<!-- 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
|
||||
- Do not report issues for custom Lovelace cards.
|
||||
- Provide as many details as possible. Paste logs, configuration samples and code into the backticks.
|
||||
DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed without comment.
|
||||
-->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I have updated to the latest available Home Assistant version.
|
||||
- [ ] I have cleared the cache of my browser.
|
||||
- [ ] I have tried a different browser to see if it is related to my browser.
|
||||
|
||||
## The problem
|
||||
|
||||
<!--
|
||||
Describe the issue you are experiencing here to communicate to the
|
||||
maintainers. Tell us about the current behavior.
|
||||
If possible provide a screenshot with a description.
|
||||
-->
|
||||
|
||||
## Expected behavior
|
||||
|
||||
<!--
|
||||
Describe what you expected to happen or it should look/behave.
|
||||
If possible provide a screenshot with a description.
|
||||
-->
|
||||
|
||||
## Steps to reproduce
|
||||
|
||||
<!--
|
||||
Provide steps for us, that helps reproducing your issue.
|
||||
For example:
|
||||
1. Add a climate integration
|
||||
2. Navigate to Lovelace
|
||||
3. Click more info of the climate entity
|
||||
4. Set the HVAC action to heat
|
||||
5. Set the temperature higher than the current temperature
|
||||
6. Set the HVAC action to cool
|
||||
-->
|
||||
|
||||
## Environment
|
||||
|
||||
<!--
|
||||
Provide details about the versions you are using, which helps us reproducing
|
||||
and finding the issue quicker. Version information is found in the
|
||||
Home Assistant frontend: Configuration -> Info.
|
||||
|
||||
Browser version and operating system is important! Please try to replicate
|
||||
your issue in a different browser and be sure to include your findings.
|
||||
-->
|
||||
|
||||
- Home Assistant release with the issue:
|
||||
- Last working Home Assistant release (if known):
|
||||
- Browser and browser version:
|
||||
- Operating system:
|
||||
|
||||
## State of relevant entities
|
||||
|
||||
<!--
|
||||
If your issue is about how an entity is shown in the UI, please add the state
|
||||
and attributes for all situations with a screenshot of the UI.
|
||||
You can find this information at `/developer-tools/state`
|
||||
-->
|
||||
|
||||
```yaml
|
||||
|
||||
```
|
||||
|
||||
## Problem-relevant configuration
|
||||
|
||||
<!--
|
||||
An example configuration that caused the problem for you. Fill this out even
|
||||
if it seems unimportant to you. Please be sure to remove personal information
|
||||
like passwords, private URLs and other credentials.
|
||||
-->
|
||||
|
||||
```yaml
|
||||
|
||||
```
|
||||
|
||||
## Javascript errors shown in your browser console/inspector
|
||||
|
||||
<!--
|
||||
If you come across any javascript or other error logs, e.g., in your browser
|
||||
console/inspector please provide them.
|
||||
-->
|
||||
|
||||
```txt
|
||||
|
||||
```
|
||||
|
||||
## Additional information
|
53
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,53 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ""
|
||||
labels: bug
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
<!-- 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
|
||||
- Provide as many details as possible. Do not delete any text from this template!
|
||||
-->
|
||||
|
||||
**Checklist:**
|
||||
|
||||
- [ ] I updated to the latest version available
|
||||
- [ ] I cleared the cache of my browser
|
||||
|
||||
**Home Assistant release with the issue:**
|
||||
|
||||
<!--
|
||||
- Frontend -> Developer tools -> Info
|
||||
- Or use this command: hass --version
|
||||
-->
|
||||
|
||||
**Last working Home Assistant release (if known):**
|
||||
|
||||
**UI (States or Lovelace UI?):**
|
||||
|
||||
<!--
|
||||
- Frontend -> Developer tools -> Info
|
||||
-->
|
||||
|
||||
**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:**
|
17
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Request a feature for the UI, Frontend or Lovelace
|
||||
url: https://github.com/home-assistant/frontend/discussions/category_choices
|
||||
about: Request an new feature for the Home Assistant frontend.
|
||||
- name: Report a bug that is NOT related to the UI, Frontend or Lovelace
|
||||
url: https://github.com/home-assistant/core/issues
|
||||
about: This is the issue tracker for our frontend. Please report other issues with the backend repository.
|
||||
- name: Report incorrect or missing information on our website
|
||||
url: https://github.com/home-assistant/home-assistant.io/issues
|
||||
about: Our documentation has its own issue tracker. Please report issues with the website there.
|
||||
- name: I have a question or need support
|
||||
url: https://www.home-assistant.io/help
|
||||
about: We use GitHub for tracking bugs, check our website for resources on getting help.
|
||||
- name: I'm unsure where to go
|
||||
url: https://www.home-assistant.io/join-chat
|
||||
about: If you are unsure where to go, then joining our chat is recommended; Just ask!
|
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,19 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ""
|
||||
labels: feature request
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
83
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
<!--
|
||||
You are amazing! Thanks for contributing to our project!
|
||||
Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
|
||||
-->
|
||||
|
||||
## Breaking change
|
||||
|
||||
<!--
|
||||
If your PR contains a breaking change for existing users, it is important
|
||||
to tell them what breaks, how to make it work again and why we did this.
|
||||
This piece of text is published with the release notes, so it helps if you
|
||||
write it towards our users, not us.
|
||||
Note: Remove this section if this PR is NOT a breaking change.
|
||||
-->
|
||||
|
||||
## Proposed change
|
||||
|
||||
<!--
|
||||
Describe the big picture of your changes here to communicate to the
|
||||
maintainers why we should accept this pull request. If it fixes a bug
|
||||
or resolves a feature request, be sure to link to that issue in the
|
||||
additional information section.
|
||||
-->
|
||||
|
||||
## Type of change
|
||||
|
||||
<!--
|
||||
What type of change does your PR introduce to the Home Assistant frontend?
|
||||
NOTE: Please, check only 1! box!
|
||||
If your PR requires multiple boxes to be checked, you'll most likely need to
|
||||
split it into multiple PRs. This makes things easier and faster to code review.
|
||||
-->
|
||||
|
||||
- [ ] Dependency upgrade
|
||||
- [ ] Bugfix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (thank you!)
|
||||
- [ ] Breaking change (fix/feature causing existing functionality to break)
|
||||
- [ ] Code quality improvements to existing code or addition of tests
|
||||
|
||||
## Example configuration
|
||||
|
||||
<!--
|
||||
Supplying a configuration snippet, makes it easier for a maintainer to test
|
||||
your PR.
|
||||
-->
|
||||
|
||||
```yaml
|
||||
|
||||
```
|
||||
|
||||
## Additional information
|
||||
|
||||
<!--
|
||||
Details are important, and help maintainers processing your PR.
|
||||
Please be sure to fill out additional details, if applicable.
|
||||
-->
|
||||
|
||||
- This PR fixes or closes issue: fixes #
|
||||
- This PR is related to issue:
|
||||
- Link to documentation pull request:
|
||||
|
||||
## Checklist
|
||||
|
||||
<!--
|
||||
Put an `x` in the boxes that apply. You can also fill these out after
|
||||
creating the PR. If you're unsure about any of them, don't hesitate to ask.
|
||||
We're here to help! This is simply a reminder of what we are going to look
|
||||
for before merging your code.
|
||||
-->
|
||||
|
||||
- [ ] The code change is tested and works locally.
|
||||
- [ ] There is no commented out code in this PR.
|
||||
- [ ] Tests have been added to verify that the new code works.
|
||||
|
||||
If user exposed functionality or configuration variables are added/changed:
|
||||
|
||||
- [ ] Documentation added/updated for [www.home-assistant.io][docs-repository]
|
||||
|
||||
<!--
|
||||
Thank you for contributing <3
|
||||
-->
|
||||
|
||||
[docs-repository]: https://github.com/home-assistant/home-assistant.io
|
27
.github/lock.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
||||
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 1
|
||||
|
||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||
skipCreatedBefore: 2020-01-01
|
||||
|
||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||
exemptLabels: []
|
||||
|
||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||
lockLabel: false
|
||||
|
||||
# Comment to post before locking. Set to `false` to disable
|
||||
lockComment: false
|
||||
|
||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||
setLockReason: false
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
only: pulls
|
||||
|
||||
# Optionally, specify configuration settings just for `issues` or `pulls`
|
||||
issues:
|
||||
daysUntilLock: 30
|
56
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 90
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
daysUntilClose: 7
|
||||
|
||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
||||
onlyLabels: []
|
||||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- feature request
|
||||
- Help wanted
|
||||
- to do
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: true
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: true
|
||||
|
||||
# Set to true to ignore issues with an assignee (defaults to false)
|
||||
exemptAssignees: false
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
There hasn't been any activity on this issue recently. Due to the high number
|
||||
of incoming GitHub notifications, we have to clean some of the old issues,
|
||||
as many of them have already been resolved with the latest updates.
|
||||
|
||||
Please make sure to update to the latest Home Assistant version and check
|
||||
if that solves the issue. Let us know if that works for you by adding a
|
||||
comment 👍
|
||||
|
||||
This issue now has been marked as stale and will be closed if no further
|
||||
activity occurs. Thank you for your contributions.
|
||||
|
||||
# Comment to post when removing the stale label.
|
||||
# unmarkComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Comment to post when closing a stale Issue or Pull Request.
|
||||
# closeComment: >
|
||||
# Your comment here.
|
||||
|
||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||
limitPerRun: 30
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
only: issues
|
123
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
- master
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Build resources
|
||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations gather-gallery-demos
|
||||
- name: Run eslint
|
||||
run: ./node_modules/.bin/eslint '{**/src,src}/**/*.{js,ts,html}' --ignore-path .gitignore
|
||||
- name: Run tsc
|
||||
run: ./node_modules/.bin/tsc
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Run Mocha
|
||||
run: npm run mocha
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint, test]
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Build Application
|
||||
run: ./node_modules/.bin/gulp build-app
|
||||
env:
|
||||
IS_TEST: "true"
|
||||
supervisor:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint, test]
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Build Application
|
||||
run: ./node_modules/.bin/gulp build-hassio
|
||||
env:
|
||||
IS_TEST: "true"
|
60
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev, master]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [dev]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Override automatic language detection by changing the below list
|
||||
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
||||
language: ['javascript']
|
||||
# Learn more...
|
||||
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
39
.github/workflows/demo.yaml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Demo
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Build Demo
|
||||
run: ./node_modules/.bin/gulp build-demo
|
||||
- name: Deploy to Netlify
|
||||
uses: netlify/actions/cli@master
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }}
|
||||
with:
|
||||
args: deploy --dir=demo/dist --prod
|
14
.github/workflows/release-drafter.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Release Drafter
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
update_release_draft:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
6
.gitignore
vendored
@@ -5,7 +5,6 @@ npm-debug.log
|
||||
.DS_Store
|
||||
hass_frontend/*
|
||||
.reify-cache
|
||||
demo/hademo-icons.html
|
||||
|
||||
# Python stuff
|
||||
*.py[cod]
|
||||
@@ -25,9 +24,12 @@ dist
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
|
||||
# Cast dev settings
|
||||
# Cast dev settings
|
||||
src/cast/dev_const.ts
|
||||
|
||||
# Secrets
|
||||
.lokalise_token
|
||||
yarn-error.log
|
||||
|
||||
#asdf
|
||||
.tool-versions
|
||||
|
10
.prettierignore
Normal file
@@ -0,0 +1,10 @@
|
||||
build
|
||||
build-translations/*
|
||||
translations/*
|
||||
node_modules/*
|
||||
hass_frontend/*
|
||||
pip-selfcheck.json
|
||||
|
||||
# vscode
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
27
.travis.yml
@@ -1,27 +0,0 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
- bower_components
|
||||
install: yarn install
|
||||
script:
|
||||
- npm run build
|
||||
- hassio/script/build_hassio
|
||||
# Because else eslint fails because hassio has cleaned that build
|
||||
- ./node_modules/.bin/gulp gen-icons-app
|
||||
- npm run test
|
||||
# - 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
.vscode/extensions.json
vendored
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||
"esbenp.prettier-vscode",
|
||||
"bierner.lit-html",
|
||||
"runem.lit-plugin"
|
||||
|
@@ -2,79 +2,139 @@
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [safety@home-assistant.io][email]. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
reported to the community leaders responsible for enforcement at
|
||||
[safety@home-assistant.io][email] or by using the report/flag feature of
|
||||
the medium used. All complaints will be reviewed and investigated promptly and
|
||||
fairly.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available [here][version].
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available [here][version].
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder][mozilla].
|
||||
|
||||
## Adoption
|
||||
|
||||
This Code of Conduct was first adopted January 21st, 2017 and announced in [this][coc-blog] blog post.
|
||||
This Code of Conduct was first adopted January 21st, 2017 and announced in
|
||||
[this][coc-blog] blog post and has been updated on May 25th, 2020 to version
|
||||
2.0 of the [Contributor Covenant][homepage] as announced in [this][coc2-blog]
|
||||
blog post.
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
<https://www.contributor-covenant.org/faq>. Translations are available at
|
||||
<https://www.contributor-covenant.org/translations>.
|
||||
|
||||
[coc-blog]: /blog/2017/01/21/home-assistant-governance/
|
||||
[coc2-blog]: /blog/2020/05/25/code-of-conduct-updated/
|
||||
[email]: mailto:safety@home-assistant.io
|
||||
[coc-blog]: https://home-assistant.io/blog/2017/01/21/home-assistant-governance/
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[mozilla]: https://github.com/mozilla/diversity
|
||||
[version]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
||||
|
31
Dockerfile
@@ -1,31 +0,0 @@
|
||||
FROM node:8.11.1-alpine
|
||||
|
||||
# install yarn
|
||||
ENV PATH /root/.yarn/bin:$PATH
|
||||
|
||||
## Install/force base tools
|
||||
RUN apk update \
|
||||
&& apk add make g++ curl bash binutils tar git python2 python3 \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& /bin/bash \
|
||||
&& touch ~/.bashrc
|
||||
|
||||
## Install yarn
|
||||
RUN curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||
|
||||
## Setup the project
|
||||
RUN mkdir -p /frontend
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
|
||||
COPY script/docker_entrypoint.sh /usr/bin/docker_entrypoint.sh
|
||||
|
||||
RUN chmod +x /usr/bin/docker_entrypoint.sh
|
||||
|
||||
CMD [ "docker_entrypoint.sh" ]
|
20
README.md
@@ -1,17 +1,17 @@
|
||||
# Home Assistant Polymer [](https://travis-ci.org/home-assistant/home-assistant-polymer)
|
||||
# Home Assistant Frontend
|
||||
|
||||
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
|
||||
|
||||
[](https://home-assistant.io/demo/)
|
||||
[](https://demo.home-assistant.io/)
|
||||
|
||||
- [View demo of the Polymer frontend](https://home-assistant.io/demo/)
|
||||
- [View demo of Home Assistant](https://demo.home-assistant.io/)
|
||||
- [More information about Home Assistant](https://home-assistant.io)
|
||||
- [Frontend development instructions](https://developers.home-assistant.io/docs/en/frontend_index.html)
|
||||
- [Frontend development instructions](https://developers.home-assistant.io/docs/frontend/development/)
|
||||
|
||||
## Development
|
||||
|
||||
- Initial setup: `script/setup`
|
||||
- Development: [Instructions](https://developers.home-assistant.io/docs/en/frontend_development.html)
|
||||
- Development: [Instructions](https://developers.home-assistant.io/docs/frontend/development/)
|
||||
- Production build: `script/build_frontend`
|
||||
- Gallery: `cd gallery && script/develop_gallery`
|
||||
- Hass.io: [Instructions](https://developers.home-assistant.io/docs/en/hassio_hass.html)
|
||||
@@ -19,15 +19,11 @@ This is the repository for the official [Home Assistant](https://home-assistant.
|
||||
## Frontend development
|
||||
|
||||
### Classic environment
|
||||
|
||||
A complete guide can be found at the following [link](https://www.home-assistant.io/developers/frontend/). It describes a short guide for the build of project.
|
||||
|
||||
### Docker environment
|
||||
It is possible to compile the project and/or run commands in the development environment having only the [Docker](https://www.docker.com) pre-installed in the system. On the root of project you can do:
|
||||
* `sh ./script/docker_run.sh build` Build all the project with one command
|
||||
* `sh ./script/docker_run.sh bash` Open an interactive shell (the same environment generated by the *classic environment*) where you can run commands. This bash work on your project directory and any change on your file is automatically present within your build bash.
|
||||
|
||||
**Note**: if you have installed `npm` in addition to the `docker`, you can use the commands `npm run docker_build` and `npm run bash` to get a full build or bash as explained above
|
||||
|
||||
## License
|
||||
|
||||
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.
|
||||
|
||||
We use [BrowserStack](https://www.browserstack.com) to test Home Assistant on a large variety of devices.
|
||||
|
30
azure-pipelines-netlify.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
# https://dev.azure.com/home-assistant
|
||||
|
||||
trigger: none
|
||||
pr: none
|
||||
schedules:
|
||||
- cron: "0 0 * * *"
|
||||
displayName: "build preview"
|
||||
branches:
|
||||
include:
|
||||
- dev
|
||||
always: true
|
||||
variables:
|
||||
- group: netlify
|
||||
|
||||
jobs:
|
||||
|
||||
- job: 'Netlify_preview'
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- script: |
|
||||
# Cast
|
||||
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_CAST}
|
||||
|
||||
# Demo
|
||||
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_DEMO}
|
||||
|
||||
# Gallery
|
||||
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_GALLERY}
|
||||
displayName: 'Trigger netlify build preview'
|
@@ -8,7 +8,7 @@ trigger:
|
||||
pr: none
|
||||
variables:
|
||||
- name: versionWheels
|
||||
value: '1.3-3.7-alpine3.10'
|
||||
value: '1.10.1-3.7-alpine3.11'
|
||||
- name: versionNode
|
||||
value: '12.1'
|
||||
- group: twine
|
||||
@@ -47,18 +47,13 @@ stages:
|
||||
|
||||
script/release
|
||||
displayName: "Build and release package"
|
||||
- stage: "Wheels"
|
||||
jobs:
|
||||
- template: templates/azp-job-wheels.yaml@azure
|
||||
parameters:
|
||||
builderVersion: '$(versionWheels)'
|
||||
builderApk: 'build-base'
|
||||
wheelsLocal: true
|
||||
wheelsRequirement: 'requirement.txt'
|
||||
preBuild:
|
||||
- task: NodeTool@0
|
||||
displayName: "Use Node $(versionNode)"
|
||||
inputs:
|
||||
versionSpec: "$(versionNode)"
|
||||
- script: |
|
||||
set -e
|
||||
|
||||
yarn install
|
||||
script/build_frontend
|
||||
sleep 240
|
||||
echo "home-assistant-frontend==$(Build.SourceBranchName)" > requirement.txt
|
||||
|
70
azure-pipelines-translation.yml
Normal file
@@ -0,0 +1,70 @@
|
||||
# https://dev.azure.com/home-assistant
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- dev
|
||||
paths:
|
||||
include:
|
||||
- translations/en.json
|
||||
pr: none
|
||||
schedules:
|
||||
- cron: "30 0 * * *"
|
||||
displayName: "frontend translation update"
|
||||
branches:
|
||||
include:
|
||||
- dev
|
||||
always: true
|
||||
variables:
|
||||
- group: translation
|
||||
resources:
|
||||
repositories:
|
||||
- repository: azure
|
||||
type: github
|
||||
name: 'home-assistant/ci-azure'
|
||||
endpoint: 'home-assistant'
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
- job: 'Upload'
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
displayName: 'Use Node 12.x'
|
||||
inputs:
|
||||
versionSpec: '12.x'
|
||||
- script: |
|
||||
export LOKALISE_TOKEN="$(lokaliseToken)"
|
||||
export AZURE_BRANCH="$(Build.SourceBranchName)"
|
||||
|
||||
./script/translations_upload_base
|
||||
displayName: 'Upload Translation'
|
||||
|
||||
- job: 'Download'
|
||||
dependsOn:
|
||||
- 'Upload'
|
||||
condition: or(eq(variables['Build.Reason'], 'Schedule'), eq(variables['Build.Reason'], 'Manual'))
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
displayName: 'Use Node 12.x'
|
||||
inputs:
|
||||
versionSpec: '12.x'
|
||||
- template: templates/azp-step-git-init.yaml@azure
|
||||
- script: |
|
||||
export LOKALISE_TOKEN="$(lokaliseToken)"
|
||||
export AZURE_BRANCH="$(Build.SourceBranchName)"
|
||||
|
||||
npm install
|
||||
./script/translations_download
|
||||
displayName: 'Download Translation'
|
||||
- script: |
|
||||
git checkout dev
|
||||
git add translation
|
||||
git commit -am "[ci skip] Translation update"
|
||||
git push
|
||||
displayName: 'Update translation'
|
@@ -1,48 +0,0 @@
|
||||
module.exports.babelLoaderConfig = ({ latestBuild }) => {
|
||||
if (latestBuild === undefined) {
|
||||
throw Error("latestBuild not defined for babel loader config");
|
||||
}
|
||||
return {
|
||||
test: /\.m?js$|\.tsx?$/,
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: [
|
||||
!latestBuild && [
|
||||
require("@babel/preset-env").default,
|
||||
{ modules: false },
|
||||
],
|
||||
[
|
||||
require("@babel/preset-typescript").default,
|
||||
{
|
||||
jsxPragma: "h",
|
||||
},
|
||||
],
|
||||
].filter(Boolean),
|
||||
plugins: [
|
||||
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
|
||||
[
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
{ loose: true, useBuiltIns: true },
|
||||
],
|
||||
// Only support the syntax, Webpack will handle it.
|
||||
"@babel/syntax-dynamic-import",
|
||||
[
|
||||
"@babel/transform-react-jsx",
|
||||
{
|
||||
pragma: "h",
|
||||
},
|
||||
],
|
||||
[
|
||||
require("@babel/plugin-proposal-decorators").default,
|
||||
{ decoratorsBeforeExport: true },
|
||||
],
|
||||
[
|
||||
require("@babel/plugin-proposal-class-properties").default,
|
||||
{ loose: true },
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
204
build-scripts/bundle.js
Normal file
@@ -0,0 +1,204 @@
|
||||
const path = require("path");
|
||||
const env = require("./env.js");
|
||||
const paths = require("./paths.js");
|
||||
|
||||
// Files from NPM Packages that should not be imported
|
||||
module.exports.ignorePackages = ({ latestBuild }) => [
|
||||
// Bloats bundle and it's not used.
|
||||
path.resolve(require.resolve("moment"), "../locale"),
|
||||
// Part of yaml.js and only used for !!js functions that we don't use
|
||||
require.resolve("esprima"),
|
||||
];
|
||||
|
||||
// Files from NPM packages that we should replace with empty file
|
||||
module.exports.emptyPackages = ({ latestBuild }) =>
|
||||
[
|
||||
// Contains all color definitions for all material color sets.
|
||||
// We don't use it
|
||||
require.resolve("@polymer/paper-styles/color.js"),
|
||||
require.resolve("@polymer/paper-styles/default-theme.js"),
|
||||
// Loads stuff from a CDN
|
||||
require.resolve("@polymer/font-roboto/roboto.js"),
|
||||
require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"),
|
||||
// Compatibility not needed for latest builds
|
||||
latestBuild &&
|
||||
// wrapped in require.resolve so it blows up if file no longer exists
|
||||
require.resolve(
|
||||
path.resolve(paths.polymer_dir, "src/resources/compatibility.ts")
|
||||
),
|
||||
// This polyfill is loaded in workers to support ES5, filter it out.
|
||||
latestBuild && require.resolve("proxy-polyfill/src/index.js"),
|
||||
].filter(Boolean);
|
||||
|
||||
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
||||
__DEV__: !isProdBuild,
|
||||
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
|
||||
__VERSION__: JSON.stringify(env.version()),
|
||||
__DEMO__: false,
|
||||
__BACKWARDS_COMPAT__: false,
|
||||
__STATIC_PATH__: "/static/",
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
isProdBuild ? "production" : "development"
|
||||
),
|
||||
...defineOverlay,
|
||||
});
|
||||
|
||||
module.exports.terserOptions = (latestBuild) => ({
|
||||
safari10: true,
|
||||
ecma: latestBuild ? undefined : 5,
|
||||
output: { comments: false },
|
||||
});
|
||||
|
||||
module.exports.babelOptions = ({ latestBuild }) => ({
|
||||
babelrc: false,
|
||||
presets: [
|
||||
!latestBuild && [
|
||||
require("@babel/preset-env").default,
|
||||
{
|
||||
modules: false,
|
||||
useBuiltIns: "entry",
|
||||
corejs: "3.6",
|
||||
},
|
||||
],
|
||||
require("@babel/preset-typescript").default,
|
||||
].filter(Boolean),
|
||||
plugins: [
|
||||
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
|
||||
!latestBuild && [
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
{ loose: true, useBuiltIns: true },
|
||||
],
|
||||
// Only support the syntax, Webpack will handle it.
|
||||
"@babel/plugin-syntax-import-meta",
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"@babel/plugin-syntax-top-level-await",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||
[
|
||||
require("@babel/plugin-proposal-decorators").default,
|
||||
{ decoratorsBeforeExport: true },
|
||||
],
|
||||
[
|
||||
require("@babel/plugin-proposal-class-properties").default,
|
||||
{ loose: true },
|
||||
],
|
||||
].filter(Boolean),
|
||||
});
|
||||
|
||||
// Are already ES5, cause warnings when babelified.
|
||||
module.exports.babelExclude = () => [
|
||||
require.resolve("@mdi/js/mdi.js"),
|
||||
require.resolve("hls.js"),
|
||||
];
|
||||
|
||||
const outputPath = (outputRoot, latestBuild) =>
|
||||
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
|
||||
|
||||
const publicPath = (latestBuild, root = "") =>
|
||||
latestBuild ? `${root}/frontend_latest/` : `${root}/frontend_es5/`;
|
||||
|
||||
/*
|
||||
BundleConfig {
|
||||
// Object with entrypoints that need to be bundled
|
||||
entry: { [name: string]: pathToFile },
|
||||
// Folder where bundled files need to be written
|
||||
outputPath: string,
|
||||
// absolute url-path where bundled files can be found
|
||||
publicPath: string,
|
||||
// extra definitions that we need to replace in source
|
||||
defineOverlay: {[name: string]: value },
|
||||
// if this is a production build
|
||||
isProdBuild: boolean,
|
||||
// If we're targeting latest browsers
|
||||
latestBuild: boolean,
|
||||
// If we're doing a stats build (create nice chunk names)
|
||||
isStatsBuild: boolean,
|
||||
// Names of entrypoints that should not be hashed
|
||||
dontHash: Set<string>
|
||||
}
|
||||
*/
|
||||
|
||||
module.exports.config = {
|
||||
app({ isProdBuild, latestBuild, isStatsBuild }) {
|
||||
return {
|
||||
entry: {
|
||||
service_worker: "./src/entrypoints/service_worker.ts",
|
||||
app: "./src/entrypoints/app.ts",
|
||||
authorize: "./src/entrypoints/authorize.ts",
|
||||
onboarding: "./src/entrypoints/onboarding.ts",
|
||||
core: "./src/entrypoints/core.ts",
|
||||
"custom-panel": "./src/entrypoints/custom-panel.ts",
|
||||
},
|
||||
outputPath: outputPath(paths.app_output_root, latestBuild),
|
||||
publicPath: publicPath(latestBuild),
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
};
|
||||
},
|
||||
|
||||
demo({ isProdBuild, latestBuild, isStatsBuild }) {
|
||||
return {
|
||||
entry: {
|
||||
main: path.resolve(paths.demo_dir, "src/entrypoint.ts"),
|
||||
},
|
||||
outputPath: outputPath(paths.demo_output_root, latestBuild),
|
||||
publicPath: publicPath(latestBuild),
|
||||
defineOverlay: {
|
||||
__VERSION__: JSON.stringify(`DEMO-${env.version()}`),
|
||||
__DEMO__: true,
|
||||
},
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
};
|
||||
},
|
||||
|
||||
cast({ isProdBuild, latestBuild }) {
|
||||
const entry = {
|
||||
launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
|
||||
};
|
||||
|
||||
if (latestBuild) {
|
||||
entry.receiver = path.resolve(
|
||||
paths.cast_dir,
|
||||
"src/receiver/entrypoint.ts"
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
entry,
|
||||
outputPath: outputPath(paths.cast_output_root, latestBuild),
|
||||
publicPath: publicPath(latestBuild),
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
defineOverlay: {
|
||||
__BACKWARDS_COMPAT__: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
hassio({ isProdBuild, latestBuild }) {
|
||||
return {
|
||||
entry: {
|
||||
entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.ts"),
|
||||
},
|
||||
outputPath: outputPath(paths.hassio_output_root, latestBuild),
|
||||
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
};
|
||||
},
|
||||
|
||||
gallery({ isProdBuild, latestBuild }) {
|
||||
return {
|
||||
entry: {
|
||||
entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"),
|
||||
},
|
||||
outputPath: outputPath(paths.gallery_output_root, latestBuild),
|
||||
publicPath: publicPath(latestBuild),
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
};
|
||||
},
|
||||
};
|
@@ -1,6 +1,32 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const paths = require("./paths.js");
|
||||
|
||||
module.exports = {
|
||||
isProdBuild: process.env.NODE_ENV === "production",
|
||||
isStatsBuild: process.env.STATS === "1",
|
||||
isTravis: process.env.TRAVIS === "true",
|
||||
isNetlify: process.env.NETLIFY === "true",
|
||||
useRollup() {
|
||||
return process.env.ROLLUP === "1";
|
||||
},
|
||||
isProdBuild() {
|
||||
return (
|
||||
process.env.NODE_ENV === "production" || module.exports.isStatsBuild()
|
||||
);
|
||||
},
|
||||
isStatsBuild() {
|
||||
return process.env.STATS === "1";
|
||||
},
|
||||
isTest() {
|
||||
return process.env.IS_TEST === "true";
|
||||
},
|
||||
isNetlify() {
|
||||
return process.env.NETLIFY === "true";
|
||||
},
|
||||
version() {
|
||||
const version = fs
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "setup.py"), "utf8")
|
||||
.match(/\d{8}\.\d+/);
|
||||
if (!version) {
|
||||
throw Error("Version not found");
|
||||
}
|
||||
return version[0];
|
||||
},
|
||||
};
|
||||
|
@@ -1,16 +1,17 @@
|
||||
// Run HA develop mode
|
||||
const gulp = require("gulp");
|
||||
|
||||
const envVars = require("../env");
|
||||
const env = require("../env");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons.js");
|
||||
require("./gen-icons-json.js");
|
||||
require("./gather-static.js");
|
||||
require("./compress.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
require("./rollup.js");
|
||||
|
||||
gulp.task(
|
||||
"develop-app",
|
||||
@@ -20,14 +21,14 @@ gulp.task(
|
||||
},
|
||||
"clean",
|
||||
gulp.parallel(
|
||||
"gen-service-worker-dev",
|
||||
gulp.parallel("gen-icons-app", "gen-icons-mdi"),
|
||||
"gen-service-worker-app-dev",
|
||||
"gen-icons-json",
|
||||
"gen-pages-dev",
|
||||
"gen-index-app-dev",
|
||||
gulp.series("create-test-translation", "build-translations")
|
||||
"build-translations"
|
||||
),
|
||||
"copy-static",
|
||||
"webpack-watch-app"
|
||||
"copy-static-app",
|
||||
env.useRollup() ? "rollup-watch-app" : "webpack-watch-app"
|
||||
)
|
||||
);
|
||||
|
||||
@@ -38,15 +39,15 @@ gulp.task(
|
||||
process.env.NODE_ENV = "production";
|
||||
},
|
||||
"clean",
|
||||
gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"),
|
||||
"copy-static",
|
||||
"webpack-prod-app",
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-app",
|
||||
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
|
||||
...// Don't compress running tests
|
||||
(envVars.isTravis ? [] : ["compress-app"]),
|
||||
(env.isTest() ? [] : ["compress-app"]),
|
||||
gulp.parallel(
|
||||
"gen-pages-prod",
|
||||
"gen-index-app-prod",
|
||||
"gen-service-worker-prod"
|
||||
"gen-service-worker-app-prod"
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@@ -1,12 +1,14 @@
|
||||
const gulp = require("gulp");
|
||||
|
||||
const env = require("../env");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons.js");
|
||||
require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
require("./rollup.js");
|
||||
|
||||
gulp.task(
|
||||
"develop-cast",
|
||||
@@ -15,14 +17,11 @@ gulp.task(
|
||||
process.env.NODE_ENV = "development";
|
||||
},
|
||||
"clean-cast",
|
||||
gulp.parallel(
|
||||
"gen-icons-app",
|
||||
"gen-icons-mdi",
|
||||
"gen-index-cast-dev",
|
||||
"build-translations"
|
||||
),
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-cast",
|
||||
"webpack-dev-server-cast"
|
||||
"gen-index-cast-dev",
|
||||
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
|
||||
)
|
||||
);
|
||||
|
||||
@@ -33,9 +32,10 @@ gulp.task(
|
||||
process.env.NODE_ENV = "production";
|
||||
},
|
||||
"clean-cast",
|
||||
gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"),
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-cast",
|
||||
"webpack-prod-cast",
|
||||
env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast",
|
||||
"gen-index-cast-prod"
|
||||
)
|
||||
);
|
||||
|
@@ -1,39 +1,36 @@
|
||||
const del = require("del");
|
||||
const gulp = require("gulp");
|
||||
const config = require("../paths");
|
||||
const paths = require("../paths");
|
||||
require("./translations");
|
||||
|
||||
gulp.task(
|
||||
"clean",
|
||||
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
|
||||
return del([config.root, config.build_dir]);
|
||||
return del([paths.app_output_root, paths.build_dir]);
|
||||
})
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"clean-demo",
|
||||
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
|
||||
return del([config.demo_root, config.build_dir]);
|
||||
return del([paths.demo_output_root, paths.build_dir]);
|
||||
})
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"clean-cast",
|
||||
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
|
||||
return del([config.cast_root, config.build_dir]);
|
||||
return del([paths.cast_output_root, paths.build_dir]);
|
||||
})
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"clean-hassio",
|
||||
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
|
||||
return del([config.hassio_root, config.build_dir]);
|
||||
})
|
||||
);
|
||||
gulp.task("clean-hassio", function cleanOutputAndBuildDir() {
|
||||
return del([paths.hassio_output_root, paths.build_dir]);
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
"clean-gallery",
|
||||
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
|
||||
return del([config.gallery_root, config.build_dir]);
|
||||
return del([paths.gallery_output_root, paths.build_dir]);
|
||||
})
|
||||
);
|
||||
|
@@ -6,33 +6,40 @@ const merge = require("merge-stream");
|
||||
const path = require("path");
|
||||
const paths = require("../paths");
|
||||
|
||||
const zopfliOptions = { threshold: 150 };
|
||||
|
||||
gulp.task("compress-app", function compressApp() {
|
||||
const jsLatest = gulp
|
||||
.src(path.resolve(paths.output, "**/*.js"))
|
||||
.pipe(zopfli())
|
||||
.pipe(gulp.dest(paths.output));
|
||||
.src(path.resolve(paths.app_output_latest, "**/*.js"))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(gulp.dest(paths.app_output_latest));
|
||||
|
||||
const jsEs5 = gulp
|
||||
.src(path.resolve(paths.output_es5, "**/*.js"))
|
||||
.pipe(zopfli())
|
||||
.pipe(gulp.dest(paths.output_es5));
|
||||
.src(path.resolve(paths.app_output_es5, "**/*.js"))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(gulp.dest(paths.app_output_es5));
|
||||
|
||||
const polyfills = gulp
|
||||
.src(path.resolve(paths.static, "polyfills/*.js"))
|
||||
.pipe(zopfli())
|
||||
.pipe(gulp.dest(path.resolve(paths.static, "polyfills")));
|
||||
.src(path.resolve(paths.app_output_static, "polyfills/*.js"))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "polyfills")));
|
||||
|
||||
const translations = gulp
|
||||
.src(path.resolve(paths.static, "translations/*.json"))
|
||||
.pipe(zopfli())
|
||||
.pipe(gulp.dest(path.resolve(paths.static, "translations")));
|
||||
.src(path.resolve(paths.app_output_static, "translations/**/*.json"))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "translations")));
|
||||
|
||||
return merge(jsLatest, jsEs5, polyfills, translations);
|
||||
const icons = gulp
|
||||
.src(path.resolve(paths.app_output_static, "mdi/*.json"))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "mdi")));
|
||||
|
||||
return merge(jsLatest, jsEs5, polyfills, translations, icons);
|
||||
});
|
||||
|
||||
gulp.task("compress-hassio", function compressApp() {
|
||||
return gulp
|
||||
.src(path.resolve(paths.hassio_root, "**/*.js"))
|
||||
.pipe(zopfli())
|
||||
.pipe(gulp.dest(paths.hassio_root));
|
||||
.src(path.resolve(paths.hassio_output_root, "**/*.js"))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(gulp.dest(paths.hassio_output_root));
|
||||
});
|
||||
|
@@ -1,13 +1,16 @@
|
||||
// Run demo develop mode
|
||||
const gulp = require("gulp");
|
||||
|
||||
const env = require("../env");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons.js");
|
||||
require("./gen-icons-json.js");
|
||||
require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
require("./rollup.js");
|
||||
|
||||
gulp.task(
|
||||
"develop-demo",
|
||||
@@ -16,15 +19,10 @@ gulp.task(
|
||||
process.env.NODE_ENV = "development";
|
||||
},
|
||||
"clean-demo",
|
||||
gulp.parallel(
|
||||
"gen-icons-app",
|
||||
"gen-icons-mdi",
|
||||
"gen-icons-demo",
|
||||
"gen-index-demo-dev",
|
||||
"build-translations"
|
||||
),
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "gen-index-demo-dev", "build-translations"),
|
||||
"copy-static-demo",
|
||||
"webpack-dev-server-demo"
|
||||
env.useRollup() ? "rollup-dev-server-demo" : "webpack-dev-server-demo"
|
||||
)
|
||||
);
|
||||
|
||||
@@ -35,14 +33,11 @@ gulp.task(
|
||||
process.env.NODE_ENV = "production";
|
||||
},
|
||||
"clean-demo",
|
||||
gulp.parallel(
|
||||
"gen-icons-app",
|
||||
"gen-icons-mdi",
|
||||
"gen-icons-demo",
|
||||
"build-translations"
|
||||
),
|
||||
// Cast needs to be backwards compatible and older HA has no translations
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-demo",
|
||||
"webpack-prod-demo",
|
||||
env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo",
|
||||
"gen-index-demo-prod"
|
||||
)
|
||||
);
|
||||
|
@@ -1,9 +1,14 @@
|
||||
const del = require("del");
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const mapStream = require("map-stream");
|
||||
|
||||
const inDir = "translations";
|
||||
const downloadDir = inDir + "/downloads";
|
||||
const inDirFrontend = "translations/frontend";
|
||||
const inDirBackend = "translations/backend";
|
||||
const downloadDir = "translations/downloads";
|
||||
const srcMeta = "src/translations/translationMetadata.json";
|
||||
|
||||
const encoding = "utf8";
|
||||
|
||||
const tasks = [];
|
||||
|
||||
@@ -12,7 +17,7 @@ function hasHtml(data) {
|
||||
}
|
||||
|
||||
function recursiveCheckHasHtml(file, data, errors, recKey) {
|
||||
Object.keys(data).forEach(function(key) {
|
||||
Object.keys(data).forEach(function (key) {
|
||||
if (typeof data[key] === "object") {
|
||||
const nextRecKey = recKey ? `${recKey}.${key}` : key;
|
||||
recursiveCheckHasHtml(file, data[key], errors, nextRecKey);
|
||||
@@ -25,7 +30,7 @@ function recursiveCheckHasHtml(file, data, errors, recKey) {
|
||||
function checkHtml() {
|
||||
const errors = [];
|
||||
|
||||
return mapStream(function(file, cb) {
|
||||
return mapStream(function (file, cb) {
|
||||
const content = file.contents;
|
||||
let error;
|
||||
if (content) {
|
||||
@@ -42,20 +47,36 @@ function checkHtml() {
|
||||
}
|
||||
|
||||
let taskName = "clean-downloaded-translations";
|
||||
gulp.task(taskName, function() {
|
||||
gulp.task(taskName, function () {
|
||||
return del([`${downloadDir}/**`]);
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
taskName = "check-translations-html";
|
||||
gulp.task(taskName, function() {
|
||||
gulp.task(taskName, function () {
|
||||
return gulp.src(`${downloadDir}/*.json`).pipe(checkHtml());
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
taskName = "check-all-files-exist";
|
||||
gulp.task(taskName, function () {
|
||||
const file = fs.readFileSync(srcMeta, { encoding });
|
||||
const meta = JSON.parse(file);
|
||||
Object.keys(meta).forEach((lang) => {
|
||||
if (!fs.existsSync(`${inDirFrontend}/${lang}.json`)) {
|
||||
fs.writeFileSync(`${inDirFrontend}/${lang}.json`, JSON.stringify({}));
|
||||
}
|
||||
if (!fs.existsSync(`${inDirBackend}/${lang}.json`)) {
|
||||
fs.writeFileSync(`${inDirBackend}/${lang}.json`, JSON.stringify({}));
|
||||
}
|
||||
});
|
||||
return Promise.resolve();
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
taskName = "move-downloaded-translations";
|
||||
gulp.task(taskName, function() {
|
||||
return gulp.src(`${downloadDir}/*.json`).pipe(gulp.dest(inDir));
|
||||
gulp.task(taskName, function () {
|
||||
return gulp.src(`${downloadDir}/*.json`).pipe(gulp.dest(inDirFrontend));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
@@ -65,6 +86,7 @@ gulp.task(
|
||||
gulp.series(
|
||||
"check-translations-html",
|
||||
"move-downloaded-translations",
|
||||
"check-all-files-exist",
|
||||
"clean-downloaded-translations"
|
||||
)
|
||||
);
|
||||
|
@@ -6,31 +6,36 @@ const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const template = require("lodash.template");
|
||||
const minify = require("html-minifier").minify;
|
||||
const config = require("../paths.js");
|
||||
const paths = require("../paths.js");
|
||||
const env = require("../env.js");
|
||||
|
||||
const templatePath = (tpl) =>
|
||||
path.resolve(config.polymer_dir, "src/html/", `${tpl}.html.template`);
|
||||
path.resolve(paths.polymer_dir, "src/html/", `${tpl}.html.template`);
|
||||
|
||||
const readFile = (pth) => fs.readFileSync(pth).toString();
|
||||
|
||||
const renderTemplate = (pth, data = {}, pathFunc = templatePath) => {
|
||||
const compiled = template(readFile(pathFunc(pth)));
|
||||
return compiled({ ...data, renderTemplate });
|
||||
return compiled({
|
||||
...data,
|
||||
useRollup: env.useRollup(),
|
||||
renderTemplate,
|
||||
});
|
||||
};
|
||||
|
||||
const renderDemoTemplate = (pth, data = {}) =>
|
||||
renderTemplate(pth, data, (tpl) =>
|
||||
path.resolve(config.demo_dir, "src/html/", `${tpl}.html.template`)
|
||||
path.resolve(paths.demo_dir, "src/html/", `${tpl}.html.template`)
|
||||
);
|
||||
|
||||
const renderCastTemplate = (pth, data = {}) =>
|
||||
renderTemplate(pth, data, (tpl) =>
|
||||
path.resolve(config.cast_dir, "src/html/", `${tpl}.html.template`)
|
||||
path.resolve(paths.cast_dir, "src/html/", `${tpl}.html.template`)
|
||||
);
|
||||
|
||||
const renderGalleryTemplate = (pth, data = {}) =>
|
||||
renderTemplate(pth, data, (tpl) =>
|
||||
path.resolve(config.gallery_dir, "src/html/", `${tpl}.html.template`)
|
||||
path.resolve(paths.gallery_dir, "src/html/", `${tpl}.html.template`)
|
||||
);
|
||||
|
||||
const minifyHtml = (content) =>
|
||||
@@ -47,34 +52,37 @@ gulp.task("gen-pages-dev", (done) => {
|
||||
for (const page of PAGES) {
|
||||
const content = renderTemplate(page, {
|
||||
latestPageJS: `/frontend_latest/${page}.js`,
|
||||
latestHassIconsJS: "/frontend_latest/hass-icons.js",
|
||||
|
||||
es5Compatibility: "/frontend_es5/compatibility.js",
|
||||
es5PageJS: `/frontend_es5/${page}.js`,
|
||||
es5HassIconsJS: "/frontend_es5/hass-icons.js",
|
||||
});
|
||||
|
||||
fs.outputFileSync(path.resolve(config.root, `${page}.html`), content);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.app_output_root, `${page}.html`),
|
||||
content
|
||||
);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-pages-prod", (done) => {
|
||||
const latestManifest = require(path.resolve(config.output, "manifest.json"));
|
||||
const es5Manifest = require(path.resolve(config.output_es5, "manifest.json"));
|
||||
const latestManifest = require(path.resolve(
|
||||
paths.app_output_latest,
|
||||
"manifest.json"
|
||||
));
|
||||
const es5Manifest = require(path.resolve(
|
||||
paths.app_output_es5,
|
||||
"manifest.json"
|
||||
));
|
||||
|
||||
for (const page of PAGES) {
|
||||
const content = renderTemplate(page, {
|
||||
latestPageJS: latestManifest[`${page}.js`],
|
||||
latestHassIconsJS: latestManifest["hass-icons.js"],
|
||||
|
||||
es5Compatibility: es5Manifest["compatibility.js"],
|
||||
es5PageJS: es5Manifest[`${page}.js`],
|
||||
es5HassIconsJS: es5Manifest["hass-icons.js"],
|
||||
});
|
||||
|
||||
fs.outputFileSync(
|
||||
path.resolve(config.root, `${page}.html`),
|
||||
path.resolve(paths.app_output_root, `${page}.html`),
|
||||
minifyHtml(content)
|
||||
);
|
||||
}
|
||||
@@ -82,43 +90,44 @@ gulp.task("gen-pages-prod", (done) => {
|
||||
});
|
||||
|
||||
gulp.task("gen-index-app-dev", (done) => {
|
||||
// In dev mode we don't mangle names, so we hardcode urls. That way we can
|
||||
// run webpack as last in watch mode, which blocks output.
|
||||
const content = renderTemplate("index", {
|
||||
latestAppJS: "/frontend_latest/app.js",
|
||||
latestCoreJS: "/frontend_latest/core.js",
|
||||
latestCustomPanelJS: "/frontend_latest/custom-panel.js",
|
||||
latestHassIconsJS: "/frontend_latest/hass-icons.js",
|
||||
|
||||
es5Compatibility: "/frontend_es5/compatibility.js",
|
||||
es5AppJS: "/frontend_es5/app.js",
|
||||
es5CoreJS: "/frontend_es5/core.js",
|
||||
es5CustomPanelJS: "/frontend_es5/custom-panel.js",
|
||||
es5HassIconsJS: "/frontend_es5/hass-icons.js",
|
||||
}).replace(/#THEMEC/g, "{{ theme_color }}");
|
||||
|
||||
fs.outputFileSync(path.resolve(config.root, "index.html"), content);
|
||||
fs.outputFileSync(path.resolve(paths.app_output_root, "index.html"), content);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-index-app-prod", (done) => {
|
||||
const latestManifest = require(path.resolve(config.output, "manifest.json"));
|
||||
const es5Manifest = require(path.resolve(config.output_es5, "manifest.json"));
|
||||
const latestManifest = require(path.resolve(
|
||||
paths.app_output_latest,
|
||||
"manifest.json"
|
||||
));
|
||||
const es5Manifest = require(path.resolve(
|
||||
paths.app_output_es5,
|
||||
"manifest.json"
|
||||
));
|
||||
const content = renderTemplate("index", {
|
||||
latestAppJS: latestManifest["app.js"],
|
||||
latestCoreJS: latestManifest["core.js"],
|
||||
latestCustomPanelJS: latestManifest["custom-panel.js"],
|
||||
latestHassIconsJS: latestManifest["hass-icons.js"],
|
||||
|
||||
es5Compatibility: es5Manifest["compatibility.js"],
|
||||
es5AppJS: es5Manifest["app.js"],
|
||||
es5CoreJS: es5Manifest["core.js"],
|
||||
es5CustomPanelJS: es5Manifest["custom-panel.js"],
|
||||
es5HassIconsJS: es5Manifest["hass-icons.js"],
|
||||
});
|
||||
const minified = minifyHtml(content).replace(/#THEMEC/g, "{{ theme_color }}");
|
||||
|
||||
fs.outputFileSync(path.resolve(config.root, "index.html"), minified);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.app_output_root, "index.html"),
|
||||
minified
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -127,7 +136,7 @@ gulp.task("gen-index-cast-dev", (done) => {
|
||||
latestReceiverJS: "/frontend_latest/receiver.js",
|
||||
});
|
||||
fs.outputFileSync(
|
||||
path.resolve(config.cast_root, "receiver.html"),
|
||||
path.resolve(paths.cast_output_root, "receiver.html"),
|
||||
contentReceiver
|
||||
);
|
||||
|
||||
@@ -135,14 +144,17 @@ gulp.task("gen-index-cast-dev", (done) => {
|
||||
latestLauncherJS: "/frontend_latest/launcher.js",
|
||||
es5LauncherJS: "/frontend_es5/launcher.js",
|
||||
});
|
||||
fs.outputFileSync(path.resolve(config.cast_root, "faq.html"), contentFAQ);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.cast_output_root, "faq.html"),
|
||||
contentFAQ
|
||||
);
|
||||
|
||||
const contentLauncher = renderCastTemplate("launcher", {
|
||||
latestLauncherJS: "/frontend_latest/launcher.js",
|
||||
es5LauncherJS: "/frontend_es5/launcher.js",
|
||||
});
|
||||
fs.outputFileSync(
|
||||
path.resolve(config.cast_root, "index.html"),
|
||||
path.resolve(paths.cast_output_root, "index.html"),
|
||||
contentLauncher
|
||||
);
|
||||
done();
|
||||
@@ -150,11 +162,11 @@ gulp.task("gen-index-cast-dev", (done) => {
|
||||
|
||||
gulp.task("gen-index-cast-prod", (done) => {
|
||||
const latestManifest = require(path.resolve(
|
||||
config.cast_output,
|
||||
paths.cast_output_latest,
|
||||
"manifest.json"
|
||||
));
|
||||
const es5Manifest = require(path.resolve(
|
||||
config.cast_output_es5,
|
||||
paths.cast_output_es5,
|
||||
"manifest.json"
|
||||
));
|
||||
|
||||
@@ -162,7 +174,7 @@ gulp.task("gen-index-cast-prod", (done) => {
|
||||
latestReceiverJS: latestManifest["receiver.js"],
|
||||
});
|
||||
fs.outputFileSync(
|
||||
path.resolve(config.cast_root, "receiver.html"),
|
||||
path.resolve(paths.cast_output_root, "receiver.html"),
|
||||
contentReceiver
|
||||
);
|
||||
|
||||
@@ -170,68 +182,74 @@ gulp.task("gen-index-cast-prod", (done) => {
|
||||
latestLauncherJS: latestManifest["launcher.js"],
|
||||
es5LauncherJS: es5Manifest["launcher.js"],
|
||||
});
|
||||
fs.outputFileSync(path.resolve(config.cast_root, "faq.html"), contentFAQ);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.cast_output_root, "faq.html"),
|
||||
contentFAQ
|
||||
);
|
||||
|
||||
const contentLauncher = renderCastTemplate("launcher", {
|
||||
latestLauncherJS: latestManifest["launcher.js"],
|
||||
es5LauncherJS: es5Manifest["launcher.js"],
|
||||
});
|
||||
fs.outputFileSync(
|
||||
path.resolve(config.cast_root, "index.html"),
|
||||
path.resolve(paths.cast_output_root, "index.html"),
|
||||
contentLauncher
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-index-demo-dev", (done) => {
|
||||
// In dev mode we don't mangle names, so we hardcode urls. That way we can
|
||||
// run webpack as last in watch mode, which blocks output.
|
||||
const content = renderDemoTemplate("index", {
|
||||
latestDemoJS: "/frontend_latest/main.js",
|
||||
|
||||
es5Compatibility: "/frontend_es5/compatibility.js",
|
||||
es5DemoJS: "/frontend_es5/main.js",
|
||||
});
|
||||
|
||||
fs.outputFileSync(path.resolve(config.demo_root, "index.html"), content);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.demo_output_root, "index.html"),
|
||||
content
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-index-demo-prod", (done) => {
|
||||
const latestManifest = require(path.resolve(
|
||||
config.demo_output,
|
||||
paths.demo_output_latest,
|
||||
"manifest.json"
|
||||
));
|
||||
const es5Manifest = require(path.resolve(
|
||||
config.demo_output_es5,
|
||||
paths.demo_output_es5,
|
||||
"manifest.json"
|
||||
));
|
||||
const content = renderDemoTemplate("index", {
|
||||
latestDemoJS: latestManifest["main.js"],
|
||||
|
||||
es5Compatibility: es5Manifest["compatibility.js"],
|
||||
es5DemoJS: es5Manifest["main.js"],
|
||||
});
|
||||
const minified = minifyHtml(content);
|
||||
|
||||
fs.outputFileSync(path.resolve(config.demo_root, "index.html"), minified);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.demo_output_root, "index.html"),
|
||||
minified
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-index-gallery-dev", (done) => {
|
||||
// In dev mode we don't mangle names, so we hardcode urls. That way we can
|
||||
// run webpack as last in watch mode, which blocks output.
|
||||
const content = renderGalleryTemplate("index", {
|
||||
latestGalleryJS: "./entrypoint.js",
|
||||
latestGalleryJS: "./frontend_latest/entrypoint.js",
|
||||
});
|
||||
|
||||
fs.outputFileSync(path.resolve(config.gallery_root, "index.html"), content);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.gallery_output_root, "index.html"),
|
||||
content
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-index-gallery-prod", (done) => {
|
||||
const latestManifest = require(path.resolve(
|
||||
config.gallery_output,
|
||||
paths.gallery_output_latest,
|
||||
"manifest.json"
|
||||
));
|
||||
const content = renderGalleryTemplate("index", {
|
||||
@@ -239,6 +257,48 @@ gulp.task("gen-index-gallery-prod", (done) => {
|
||||
});
|
||||
const minified = minifyHtml(content);
|
||||
|
||||
fs.outputFileSync(path.resolve(config.gallery_root, "index.html"), minified);
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.gallery_output_root, "index.html"),
|
||||
minified
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-index-hassio-dev", async () => {
|
||||
writeHassioEntrypoint(
|
||||
`${paths.hassio_publicPath}/frontend_latest/entrypoint.js`,
|
||||
`${paths.hassio_publicPath}/frontend_es5/entrypoint.js`
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task("gen-index-hassio-prod", async () => {
|
||||
const latestManifest = require(path.resolve(
|
||||
paths.hassio_output_latest,
|
||||
"manifest.json"
|
||||
));
|
||||
const es5Manifest = require(path.resolve(
|
||||
paths.hassio_output_es5,
|
||||
"manifest.json"
|
||||
));
|
||||
writeHassioEntrypoint(
|
||||
latestManifest["entrypoint.js"],
|
||||
es5Manifest["entrypoint.js"]
|
||||
);
|
||||
});
|
||||
|
||||
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
|
||||
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.resolve(paths.hassio_output_root, "entrypoint.js"),
|
||||
`
|
||||
try {
|
||||
new Function("import('${latestEntrypoint}')")();
|
||||
} catch (err) {
|
||||
var el = document.createElement('script');
|
||||
el.src = '${es5Entrypoint}';
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
`,
|
||||
{ encoding: "utf-8" }
|
||||
);
|
||||
}
|
||||
|
@@ -1,13 +1,44 @@
|
||||
// Run demo develop mode
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const env = require("../env");
|
||||
const paths = require("../paths");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons.js");
|
||||
require("./gen-icons-json.js");
|
||||
require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
require("./rollup.js");
|
||||
|
||||
gulp.task("gather-gallery-demos", async function gatherDemos() {
|
||||
const files = await fs.promises.readdir(
|
||||
path.resolve(paths.gallery_dir, "src/demos")
|
||||
);
|
||||
|
||||
let content = "export const DEMOS = {\n";
|
||||
|
||||
for (const file of files) {
|
||||
const demoId = path.basename(file, ".ts");
|
||||
const demoPath = "../src/demos/" + demoId;
|
||||
content += ` "${demoId}": () => import("${demoPath}"),\n`;
|
||||
}
|
||||
|
||||
content += "};";
|
||||
|
||||
const galleryBuild = path.resolve(paths.gallery_dir, "build");
|
||||
|
||||
fs.mkdirSync(galleryBuild, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.resolve(galleryBuild, "import-demos.ts"),
|
||||
content,
|
||||
"utf-8"
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
"develop-gallery",
|
||||
@@ -16,10 +47,15 @@ gulp.task(
|
||||
process.env.NODE_ENV = "development";
|
||||
},
|
||||
"clean-gallery",
|
||||
gulp.parallel("gen-icons-app", "gen-icons-app", "build-translations"),
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel(
|
||||
"gen-icons-json",
|
||||
"build-translations",
|
||||
"gather-gallery-demos"
|
||||
),
|
||||
"copy-static-gallery",
|
||||
"gen-index-gallery-dev",
|
||||
"webpack-dev-server-gallery"
|
||||
env.useRollup() ? "rollup-dev-server-gallery" : "webpack-dev-server-gallery"
|
||||
)
|
||||
);
|
||||
|
||||
@@ -30,9 +66,14 @@ gulp.task(
|
||||
process.env.NODE_ENV = "production";
|
||||
},
|
||||
"clean-gallery",
|
||||
gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"),
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel(
|
||||
"gen-icons-json",
|
||||
"build-translations",
|
||||
"gather-gallery-demos"
|
||||
),
|
||||
"copy-static-gallery",
|
||||
"webpack-prod-gallery",
|
||||
env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery",
|
||||
"gen-index-gallery-prod"
|
||||
)
|
||||
);
|
||||
|
@@ -26,14 +26,23 @@ function copyTranslations(staticDir) {
|
||||
);
|
||||
}
|
||||
|
||||
function copyMdiIcons(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
|
||||
// MDI icons output
|
||||
fs.copySync(polyPath("build/mdi"), staticPath("mdi"));
|
||||
}
|
||||
|
||||
function copyPolyfills(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
|
||||
// Web Component polyfills and adapters
|
||||
// For custom panels using ES5 builds that don't use Babel 7+
|
||||
copyFileDir(
|
||||
npmPath("@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"),
|
||||
staticPath("polyfills/")
|
||||
);
|
||||
|
||||
// Web Component polyfills and adapters
|
||||
copyFileDir(
|
||||
npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js"),
|
||||
staticPath("polyfills/")
|
||||
@@ -44,6 +53,12 @@ function copyPolyfills(staticDir) {
|
||||
);
|
||||
}
|
||||
|
||||
function copyLoaderJS(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
copyFileDir(npmPath("systemjs/dist/s.min.js"), staticPath("js"));
|
||||
copyFileDir(npmPath("systemjs/dist/s.min.js.map"), staticPath("js"));
|
||||
}
|
||||
|
||||
function copyFonts(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
// Local fonts
|
||||
@@ -65,61 +80,68 @@ function copyMapPanel(staticDir) {
|
||||
);
|
||||
}
|
||||
|
||||
gulp.task("copy-static", (done) => {
|
||||
const staticDir = paths.static;
|
||||
const staticPath = genStaticPath(paths.static);
|
||||
// Basic static files
|
||||
fs.copySync(polyPath("public"), paths.root);
|
||||
gulp.task("copy-translations-app", async () => {
|
||||
const staticDir = paths.app_output_static;
|
||||
copyTranslations(staticDir);
|
||||
});
|
||||
|
||||
gulp.task("copy-static-app", async () => {
|
||||
const staticDir = paths.app_output_static;
|
||||
// Basic static files
|
||||
fs.copySync(polyPath("public"), paths.app_output_root);
|
||||
|
||||
copyLoaderJS(staticDir);
|
||||
copyPolyfills(staticDir);
|
||||
copyFonts(staticDir);
|
||||
copyTranslations(staticDir);
|
||||
copyMdiIcons(staticDir);
|
||||
|
||||
// Panel assets
|
||||
copyFileDir(
|
||||
npmPath("react-big-calendar/lib/css/react-big-calendar.css"),
|
||||
staticPath("panels/calendar/")
|
||||
);
|
||||
copyMapPanel(staticDir);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("copy-static-demo", (done) => {
|
||||
gulp.task("copy-static-demo", async () => {
|
||||
// Copy app static files
|
||||
fs.copySync(
|
||||
polyPath("public/static"),
|
||||
path.resolve(paths.demo_root, "static")
|
||||
path.resolve(paths.demo_output_root, "static")
|
||||
);
|
||||
// Copy demo static files
|
||||
fs.copySync(path.resolve(paths.demo_dir, "public"), paths.demo_root);
|
||||
fs.copySync(path.resolve(paths.demo_dir, "public"), paths.demo_output_root);
|
||||
|
||||
copyPolyfills(paths.demo_static);
|
||||
copyMapPanel(paths.demo_static);
|
||||
copyFonts(paths.demo_static);
|
||||
copyTranslations(paths.demo_static);
|
||||
done();
|
||||
copyLoaderJS(paths.demo_output_static);
|
||||
copyPolyfills(paths.demo_output_static);
|
||||
copyMapPanel(paths.demo_output_static);
|
||||
copyFonts(paths.demo_output_static);
|
||||
copyTranslations(paths.demo_output_static);
|
||||
copyMdiIcons(paths.demo_output_static);
|
||||
});
|
||||
|
||||
gulp.task("copy-static-cast", (done) => {
|
||||
gulp.task("copy-static-cast", async () => {
|
||||
// Copy app static files
|
||||
fs.copySync(polyPath("public/static"), paths.cast_static);
|
||||
fs.copySync(polyPath("public/static"), paths.cast_output_static);
|
||||
// Copy cast static files
|
||||
fs.copySync(path.resolve(paths.cast_dir, "public"), paths.cast_root);
|
||||
fs.copySync(path.resolve(paths.cast_dir, "public"), paths.cast_output_root);
|
||||
|
||||
copyMapPanel(paths.cast_static);
|
||||
copyFonts(paths.cast_static);
|
||||
copyTranslations(paths.cast_static);
|
||||
done();
|
||||
copyLoaderJS(paths.cast_output_static);
|
||||
copyPolyfills(paths.cast_output_static);
|
||||
copyMapPanel(paths.cast_output_static);
|
||||
copyFonts(paths.cast_output_static);
|
||||
copyTranslations(paths.cast_output_static);
|
||||
copyMdiIcons(paths.cast_output_static);
|
||||
});
|
||||
|
||||
gulp.task("copy-static-gallery", (done) => {
|
||||
gulp.task("copy-static-gallery", async () => {
|
||||
// Copy app static files
|
||||
fs.copySync(polyPath("public/static"), paths.gallery_static);
|
||||
fs.copySync(polyPath("public/static"), paths.gallery_output_static);
|
||||
// Copy gallery static files
|
||||
fs.copySync(path.resolve(paths.gallery_dir, "public"), paths.gallery_root);
|
||||
fs.copySync(
|
||||
path.resolve(paths.gallery_dir, "public"),
|
||||
paths.gallery_output_root
|
||||
);
|
||||
|
||||
copyMapPanel(paths.gallery_static);
|
||||
copyFonts(paths.gallery_static);
|
||||
copyTranslations(paths.gallery_static);
|
||||
done();
|
||||
copyMapPanel(paths.gallery_output_static);
|
||||
copyFonts(paths.gallery_output_static);
|
||||
copyTranslations(paths.gallery_output_static);
|
||||
copyMdiIcons(paths.gallery_output_static);
|
||||
});
|
||||
|
120
build-scripts/gulp/gen-icons-json.js
Normal file
@@ -0,0 +1,120 @@
|
||||
const gulp = require("gulp");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const hash = require("object-hash");
|
||||
|
||||
const ICON_PACKAGE_PATH = path.resolve(
|
||||
__dirname,
|
||||
"../../node_modules/@mdi/svg/"
|
||||
);
|
||||
const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
|
||||
const PACKAGE_PATH = path.resolve(ICON_PACKAGE_PATH, "package.json");
|
||||
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg");
|
||||
const OUTPUT_DIR = path.resolve(__dirname, "../../build/mdi");
|
||||
const REMOVED_ICONS_PATH = path.resolve(__dirname, "../removedIcons.json");
|
||||
|
||||
const encoding = "utf8";
|
||||
|
||||
const getMeta = () => {
|
||||
const file = fs.readFileSync(META_PATH, { encoding });
|
||||
const meta = JSON.parse(file);
|
||||
return meta.map((icon) => {
|
||||
const svg = fs.readFileSync(`${ICON_PATH}/${icon.name}.svg`, {
|
||||
encoding,
|
||||
});
|
||||
return { path: svg.match(/ d="([^"]+)"/)[1], name: icon.name };
|
||||
});
|
||||
};
|
||||
|
||||
const addRemovedMeta = (meta) => {
|
||||
const file = fs.readFileSync(REMOVED_ICONS_PATH, { encoding });
|
||||
const removed = JSON.parse(file);
|
||||
const combinedMeta = [...meta, ...removed];
|
||||
return combinedMeta.sort((a, b) => a.name.localeCompare(b.name));
|
||||
};
|
||||
|
||||
const splitBySize = (meta) => {
|
||||
const chunks = [];
|
||||
const CHUNK_SIZE = 50000;
|
||||
|
||||
let curSize = 0;
|
||||
let startKey;
|
||||
let icons = [];
|
||||
|
||||
Object.values(meta).forEach((icon) => {
|
||||
if (startKey === undefined) {
|
||||
startKey = icon.name;
|
||||
}
|
||||
curSize += icon.path.length;
|
||||
icons.push(icon);
|
||||
if (curSize > CHUNK_SIZE) {
|
||||
chunks.push({
|
||||
startKey,
|
||||
endKey: icon.name,
|
||||
icons,
|
||||
});
|
||||
curSize = 0;
|
||||
startKey = undefined;
|
||||
icons = [];
|
||||
}
|
||||
});
|
||||
|
||||
chunks.push({
|
||||
startKey,
|
||||
icons,
|
||||
});
|
||||
|
||||
return chunks;
|
||||
};
|
||||
|
||||
const findDifferentiator = (curString, prevString) => {
|
||||
for (let i = 0; i < curString.length; i++) {
|
||||
if (curString[i] !== prevString[i]) {
|
||||
return curString.substring(0, i + 1);
|
||||
}
|
||||
}
|
||||
throw new Error("Cannot find differentiator", curString, prevString);
|
||||
};
|
||||
|
||||
gulp.task("gen-icons-json", (done) => {
|
||||
const meta = addRemovedMeta(getMeta());
|
||||
const split = splitBySize(meta);
|
||||
|
||||
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
}
|
||||
const parts = [];
|
||||
|
||||
let lastEnd;
|
||||
split.forEach((chunk) => {
|
||||
let startKey;
|
||||
if (lastEnd === undefined) {
|
||||
chunk.startKey = undefined;
|
||||
startKey = undefined;
|
||||
} else {
|
||||
startKey = findDifferentiator(chunk.startKey, lastEnd);
|
||||
}
|
||||
lastEnd = chunk.endKey;
|
||||
|
||||
const output = {};
|
||||
chunk.icons.forEach((icon) => {
|
||||
output[icon.name] = icon.path;
|
||||
});
|
||||
const filename = hash(output);
|
||||
parts.push({ start: startKey, file: filename });
|
||||
fs.writeFileSync(
|
||||
path.resolve(OUTPUT_DIR, `${filename}.json`),
|
||||
JSON.stringify(output)
|
||||
);
|
||||
});
|
||||
|
||||
const file = fs.readFileSync(PACKAGE_PATH, { encoding });
|
||||
const package = JSON.parse(file);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.resolve(OUTPUT_DIR, "iconMetadata.json"),
|
||||
JSON.stringify({ version: package.version, parts })
|
||||
);
|
||||
|
||||
done();
|
||||
});
|
@@ -1,140 +0,0 @@
|
||||
const gulp = require("gulp");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const paths = require("../paths");
|
||||
|
||||
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
|
||||
"tooltip-account", // Map
|
||||
"cart", // Shopping List
|
||||
"hammer", // developer-tools
|
||||
];
|
||||
|
||||
// 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 pth = xml.substr(start, end);
|
||||
return `<g id="${name}">${pth}</g>`;
|
||||
}
|
||||
|
||||
// Given an iconset name and icon names, generate a polymer iconset
|
||||
function generateIconset(iconsetName, iconNames) {
|
||||
const iconDefs = Array.from(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="${iconsetName}" size="24"><svg><defs>${iconDefs}</defs></svg></ha-iconset-svg>`;
|
||||
}
|
||||
|
||||
// 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(searchPath, 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(searchPath, ".js", processFile);
|
||||
mapFiles(searchPath, ".ts", processFile);
|
||||
return icons;
|
||||
}
|
||||
|
||||
gulp.task("gen-icons-mdi", (done) => {
|
||||
const meta = JSON.parse(
|
||||
fs.readFileSync(path.resolve(ICON_PACKAGE_PATH, META_PATH), "UTF-8")
|
||||
);
|
||||
const iconNames = meta.map((iconInfo) => iconInfo.name);
|
||||
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||
fs.mkdirSync(OUTPUT_DIR);
|
||||
}
|
||||
fs.writeFileSync(MDI_OUTPUT_PATH, generateIconset("mdi", iconNames));
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-icons-app", (done) => {
|
||||
const iconNames = findIcons("./src", "hass");
|
||||
BUILT_IN_PANEL_ICONS.forEach((name) => iconNames.add(name));
|
||||
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||
fs.mkdirSync(OUTPUT_DIR);
|
||||
}
|
||||
fs.writeFileSync(HASS_OUTPUT_PATH, generateIconset("hass", iconNames));
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-icons-demo", (done) => {
|
||||
const iconNames = findIcons(path.resolve(paths.demo_dir, "./src"), "hademo");
|
||||
fs.writeFileSync(
|
||||
path.resolve(paths.demo_dir, "hademo-icons.html"),
|
||||
generateIconset("hademo", iconNames)
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-icons-hassio", (done) => {
|
||||
const iconNames = findIcons(
|
||||
path.resolve(paths.hassio_dir, "./src"),
|
||||
"hassio"
|
||||
);
|
||||
// Find hassio icons inside HA main repo.
|
||||
for (const item of findIcons(
|
||||
path.resolve(paths.polymer_dir, "./src"),
|
||||
"hassio"
|
||||
)) {
|
||||
iconNames.add(item);
|
||||
}
|
||||
fs.writeFileSync(
|
||||
path.resolve(paths.hassio_dir, "hassio-icons.html"),
|
||||
generateIconset("hassio", iconNames)
|
||||
);
|
||||
done();
|
||||
});
|
@@ -1,11 +1,15 @@
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const envVars = require("../env");
|
||||
const env = require("../env");
|
||||
const paths = require("../paths");
|
||||
|
||||
require("./clean.js");
|
||||
require("./gen-icons.js");
|
||||
require("./gen-icons-json.js");
|
||||
require("./webpack.js");
|
||||
require("./compress.js");
|
||||
require("./rollup.js");
|
||||
|
||||
gulp.task(
|
||||
"develop-hassio",
|
||||
@@ -14,8 +18,9 @@ gulp.task(
|
||||
process.env.NODE_ENV = "development";
|
||||
},
|
||||
"clean-hassio",
|
||||
gulp.parallel("gen-icons-hassio", "gen-icons-mdi"),
|
||||
"webpack-watch-hassio"
|
||||
"gen-icons-json",
|
||||
"gen-index-hassio-dev",
|
||||
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
|
||||
)
|
||||
);
|
||||
|
||||
@@ -26,9 +31,10 @@ gulp.task(
|
||||
process.env.NODE_ENV = "production";
|
||||
},
|
||||
"clean-hassio",
|
||||
gulp.parallel("gen-icons-hassio", "gen-icons-mdi"),
|
||||
"webpack-prod-hassio",
|
||||
"gen-icons-json",
|
||||
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
|
||||
"gen-index-hassio-prod",
|
||||
...// Don't compress running tests
|
||||
(envVars.isTravis ? [] : ["compress-hassio"])
|
||||
(env.isTest() ? [] : ["compress-hassio"])
|
||||
)
|
||||
);
|
||||
|
146
build-scripts/gulp/rollup.js
Normal file
@@ -0,0 +1,146 @@
|
||||
// Tasks to run Rollup
|
||||
const path = require("path");
|
||||
const gulp = require("gulp");
|
||||
const rollup = require("rollup");
|
||||
const handler = require("serve-handler");
|
||||
const http = require("http");
|
||||
const log = require("fancy-log");
|
||||
const rollupConfig = require("../rollup");
|
||||
const paths = require("../paths");
|
||||
const open = require("open");
|
||||
|
||||
const bothBuilds = (createConfigFunc, params) =>
|
||||
gulp.series(
|
||||
async function buildLatest() {
|
||||
await buildRollup(
|
||||
createConfigFunc({
|
||||
...params,
|
||||
latestBuild: true,
|
||||
})
|
||||
);
|
||||
},
|
||||
async function buildES5() {
|
||||
await buildRollup(
|
||||
createConfigFunc({
|
||||
...params,
|
||||
latestBuild: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
function createServer(serveOptions) {
|
||||
const server = http.createServer((request, response) => {
|
||||
return handler(request, response, {
|
||||
public: serveOptions.root,
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(
|
||||
serveOptions.port,
|
||||
serveOptions.networkAccess ? "0.0.0.0" : undefined,
|
||||
() => {
|
||||
log.info(`Available at http://localhost:${serveOptions.port}`);
|
||||
open(`http://localhost:${serveOptions.port}`);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function watchRollup(createConfig, extraWatchSrc = [], serveOptions) {
|
||||
const { inputOptions, outputOptions } = createConfig({
|
||||
isProdBuild: false,
|
||||
latestBuild: true,
|
||||
});
|
||||
|
||||
const watcher = rollup.watch({
|
||||
...inputOptions,
|
||||
output: [outputOptions],
|
||||
watch: {
|
||||
include: ["src/**"] + extraWatchSrc,
|
||||
},
|
||||
});
|
||||
|
||||
let startedHttp = false;
|
||||
|
||||
watcher.on("event", (event) => {
|
||||
if (event.code === "BUNDLE_END") {
|
||||
log(`Build done @ ${new Date().toLocaleTimeString()}`);
|
||||
} else if (event.code === "ERROR") {
|
||||
log.error(event.error);
|
||||
} else if (event.code === "END") {
|
||||
if (startedHttp || !serveOptions) {
|
||||
return;
|
||||
}
|
||||
startedHttp = true;
|
||||
createServer(serveOptions);
|
||||
}
|
||||
});
|
||||
|
||||
gulp.watch(
|
||||
path.join(paths.translations_src, "en.json"),
|
||||
gulp.series("build-translations", "copy-translations-app")
|
||||
);
|
||||
}
|
||||
|
||||
async function buildRollup(config) {
|
||||
const bundle = await rollup.rollup(config.inputOptions);
|
||||
await bundle.write(config.outputOptions);
|
||||
}
|
||||
|
||||
gulp.task("rollup-watch-app", () => {
|
||||
watchRollup(rollupConfig.createAppConfig);
|
||||
});
|
||||
|
||||
gulp.task("rollup-watch-hassio", () => {
|
||||
watchRollup(rollupConfig.createHassioConfig, ["hassio/src/**"]);
|
||||
});
|
||||
|
||||
gulp.task("rollup-dev-server-demo", () => {
|
||||
watchRollup(rollupConfig.createDemoConfig, ["demo/src/**"], {
|
||||
root: paths.demo_output_root,
|
||||
port: 8090,
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task("rollup-dev-server-cast", () => {
|
||||
watchRollup(rollupConfig.createCastConfig, ["cast/src/**"], {
|
||||
root: paths.cast_output_root,
|
||||
port: 8080,
|
||||
networkAccess: true,
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task("rollup-dev-server-gallery", () => {
|
||||
watchRollup(rollupConfig.createGalleryConfig, ["gallery/src/**"], {
|
||||
root: paths.gallery_output_root,
|
||||
port: 8100,
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
"rollup-prod-app",
|
||||
bothBuilds(rollupConfig.createAppConfig, { isProdBuild: true })
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"rollup-prod-demo",
|
||||
bothBuilds(rollupConfig.createDemoConfig, { isProdBuild: true })
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"rollup-prod-cast",
|
||||
bothBuilds(rollupConfig.createCastConfig, { isProdBuild: true })
|
||||
);
|
||||
|
||||
gulp.task("rollup-prod-hassio", () =>
|
||||
bothBuilds(rollupConfig.createHassioConfig, { isProdBuild: true })
|
||||
);
|
||||
|
||||
gulp.task("rollup-prod-gallery", () =>
|
||||
buildRollup(
|
||||
rollupConfig.createGalleryConfig({
|
||||
isProdBuild: true,
|
||||
latestBuild: true,
|
||||
})
|
||||
)
|
||||
);
|
@@ -5,18 +5,22 @@
|
||||
const gulp = require("gulp");
|
||||
const path = require("path");
|
||||
const fs = require("fs-extra");
|
||||
const config = require("../paths.js");
|
||||
const workboxBuild = require("workbox-build");
|
||||
const sourceMapUrl = require("source-map-url");
|
||||
const paths = require("../paths.js");
|
||||
|
||||
const swPath = path.resolve(config.root, "service_worker.js");
|
||||
const swDest = path.resolve(paths.app_output_root, "service_worker.js");
|
||||
|
||||
const writeSW = (content) => fs.outputFileSync(swPath, content.trim() + "\n");
|
||||
const writeSW = (content) => fs.outputFileSync(swDest, content.trim() + "\n");
|
||||
|
||||
gulp.task("gen-service-worker-dev", (done) => {
|
||||
gulp.task("gen-service-worker-app-dev", (done) => {
|
||||
writeSW(
|
||||
`
|
||||
console.debug('Service worker disabled in development');
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
// This will activate the dev service worker,
|
||||
// removing any prod service worker the dev might have running
|
||||
self.skipWaiting();
|
||||
});
|
||||
`
|
||||
@@ -24,10 +28,69 @@ self.addEventListener('install', (event) => {
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-service-worker-prod", (done) => {
|
||||
fs.copySync(
|
||||
path.resolve(config.output, "service_worker.js"),
|
||||
path.resolve(config.root, "service_worker.js")
|
||||
gulp.task("gen-service-worker-app-prod", async () => {
|
||||
// Read bundled source file
|
||||
const bundleManifestLatest = require(path.resolve(
|
||||
paths.app_output_latest,
|
||||
"manifest.json"
|
||||
));
|
||||
let serviceWorkerContent = fs.readFileSync(
|
||||
paths.app_output_root + bundleManifestLatest["service_worker.js"],
|
||||
"utf-8"
|
||||
);
|
||||
done();
|
||||
|
||||
// Delete old file from frontend_latest so manifest won't pick it up
|
||||
fs.removeSync(
|
||||
paths.app_output_root + bundleManifestLatest["service_worker.js"]
|
||||
);
|
||||
fs.removeSync(
|
||||
paths.app_output_root + bundleManifestLatest["service_worker.js.map"]
|
||||
);
|
||||
|
||||
// Remove ES5
|
||||
const bundleManifestES5 = require(path.resolve(
|
||||
paths.app_output_es5,
|
||||
"manifest.json"
|
||||
));
|
||||
fs.removeSync(paths.app_output_root + bundleManifestES5["service_worker.js"]);
|
||||
fs.removeSync(
|
||||
paths.app_output_root + bundleManifestES5["service_worker.js.map"]
|
||||
);
|
||||
|
||||
const workboxManifest = await workboxBuild.getManifest({
|
||||
// Files that mach this pattern will be considered unique and skip revision check
|
||||
// ignore JS files + translation files
|
||||
dontCacheBustURLsMatching: /(frontend_latest\/.+|static\/translations\/.+)/,
|
||||
|
||||
globDirectory: paths.app_output_root,
|
||||
globPatterns: [
|
||||
"frontend_latest/*.js",
|
||||
// Cache all English translations because we catch them as fallback
|
||||
// Using pattern to match hash instead of * to avoid caching en-GB
|
||||
// 'v' added as valid hash letter because in dev we hash with 'dev'
|
||||
"static/translations/**/en-+([a-fv0-9]).json",
|
||||
// Icon shown on splash screen
|
||||
"static/icons/favicon-192x192.png",
|
||||
"static/icons/favicon.ico",
|
||||
// Common fonts
|
||||
"static/fonts/roboto/Roboto-Light.woff2",
|
||||
"static/fonts/roboto/Roboto-Medium.woff2",
|
||||
"static/fonts/roboto/Roboto-Regular.woff2",
|
||||
"static/fonts/roboto/Roboto-Bold.woff2",
|
||||
],
|
||||
});
|
||||
|
||||
for (const warning of workboxManifest.warnings) {
|
||||
console.warn(warning);
|
||||
}
|
||||
|
||||
// remove source map and add WB manifest
|
||||
serviceWorkerContent = sourceMapUrl.removeFrom(serviceWorkerContent);
|
||||
serviceWorkerContent = serviceWorkerContent.replace(
|
||||
"WB_MANIFEST",
|
||||
JSON.stringify(workboxManifest.manifestEntries)
|
||||
);
|
||||
|
||||
// Write new file to root
|
||||
fs.writeFileSync(swDest, serviceWorkerContent);
|
||||
});
|
||||
|
@@ -1,22 +1,33 @@
|
||||
const crypto = require("crypto");
|
||||
const del = require("del");
|
||||
const path = require("path");
|
||||
const source = require("vinyl-source-stream");
|
||||
const vinylBuffer = require("vinyl-buffer");
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const foreach = require("gulp-foreach");
|
||||
const hash = require("gulp-hash");
|
||||
const hashFilename = require("gulp-hash-filename");
|
||||
const merge = require("gulp-merge-json");
|
||||
const minify = require("gulp-jsonminify");
|
||||
const rename = require("gulp-rename");
|
||||
const transform = require("gulp-json-transform");
|
||||
const { mapFiles } = require("../util");
|
||||
const env = require("../env");
|
||||
const paths = require("../paths");
|
||||
|
||||
const inDir = "translations";
|
||||
const inFrontendDir = "translations/frontend";
|
||||
const inBackendDir = "translations/backend";
|
||||
const workDir = "build-translations";
|
||||
const fullDir = workDir + "/full";
|
||||
const coreDir = workDir + "/core";
|
||||
const outDir = workDir + "/output";
|
||||
let mergeBackend = false;
|
||||
|
||||
String.prototype.rsplit = function(sep, maxsplit) {
|
||||
gulp.task("translations-enable-merge-backend", (done) => {
|
||||
mergeBackend = true;
|
||||
done();
|
||||
});
|
||||
|
||||
String.prototype.rsplit = function (sep, maxsplit) {
|
||||
var split = this.split(sep);
|
||||
return maxsplit
|
||||
? [split.slice(0, -maxsplit).join(sep)].concat(split.slice(-maxsplit))
|
||||
@@ -39,11 +50,9 @@ const TRANSLATION_FRAGMENTS = [
|
||||
"developer-tools",
|
||||
];
|
||||
|
||||
const tasks = [];
|
||||
|
||||
function recursiveFlatten(prefix, data) {
|
||||
let output = {};
|
||||
Object.keys(data).forEach(function(key) {
|
||||
Object.keys(data).forEach(function (key) {
|
||||
if (typeof data[key] === "object") {
|
||||
output = {
|
||||
...output,
|
||||
@@ -105,7 +114,12 @@ function lokaliseTransform(data, original, file) {
|
||||
output[key] = lokaliseTransform(value, original, file);
|
||||
} else {
|
||||
output[key] = value.replace(re_key_reference, (match, key) => {
|
||||
const replace = key.split("::").reduce((tr, k) => tr[k], original);
|
||||
const replace = key.split("::").reduce((tr, k) => {
|
||||
if (!tr) {
|
||||
throw Error(`Invalid key placeholder ${key} in ${file.path}`);
|
||||
}
|
||||
return tr[k];
|
||||
}, original);
|
||||
if (typeof replace !== "string") {
|
||||
throw Error(`Invalid key placeholder ${key} in ${file.path}`);
|
||||
}
|
||||
@@ -116,11 +130,9 @@ function lokaliseTransform(data, original, file) {
|
||||
return output;
|
||||
}
|
||||
|
||||
let taskName = "clean-translations";
|
||||
gulp.task(taskName, function() {
|
||||
return del([`${outDir}/**/*.json`]);
|
||||
gulp.task("clean-translations", function () {
|
||||
return del([workDir]);
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
gulp.task("ensure-translations-build-dir", (done) => {
|
||||
if (!fs.existsSync(workDir)) {
|
||||
@@ -129,31 +141,25 @@ gulp.task("ensure-translations-build-dir", (done) => {
|
||||
done();
|
||||
});
|
||||
|
||||
taskName = "create-test-metadata";
|
||||
gulp.task(
|
||||
taskName,
|
||||
gulp.series("ensure-translations-build-dir", function writeTestMetaData(cb) {
|
||||
fs.writeFile(
|
||||
workDir + "/testMetadata.json",
|
||||
JSON.stringify({
|
||||
test: {
|
||||
nativeName: "Test",
|
||||
},
|
||||
}),
|
||||
cb
|
||||
);
|
||||
})
|
||||
);
|
||||
tasks.push(taskName);
|
||||
gulp.task("create-test-metadata", function (cb) {
|
||||
fs.writeFile(
|
||||
workDir + "/testMetadata.json",
|
||||
JSON.stringify({
|
||||
test: {
|
||||
nativeName: "Test",
|
||||
},
|
||||
}),
|
||||
cb
|
||||
);
|
||||
});
|
||||
|
||||
taskName = "create-test-translation";
|
||||
gulp.task(
|
||||
taskName,
|
||||
gulp.series("create-test-metadata", function() {
|
||||
"create-test-translation",
|
||||
gulp.series("create-test-metadata", function createTestTranslation() {
|
||||
return gulp
|
||||
.src("src/translations/en.json")
|
||||
.src(path.join(paths.translations_src, "en.json"))
|
||||
.pipe(
|
||||
transform(function(data, file) {
|
||||
transform(function (data, file) {
|
||||
return recursiveEmpty(data);
|
||||
})
|
||||
)
|
||||
@@ -161,7 +167,6 @@ gulp.task(
|
||||
.pipe(gulp.dest(workDir));
|
||||
})
|
||||
);
|
||||
tasks.push(taskName);
|
||||
|
||||
/**
|
||||
* This task will build a master translation file, to be used as the base for
|
||||
@@ -172,235 +177,231 @@ tasks.push(taskName);
|
||||
* project is buildable immediately after merging new translation keys, since
|
||||
* the Lokalise update to translations/en.json will not happen immediately.
|
||||
*/
|
||||
taskName = "build-master-translation";
|
||||
gulp.task(
|
||||
taskName,
|
||||
gulp.series("clean-translations", function() {
|
||||
return gulp
|
||||
.src("src/translations/en.json")
|
||||
.pipe(
|
||||
transform(function(data, file) {
|
||||
return lokaliseTransform(data, data, file);
|
||||
})
|
||||
)
|
||||
.pipe(rename("translationMaster.json"))
|
||||
.pipe(gulp.dest(workDir));
|
||||
})
|
||||
);
|
||||
tasks.push(taskName);
|
||||
gulp.task("build-master-translation", function () {
|
||||
const src = [path.join(paths.translations_src, "en.json")];
|
||||
|
||||
taskName = "build-merged-translations";
|
||||
gulp.task(
|
||||
taskName,
|
||||
gulp.series("build-master-translation", function() {
|
||||
return gulp
|
||||
.src([inDir + "/*.json", workDir + "/test.json"], { allowEmpty: true })
|
||||
.pipe(
|
||||
transform(function(data, file) {
|
||||
return lokaliseTransform(data, data, file);
|
||||
})
|
||||
)
|
||||
.pipe(
|
||||
foreach(function(stream, file) {
|
||||
// For each language generate a merged json file. It begins with the master
|
||||
// translation as a failsafe for untranslated strings, and merges all parent
|
||||
// tags into one file for each specific subtag
|
||||
//
|
||||
// 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 = [workDir + "/translationMaster.json"];
|
||||
for (let i = 1; i <= subtags.length; i++) {
|
||||
const lang = subtags.slice(0, i).join("-");
|
||||
if (lang === "test") {
|
||||
src.push(workDir + "/test.json");
|
||||
} else if (lang !== "en") {
|
||||
src.push(inDir + "/" + lang + ".json");
|
||||
if (mergeBackend) {
|
||||
src.push(path.join(inBackendDir, "en.json"));
|
||||
}
|
||||
|
||||
return gulp
|
||||
.src(src)
|
||||
.pipe(
|
||||
transform(function (data, file) {
|
||||
return lokaliseTransform(data, data, file);
|
||||
})
|
||||
)
|
||||
.pipe(
|
||||
merge({
|
||||
fileName: "translationMaster.json",
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(workDir));
|
||||
});
|
||||
|
||||
gulp.task("build-merged-translations", function () {
|
||||
return gulp
|
||||
.src([inFrontendDir + "/*.json", workDir + "/test.json"], {
|
||||
allowEmpty: true,
|
||||
})
|
||||
.pipe(
|
||||
transform(function (data, file) {
|
||||
return lokaliseTransform(data, data, file);
|
||||
})
|
||||
)
|
||||
.pipe(
|
||||
foreach(function (stream, file) {
|
||||
// For each language generate a merged json file. It begins with the master
|
||||
// translation as a failsafe for untranslated strings, and merges all parent
|
||||
// tags into one file for each specific subtag
|
||||
//
|
||||
// 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 = [workDir + "/translationMaster.json"];
|
||||
for (let i = 1; i <= subtags.length; i++) {
|
||||
const lang = subtags.slice(0, i).join("-");
|
||||
if (lang === "test") {
|
||||
src.push(workDir + "/test.json");
|
||||
} else if (lang !== "en") {
|
||||
src.push(inFrontendDir + "/" + lang + ".json");
|
||||
if (mergeBackend) {
|
||||
src.push(inBackendDir + "/" + lang + ".json");
|
||||
}
|
||||
}
|
||||
return gulp
|
||||
.src(src, { allowEmpty: true })
|
||||
.pipe(transform((data) => emptyFilter(data)))
|
||||
.pipe(
|
||||
merge({
|
||||
fileName: tr + ".json",
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(fullDir));
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
tasks.push(taskName);
|
||||
}
|
||||
return gulp
|
||||
.src(src, { allowEmpty: true })
|
||||
.pipe(transform((data) => emptyFilter(data)))
|
||||
.pipe(
|
||||
merge({
|
||||
fileName: tr + ".json",
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(fullDir));
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
var taskName;
|
||||
|
||||
const splitTasks = [];
|
||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
||||
taskName = "build-translation-fragment-" + fragment;
|
||||
gulp.task(
|
||||
taskName,
|
||||
gulp.series("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],
|
||||
},
|
||||
gulp.task(taskName, 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);
|
||||
},
|
||||
}))
|
||||
)
|
||||
.pipe(gulp.dest(workDir + "/" + fragment));
|
||||
});
|
||||
splitTasks.push(taskName);
|
||||
});
|
||||
|
||||
taskName = "build-translation-core";
|
||||
gulp.task(
|
||||
taskName,
|
||||
gulp.series("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);
|
||||
gulp.task(taskName, function () {
|
||||
// Remove the fragment translations from the core translation.
|
||||
return gulp
|
||||
.src(fullDir + "/*.json")
|
||||
.pipe(
|
||||
transform((data, file) => {
|
||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
||||
delete data.ui.panel[fragment];
|
||||
});
|
||||
return data;
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(coreDir));
|
||||
});
|
||||
|
||||
splitTasks.push(taskName);
|
||||
|
||||
taskName = "build-flattened-translations";
|
||||
gulp.task(
|
||||
taskName,
|
||||
gulp.series(...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(hashFilename())
|
||||
.pipe(
|
||||
rename((filePath) => {
|
||||
if (filePath.dirname === "core") {
|
||||
filePath.dirname = "";
|
||||
}
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(outDir));
|
||||
})
|
||||
);
|
||||
tasks.push(taskName);
|
||||
gulp.task("build-flattened-translations", 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));
|
||||
});
|
||||
|
||||
taskName = "build-translation-fingerprints";
|
||||
gulp.task(
|
||||
taskName,
|
||||
gulp.series("build-flattened-translations", function() {
|
||||
return gulp
|
||||
.src(outDir + "/**/*.json")
|
||||
.pipe(
|
||||
rename({
|
||||
extname: "",
|
||||
})
|
||||
)
|
||||
.pipe(
|
||||
hash({
|
||||
algorithm: "md5",
|
||||
hashLength: 32,
|
||||
template: "<%= name %>.json",
|
||||
})
|
||||
)
|
||||
.pipe(hash.manifest("translationFingerprints.json"))
|
||||
.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 [path, _md5] = key.rsplit("-", 1);
|
||||
// let translation = key;
|
||||
let translation = path;
|
||||
const parts = translation.split("/");
|
||||
if (parts.length === 2) {
|
||||
translation = parts[1];
|
||||
}
|
||||
if (!(translation in newData)) {
|
||||
newData[translation] = {
|
||||
fingerprints: {},
|
||||
};
|
||||
}
|
||||
newData[translation].fingerprints[path] = value;
|
||||
});
|
||||
return newData;
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(workDir));
|
||||
})
|
||||
);
|
||||
tasks.push(taskName);
|
||||
const fingerprints = {};
|
||||
|
||||
taskName = "build-translations";
|
||||
gulp.task(
|
||||
taskName,
|
||||
gulp.series("build-translation-fingerprints", function() {
|
||||
return gulp
|
||||
.src(
|
||||
[
|
||||
"src/translations/translationMetadata.json",
|
||||
workDir + "/testMetadata.json",
|
||||
workDir + "/translationFingerprints.json",
|
||||
],
|
||||
{ allowEmpty: true }
|
||||
)
|
||||
.pipe(merge({}))
|
||||
.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);
|
||||
"build-translation-fingerprints",
|
||||
function fingerprintTranslationFiles() {
|
||||
// Fingerprint full file of each language
|
||||
const files = fs.readdirSync(fullDir);
|
||||
|
||||
module.exports = tasks;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
fingerprints[files[i].split(".")[0]] = {
|
||||
// In dev we create fake hashes
|
||||
hash: env.isProdBuild()
|
||||
? crypto
|
||||
.createHash("md5")
|
||||
.update(fs.readFileSync(path.join(fullDir, files[i]), "utf-8"))
|
||||
.digest("hex")
|
||||
: "dev",
|
||||
};
|
||||
}
|
||||
|
||||
mapFiles(outDir, ".json", (filename) => {
|
||||
const parsed = path.parse(filename);
|
||||
|
||||
// nl.json -> nl-<hash>.json
|
||||
if (!(parsed.name in fingerprints)) {
|
||||
throw new Error(`Unable to find hash for ${filename}`);
|
||||
}
|
||||
|
||||
fs.renameSync(
|
||||
filename,
|
||||
`${parsed.dir}/${parsed.name}-${fingerprints[parsed.name].hash}${
|
||||
parsed.ext
|
||||
}`
|
||||
);
|
||||
});
|
||||
|
||||
const stream = source("translationFingerprints.json");
|
||||
stream.write(JSON.stringify(fingerprints));
|
||||
process.nextTick(() => stream.end());
|
||||
return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
|
||||
}
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"build-translations",
|
||||
gulp.series(
|
||||
"clean-translations",
|
||||
"ensure-translations-build-dir",
|
||||
env.isProdBuild() ? (done) => done() : "create-test-translation",
|
||||
"build-master-translation",
|
||||
"build-merged-translations",
|
||||
gulp.parallel(...splitTasks),
|
||||
"build-flattened-translations",
|
||||
"build-translation-fingerprints",
|
||||
function writeMetadata() {
|
||||
return gulp
|
||||
.src(
|
||||
[
|
||||
path.join(paths.translations_src, "translationMetadata.json"),
|
||||
workDir + "/testMetadata.json",
|
||||
workDir + "/translationFingerprints.json",
|
||||
],
|
||||
{ allowEmpty: true }
|
||||
)
|
||||
.pipe(merge({}))
|
||||
.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));
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@@ -3,6 +3,7 @@ const gulp = require("gulp");
|
||||
const webpack = require("webpack");
|
||||
const WebpackDevServer = require("webpack-dev-server");
|
||||
const log = require("fancy-log");
|
||||
const path = require("path");
|
||||
const paths = require("../paths");
|
||||
const {
|
||||
createAppConfig,
|
||||
@@ -27,7 +28,7 @@ const runDevServer = ({
|
||||
open: true,
|
||||
watchContentBase: true,
|
||||
contentBase,
|
||||
}).listen(port, listenHost, function(err) {
|
||||
}).listen(port, listenHost, function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
@@ -37,9 +38,9 @@ const runDevServer = ({
|
||||
|
||||
const handler = (done) => (err, stats) => {
|
||||
if (err) {
|
||||
console.log(err.stack || err);
|
||||
log.error(err.stack || err);
|
||||
if (err.details) {
|
||||
console.log(err.details);
|
||||
log.error(err.details);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -47,7 +48,7 @@ const handler = (done) => (err, stats) => {
|
||||
log(`Build done @ ${new Date().toLocaleTimeString()}`);
|
||||
|
||||
if (stats.hasErrors() || stats.hasWarnings()) {
|
||||
console.log(stats.toString("minimal"));
|
||||
log.warn(stats.toString("minimal"));
|
||||
}
|
||||
|
||||
if (done) {
|
||||
@@ -57,10 +58,14 @@ const handler = (done) => (err, stats) => {
|
||||
|
||||
gulp.task("webpack-watch-app", () => {
|
||||
// we are not calling done, so this command will run forever
|
||||
webpack(bothBuilds(createAppConfig, { isProdBuild: false })).watch(
|
||||
{},
|
||||
webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch(
|
||||
{ ignored: /build-translations/ },
|
||||
handler()
|
||||
);
|
||||
gulp.watch(
|
||||
path.join(paths.translations_src, "en.json"),
|
||||
gulp.series("build-translations", "copy-translations-app")
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
@@ -77,7 +82,7 @@ gulp.task(
|
||||
gulp.task("webpack-dev-server-demo", () => {
|
||||
runDevServer({
|
||||
compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })),
|
||||
contentBase: paths.demo_root,
|
||||
contentBase: paths.demo_output_root,
|
||||
port: 8090,
|
||||
});
|
||||
});
|
||||
@@ -98,7 +103,7 @@ gulp.task(
|
||||
gulp.task("webpack-dev-server-cast", () => {
|
||||
runDevServer({
|
||||
compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })),
|
||||
contentBase: paths.cast_root,
|
||||
contentBase: paths.cast_output_root,
|
||||
port: 8080,
|
||||
// Accessible from the network, because that's how Cast hits it.
|
||||
listenHost: "0.0.0.0",
|
||||
@@ -124,7 +129,7 @@ gulp.task("webpack-watch-hassio", () => {
|
||||
webpack(
|
||||
createHassioConfig({
|
||||
isProdBuild: false,
|
||||
latestBuild: false,
|
||||
latestBuild: true,
|
||||
})
|
||||
).watch({}, handler());
|
||||
});
|
||||
@@ -134,9 +139,8 @@ gulp.task(
|
||||
() =>
|
||||
new Promise((resolve) =>
|
||||
webpack(
|
||||
createHassioConfig({
|
||||
bothBuilds(createHassioConfig, {
|
||||
isProdBuild: true,
|
||||
latestBuild: false,
|
||||
}),
|
||||
handler(resolve)
|
||||
)
|
||||
@@ -145,10 +149,9 @@ gulp.task(
|
||||
|
||||
gulp.task("webpack-dev-server-gallery", () => {
|
||||
runDevServer({
|
||||
compiler: webpack(
|
||||
createGalleryConfig({ latestBuild: true, isProdBuild: false })
|
||||
),
|
||||
contentBase: paths.gallery_root,
|
||||
// We don't use the es5 build, but the dev server will fuck up the publicPath if we don't
|
||||
compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })),
|
||||
contentBase: paths.gallery_output_root,
|
||||
port: 8100,
|
||||
});
|
||||
});
|
||||
|
@@ -4,29 +4,42 @@ module.exports = {
|
||||
polymer_dir: path.resolve(__dirname, ".."),
|
||||
|
||||
build_dir: path.resolve(__dirname, "../build"),
|
||||
root: path.resolve(__dirname, "../hass_frontend"),
|
||||
static: path.resolve(__dirname, "../hass_frontend/static"),
|
||||
output: path.resolve(__dirname, "../hass_frontend/frontend_latest"),
|
||||
output_es5: path.resolve(__dirname, "../hass_frontend/frontend_es5"),
|
||||
app_output_root: path.resolve(__dirname, "../hass_frontend"),
|
||||
app_output_static: path.resolve(__dirname, "../hass_frontend/static"),
|
||||
app_output_latest: path.resolve(
|
||||
__dirname,
|
||||
"../hass_frontend/frontend_latest"
|
||||
),
|
||||
app_output_es5: path.resolve(__dirname, "../hass_frontend/frontend_es5"),
|
||||
|
||||
demo_dir: path.resolve(__dirname, "../demo"),
|
||||
demo_root: path.resolve(__dirname, "../demo/dist"),
|
||||
demo_static: path.resolve(__dirname, "../demo/dist/static"),
|
||||
demo_output: path.resolve(__dirname, "../demo/dist/frontend_latest"),
|
||||
demo_output_root: path.resolve(__dirname, "../demo/dist"),
|
||||
demo_output_static: path.resolve(__dirname, "../demo/dist/static"),
|
||||
demo_output_latest: path.resolve(__dirname, "../demo/dist/frontend_latest"),
|
||||
demo_output_es5: path.resolve(__dirname, "../demo/dist/frontend_es5"),
|
||||
|
||||
cast_dir: path.resolve(__dirname, "../cast"),
|
||||
cast_root: path.resolve(__dirname, "../cast/dist"),
|
||||
cast_static: path.resolve(__dirname, "../cast/dist/static"),
|
||||
cast_output: path.resolve(__dirname, "../cast/dist/frontend_latest"),
|
||||
cast_output_root: path.resolve(__dirname, "../cast/dist"),
|
||||
cast_output_static: path.resolve(__dirname, "../cast/dist/static"),
|
||||
cast_output_latest: path.resolve(__dirname, "../cast/dist/frontend_latest"),
|
||||
cast_output_es5: path.resolve(__dirname, "../cast/dist/frontend_es5"),
|
||||
|
||||
gallery_dir: path.resolve(__dirname, "../gallery"),
|
||||
gallery_root: path.resolve(__dirname, "../gallery/dist"),
|
||||
gallery_output: path.resolve(__dirname, "../gallery/dist/frontend_latest"),
|
||||
gallery_static: path.resolve(__dirname, "../gallery/dist/static"),
|
||||
gallery_output_root: path.resolve(__dirname, "../gallery/dist"),
|
||||
gallery_output_latest: path.resolve(
|
||||
__dirname,
|
||||
"../gallery/dist/frontend_latest"
|
||||
),
|
||||
gallery_output_static: path.resolve(__dirname, "../gallery/dist/static"),
|
||||
|
||||
hassio_dir: path.resolve(__dirname, "../hassio"),
|
||||
hassio_root: path.resolve(__dirname, "../hassio/build"),
|
||||
hassio_publicPath: "/api/hassio/app/",
|
||||
hassio_output_root: path.resolve(__dirname, "../hassio/build"),
|
||||
hassio_output_latest: path.resolve(
|
||||
__dirname,
|
||||
"../hassio/build/frontend_latest"
|
||||
),
|
||||
hassio_output_es5: path.resolve(__dirname, "../hassio/build/frontend_es5"),
|
||||
hassio_publicPath: "/api/hassio/app",
|
||||
|
||||
translations_src: path.resolve(__dirname, "../src/translations"),
|
||||
};
|
||||
|
1
build-scripts/removedIcons.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
14
build-scripts/rollup-plugins/dont-hash-plugin.js
Normal file
@@ -0,0 +1,14 @@
|
||||
module.exports = function (opts = {}) {
|
||||
const dontHash = opts.dontHash || new Set();
|
||||
|
||||
return {
|
||||
name: "dont-hash",
|
||||
renderChunk(_code, chunk, _options) {
|
||||
if (!chunk.isEntry || !dontHash.has(chunk.name)) {
|
||||
return null;
|
||||
}
|
||||
chunk.fileName = `${chunk.name}.js`;
|
||||
return null;
|
||||
},
|
||||
};
|
||||
};
|
26
build-scripts/rollup-plugins/ignore-plugin.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const path = require("path");
|
||||
|
||||
module.exports = function (userOptions = {}) {
|
||||
// Files need to be absolute paths.
|
||||
// This only works if the file has no exports
|
||||
// and only is imported for its side effects
|
||||
const files = userOptions.files || [];
|
||||
|
||||
if (files.length === 0) {
|
||||
return {
|
||||
name: "ignore",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: "ignore",
|
||||
|
||||
load(id) {
|
||||
return files.some((toIgnorePath) => id.startsWith(toIgnorePath))
|
||||
? {
|
||||
code: "",
|
||||
}
|
||||
: null;
|
||||
},
|
||||
};
|
||||
};
|
34
build-scripts/rollup-plugins/manifest-plugin.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const url = require("url");
|
||||
|
||||
const defaultOptions = {
|
||||
publicPath: "",
|
||||
};
|
||||
|
||||
module.exports = function (userOptions = {}) {
|
||||
const options = { ...defaultOptions, ...userOptions };
|
||||
|
||||
return {
|
||||
name: "manifest",
|
||||
generateBundle(outputOptions, bundle) {
|
||||
const manifest = {};
|
||||
|
||||
for (const chunk of Object.values(bundle)) {
|
||||
if (!chunk.isEntry) {
|
||||
continue;
|
||||
}
|
||||
// Add js extension to mimic Webpack manifest.
|
||||
manifest[`${chunk.name}.js`] = url.resolve(
|
||||
options.publicPath,
|
||||
chunk.fileName
|
||||
);
|
||||
}
|
||||
|
||||
this.emitFile({
|
||||
type: "asset",
|
||||
source: JSON.stringify(manifest, undefined, 2),
|
||||
name: "manifest.json",
|
||||
fileName: "manifest.json",
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
149
build-scripts/rollup-plugins/worker-plugin.js
Normal file
@@ -0,0 +1,149 @@
|
||||
// Worker plugin
|
||||
// Each worker will include all of its dependencies
|
||||
// instead of relying on an importer.
|
||||
|
||||
// Forked from v.1.4.1
|
||||
// https://github.com/surma/rollup-plugin-off-main-thread
|
||||
/**
|
||||
* Copyright 2018 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const rollup = require("rollup");
|
||||
const path = require("path");
|
||||
const MagicString = require("magic-string");
|
||||
|
||||
const defaultOpts = {
|
||||
// A RegExp to find `new Workers()` calls. The second capture group _must_
|
||||
// capture the provided file name without the quotes.
|
||||
workerRegexp: /new Worker\((["'])(.+?)\1(,[^)]+)?\)/g,
|
||||
plugins: ["node-resolve", "commonjs", "babel", "terser", "ignore"],
|
||||
};
|
||||
|
||||
async function getBundledWorker(workerPath, rollupOptions) {
|
||||
const bundle = await rollup.rollup({
|
||||
...rollupOptions,
|
||||
input: {
|
||||
worker: workerPath,
|
||||
},
|
||||
});
|
||||
const { output } = await bundle.generate({
|
||||
// Generates cleanest output, we shouldn't have any imports/exports
|
||||
// that would be incompatible with ES5.
|
||||
format: "es",
|
||||
// We should not export anything. This will fail build if we are.
|
||||
exports: "none",
|
||||
});
|
||||
|
||||
let code;
|
||||
|
||||
for (const chunkOrAsset of output) {
|
||||
if (chunkOrAsset.name === "worker") {
|
||||
code = chunkOrAsset.code;
|
||||
} else if (chunkOrAsset.type !== "asset") {
|
||||
throw new Error("Unexpected extra output");
|
||||
}
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
module.exports = function (opts = {}) {
|
||||
opts = { ...defaultOpts, ...opts };
|
||||
|
||||
let rollupOptions;
|
||||
let refIds;
|
||||
|
||||
return {
|
||||
name: "hass-worker",
|
||||
|
||||
async buildStart(options) {
|
||||
refIds = {};
|
||||
rollupOptions = {
|
||||
plugins: options.plugins.filter((plugin) =>
|
||||
opts.plugins.includes(plugin.name)
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
async transform(code, id) {
|
||||
// Copy the regexp as they are stateful and this hook is async.
|
||||
const workerRegexp = new RegExp(
|
||||
opts.workerRegexp.source,
|
||||
opts.workerRegexp.flags
|
||||
);
|
||||
if (!workerRegexp.test(code)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ms = new MagicString(code);
|
||||
// Reset the regexp
|
||||
workerRegexp.lastIndex = 0;
|
||||
while (true) {
|
||||
const match = workerRegexp.exec(code);
|
||||
if (!match) {
|
||||
break;
|
||||
}
|
||||
|
||||
const workerFile = match[2];
|
||||
let optionsObject = {};
|
||||
// Parse the optional options object
|
||||
if (match[3] && match[3].length > 0) {
|
||||
// FIXME: ooooof!
|
||||
optionsObject = new Function(`return ${match[3].slice(1)};`)();
|
||||
}
|
||||
delete optionsObject.type;
|
||||
|
||||
if (!new RegExp("^.*/").test(workerFile)) {
|
||||
this.warn(
|
||||
`Paths passed to the Worker constructor must be relative or absolute, i.e. start with /, ./ or ../ (just like dynamic import!). Ignoring "${workerFile}".`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find worker file and store it as a chunk with ID prefixed for our loader
|
||||
const resolvedWorkerFile = (await this.resolve(workerFile, id)).id;
|
||||
let chunkRefId;
|
||||
if (resolvedWorkerFile in refIds) {
|
||||
chunkRefId = refIds[resolvedWorkerFile];
|
||||
} else {
|
||||
this.addWatchFile(resolvedWorkerFile);
|
||||
const source = await getBundledWorker(
|
||||
resolvedWorkerFile,
|
||||
rollupOptions
|
||||
);
|
||||
chunkRefId = refIds[resolvedWorkerFile] = this.emitFile({
|
||||
name: path.basename(resolvedWorkerFile),
|
||||
source,
|
||||
type: "asset",
|
||||
});
|
||||
}
|
||||
|
||||
const workerParametersStartIndex = match.index + "new Worker(".length;
|
||||
const workerParametersEndIndex =
|
||||
match.index + match[0].length - ")".length;
|
||||
|
||||
ms.overwrite(
|
||||
workerParametersStartIndex,
|
||||
workerParametersEndIndex,
|
||||
`import.meta.ROLLUP_FILE_URL_${chunkRefId}, ${JSON.stringify(
|
||||
optionsObject
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
code: ms.toString(),
|
||||
map: ms.generateMap({ hires: true }),
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
151
build-scripts/rollup.js
Normal file
@@ -0,0 +1,151 @@
|
||||
const path = require("path");
|
||||
|
||||
const commonjs = require("@rollup/plugin-commonjs");
|
||||
const resolve = require("@rollup/plugin-node-resolve");
|
||||
const json = require("@rollup/plugin-json");
|
||||
const babel = require("rollup-plugin-babel");
|
||||
const replace = require("@rollup/plugin-replace");
|
||||
const visualizer = require("rollup-plugin-visualizer");
|
||||
const { string } = require("rollup-plugin-string");
|
||||
const { terser } = require("rollup-plugin-terser");
|
||||
const manifest = require("./rollup-plugins/manifest-plugin");
|
||||
const worker = require("./rollup-plugins/worker-plugin");
|
||||
const dontHashPlugin = require("./rollup-plugins/dont-hash-plugin");
|
||||
const ignore = require("./rollup-plugins/ignore-plugin");
|
||||
|
||||
const bundle = require("./bundle");
|
||||
const paths = require("./paths");
|
||||
|
||||
const extensions = [".js", ".ts"];
|
||||
|
||||
/**
|
||||
* @param {Object} arg
|
||||
* @param { import("rollup").InputOption } arg.input
|
||||
*/
|
||||
const createRollupConfig = ({
|
||||
entry,
|
||||
outputPath,
|
||||
defineOverlay,
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
publicPath,
|
||||
dontHash,
|
||||
}) => {
|
||||
return {
|
||||
/**
|
||||
* @type { import("rollup").InputOptions }
|
||||
*/
|
||||
inputOptions: {
|
||||
input: entry,
|
||||
// Some entry points contain no JavaScript. This setting silences a warning about that.
|
||||
// https://rollupjs.org/guide/en/#preserveentrysignatures
|
||||
preserveEntrySignatures: false,
|
||||
plugins: [
|
||||
ignore({
|
||||
files: bundle.emptyPackages({ latestBuild }),
|
||||
}),
|
||||
resolve({
|
||||
extensions,
|
||||
preferBuiltins: false,
|
||||
browser: true,
|
||||
rootDir: paths.polymer_dir,
|
||||
}),
|
||||
commonjs({
|
||||
namedExports: {
|
||||
"js-yaml": ["safeDump", "safeLoad"],
|
||||
},
|
||||
}),
|
||||
json(),
|
||||
babel({
|
||||
...bundle.babelOptions({ latestBuild }),
|
||||
extensions,
|
||||
exclude: bundle.babelExclude(),
|
||||
}),
|
||||
string({
|
||||
// Import certain extensions as strings
|
||||
include: [path.join(paths.polymer_dir, "node_modules/**/*.css")],
|
||||
}),
|
||||
replace(
|
||||
bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })
|
||||
),
|
||||
manifest({
|
||||
publicPath,
|
||||
}),
|
||||
worker(),
|
||||
dontHashPlugin({ dontHash }),
|
||||
isProdBuild && terser(bundle.terserOptions(latestBuild)),
|
||||
isStatsBuild &&
|
||||
visualizer({
|
||||
// https://github.com/btd/rollup-plugin-visualizer#options
|
||||
open: true,
|
||||
sourcemap: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
/**
|
||||
* @type { import("rollup").OutputOptions }
|
||||
*/
|
||||
outputOptions: {
|
||||
// https://rollupjs.org/guide/en/#outputdir
|
||||
dir: outputPath,
|
||||
// https://rollupjs.org/guide/en/#outputformat
|
||||
format: latestBuild ? "es" : "systemjs",
|
||||
// https://rollupjs.org/guide/en/#outputexternallivebindings
|
||||
externalLiveBindings: false,
|
||||
// https://rollupjs.org/guide/en/#outputentryfilenames
|
||||
// https://rollupjs.org/guide/en/#outputchunkfilenames
|
||||
// https://rollupjs.org/guide/en/#outputassetfilenames
|
||||
entryFileNames:
|
||||
isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js",
|
||||
chunkFileNames:
|
||||
isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js",
|
||||
assetFileNames:
|
||||
isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js",
|
||||
// https://rollupjs.org/guide/en/#outputsourcemap
|
||||
sourcemap: isProdBuild ? true : "inline",
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
|
||||
return createRollupConfig(
|
||||
bundle.config.app({
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
|
||||
return createRollupConfig(
|
||||
bundle.config.demo({
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const createCastConfig = ({ isProdBuild, latestBuild }) => {
|
||||
return createRollupConfig(bundle.config.cast({ isProdBuild, latestBuild }));
|
||||
};
|
||||
|
||||
const createHassioConfig = ({ isProdBuild, latestBuild }) => {
|
||||
return createRollupConfig(bundle.config.hassio({ isProdBuild, latestBuild }));
|
||||
};
|
||||
|
||||
const createGalleryConfig = ({ isProdBuild, latestBuild }) => {
|
||||
return createRollupConfig(
|
||||
bundle.config.gallery({ isProdBuild, latestBuild })
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createAppConfig,
|
||||
createDemoConfig,
|
||||
createCastConfig,
|
||||
createHassioConfig,
|
||||
createGalleryConfig,
|
||||
};
|
16
build-scripts/util.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
// Helper function to map recursively over files in a folder and it's subfolders
|
||||
module.exports.mapFiles = function mapFiles(startPath, filter, mapFunc) {
|
||||
const files = fs.readdirSync(startPath);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const filename = path.join(startPath, files[i]);
|
||||
const stat = fs.lstatSync(filename);
|
||||
if (stat.isDirectory()) {
|
||||
mapFiles(filename, filter, mapFunc);
|
||||
} else if (filename.indexOf(filter) >= 0) {
|
||||
mapFunc(filename);
|
||||
}
|
||||
}
|
||||
};
|
@@ -1,48 +1,45 @@
|
||||
const webpack = require("webpack");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const WorkboxPlugin = require("workbox-webpack-plugin");
|
||||
const ManifestPlugin = require("webpack-manifest-plugin");
|
||||
const paths = require("./paths.js");
|
||||
const { babelLoaderConfig } = require("./babel.js");
|
||||
|
||||
let version = fs
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "setup.py"), "utf8")
|
||||
.match(/\d{8}\.\d+/);
|
||||
if (!version) {
|
||||
throw Error("Version not found");
|
||||
}
|
||||
version = version[0];
|
||||
const bundle = require("./bundle");
|
||||
|
||||
const createWebpackConfig = ({
|
||||
entry,
|
||||
outputRoot,
|
||||
outputPath,
|
||||
publicPath,
|
||||
defineOverlay,
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
dontHash,
|
||||
}) => {
|
||||
if (!dontHash) {
|
||||
dontHash = new Set();
|
||||
}
|
||||
const ignorePackages = bundle.ignorePackages({ latestBuild });
|
||||
return {
|
||||
mode: isProdBuild ? "production" : "development",
|
||||
devtool: isProdBuild ? "source-map" : "inline-cheap-module-source-map",
|
||||
devtool: isProdBuild
|
||||
? "cheap-module-source-map"
|
||||
: "eval-cheap-module-source-map",
|
||||
entry,
|
||||
node: false,
|
||||
module: {
|
||||
rules: [
|
||||
babelLoaderConfig({ latestBuild }),
|
||||
{
|
||||
test: /\.m?js$|\.ts$/,
|
||||
exclude: bundle.babelExclude(),
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: bundle.babelOptions({ latestBuild }),
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: "raw-loader",
|
||||
},
|
||||
{
|
||||
test: /\.(html)$/,
|
||||
use: {
|
||||
loader: "html-loader",
|
||||
options: {
|
||||
exportAsEs6Default: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
optimization: {
|
||||
@@ -52,203 +49,122 @@ const createWebpackConfig = ({
|
||||
parallel: true,
|
||||
extractComments: true,
|
||||
sourceMap: true,
|
||||
terserOptions: {
|
||||
safari10: true,
|
||||
ecma: latestBuild ? undefined : 5,
|
||||
},
|
||||
terserOptions: bundle.terserOptions(latestBuild),
|
||||
}),
|
||||
],
|
||||
},
|
||||
experiments: {
|
||||
topLevelAwait: true,
|
||||
},
|
||||
plugins: [
|
||||
new ManifestPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: !isProdBuild,
|
||||
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
|
||||
__VERSION__: JSON.stringify(version),
|
||||
__DEMO__: false,
|
||||
__STATIC_PATH__: "/static/",
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
isProdBuild ? "production" : "development"
|
||||
),
|
||||
...defineOverlay,
|
||||
new ManifestPlugin({
|
||||
// Only include the JS of entrypoints
|
||||
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
|
||||
}),
|
||||
new webpack.DefinePlugin(
|
||||
bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })
|
||||
),
|
||||
new webpack.IgnorePlugin({
|
||||
checkResource(resource, context) {
|
||||
// Only use ignore to intercept imports that we don't control
|
||||
// inside node_module dependencies.
|
||||
if (
|
||||
!context.includes("/node_modules/") ||
|
||||
// calling define.amd will call require("!!webpack amd options")
|
||||
resource.startsWith("!!webpack") ||
|
||||
// loaded by webpack dev server but doesn't exist.
|
||||
resource === "webpack/hot"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
let fullPath;
|
||||
try {
|
||||
fullPath = resource.startsWith(".")
|
||||
? path.resolve(context, resource)
|
||||
: require.resolve(resource);
|
||||
} catch (err) {
|
||||
console.error(
|
||||
"Error in Home Assistant ignore plugin",
|
||||
resource,
|
||||
context
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
|
||||
return ignorePackages.some((toIgnorePath) =>
|
||||
fullPath.startsWith(toIgnorePath)
|
||||
);
|
||||
},
|
||||
}),
|
||||
// Ignore moment.js locales
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
// Color.js is bloated, it contains all color definitions for all material color sets.
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/@polymer\/paper-styles\/color\.js$/,
|
||||
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
|
||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||
),
|
||||
// Ignore roboto pointing at CDN. We use local font-roboto-local.
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/@polymer\/font-roboto\/roboto\.js$/,
|
||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||
),
|
||||
// Ignore mwc icons pointing at CDN.
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
/@material\/mwc-icon\/mwc-icon-font\.js$/,
|
||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||
),
|
||||
].filter(Boolean),
|
||||
],
|
||||
resolve: {
|
||||
extensions: [".ts", ".js", ".json", ".tsx"],
|
||||
alias: {
|
||||
react: "preact-compat",
|
||||
"react-dom": "preact-compat",
|
||||
// Not necessary unless you consume a module using `createClass`
|
||||
"create-react-class": "preact-compat/lib/create-react-class",
|
||||
// Not necessary unless you consume a module requiring `react-dom-factories`
|
||||
"react-dom-factories": "preact-compat/lib/react-dom-factories",
|
||||
},
|
||||
extensions: [".ts", ".js", ".json"],
|
||||
},
|
||||
output: {
|
||||
filename: ({ chunk }) => {
|
||||
const dontHash = new Set();
|
||||
|
||||
if (!isProdBuild || dontHash.has(chunk.name)) {
|
||||
return `${chunk.name}.js`;
|
||||
}
|
||||
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
|
||||
},
|
||||
environment: {
|
||||
// The environment supports arrow functions ('() => { ... }').
|
||||
arrowFunction: latestBuild,
|
||||
// The environment supports BigInt as literal (123n).
|
||||
bigIntLiteral: false,
|
||||
// The environment supports const and let for variable declarations.
|
||||
const: latestBuild,
|
||||
// The environment supports destructuring ('{ a, b } = obj').
|
||||
destructuring: latestBuild,
|
||||
// The environment supports an async import() function to import EcmaScript modules.
|
||||
dynamicImport: latestBuild,
|
||||
// The environment supports 'for of' iteration ('for (const x of array) { ... }').
|
||||
forOf: latestBuild,
|
||||
// The environment supports ECMAScript Module syntax to import ECMAScript modules (import ... from '...').
|
||||
module: latestBuild,
|
||||
},
|
||||
chunkFilename:
|
||||
isProdBuild && !isStatsBuild
|
||||
? "chunk.[chunkhash].js"
|
||||
: "[name].chunk.js",
|
||||
path: path.resolve(
|
||||
outputRoot,
|
||||
latestBuild ? "frontend_latest" : "frontend_es5"
|
||||
),
|
||||
publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/",
|
||||
// For workerize loader
|
||||
path: outputPath,
|
||||
publicPath,
|
||||
// To silence warning in worker plugin
|
||||
globalObject: "self",
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
|
||||
const config = createWebpackConfig({
|
||||
entry: {
|
||||
app: "./src/entrypoints/app.ts",
|
||||
authorize: "./src/entrypoints/authorize.ts",
|
||||
onboarding: "./src/entrypoints/onboarding.ts",
|
||||
core: "./src/entrypoints/core.ts",
|
||||
compatibility: "./src/entrypoints/compatibility.ts",
|
||||
"custom-panel": "./src/entrypoints/custom-panel.ts",
|
||||
"hass-icons": "./src/entrypoints/hass-icons.ts",
|
||||
},
|
||||
outputRoot: paths.root,
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
});
|
||||
|
||||
if (latestBuild) {
|
||||
// Create an object mapping browser urls to their paths during build
|
||||
const translationMetadata = require("../build-translations/translationMetadata.json");
|
||||
const workBoxTranslationsTemplatedURLs = {};
|
||||
const englishFP = translationMetadata.translations.en.fingerprints;
|
||||
Object.keys(englishFP).forEach((key) => {
|
||||
workBoxTranslationsTemplatedURLs[
|
||||
`/static/translations/${englishFP[key]}`
|
||||
] = `build-translations/output/${key}.json`;
|
||||
});
|
||||
|
||||
config.plugins.push(
|
||||
new WorkboxPlugin.InjectManifest({
|
||||
swSrc: "./src/entrypoints/service-worker-hass.js",
|
||||
swDest: "service_worker.js",
|
||||
importWorkboxFrom: "local",
|
||||
include: [/\.js$/],
|
||||
templatedURLs: {
|
||||
...workBoxTranslationsTemplatedURLs,
|
||||
"/static/icons/favicon-192x192.png":
|
||||
"public/icons/favicon-192x192.png",
|
||||
"/static/fonts/roboto/Roboto-Light.woff2":
|
||||
"node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2",
|
||||
"/static/fonts/roboto/Roboto-Medium.woff2":
|
||||
"node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2",
|
||||
"/static/fonts/roboto/Roboto-Regular.woff2":
|
||||
"node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2",
|
||||
"/static/fonts/roboto/Roboto-Bold.woff2":
|
||||
"node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2",
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return config;
|
||||
return createWebpackConfig(
|
||||
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild })
|
||||
);
|
||||
};
|
||||
|
||||
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
|
||||
return createWebpackConfig({
|
||||
entry: {
|
||||
main: path.resolve(paths.demo_dir, "src/entrypoint.ts"),
|
||||
compatibility: path.resolve(
|
||||
paths.polymer_dir,
|
||||
"src/entrypoints/compatibility.ts"
|
||||
),
|
||||
},
|
||||
outputRoot: paths.demo_root,
|
||||
defineOverlay: {
|
||||
__VERSION__: JSON.stringify(`DEMO-${version}`),
|
||||
__DEMO__: true,
|
||||
},
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
});
|
||||
return createWebpackConfig(
|
||||
bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild })
|
||||
);
|
||||
};
|
||||
|
||||
const createCastConfig = ({ isProdBuild, latestBuild }) => {
|
||||
const entry = {
|
||||
launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
|
||||
};
|
||||
|
||||
if (latestBuild) {
|
||||
entry.receiver = path.resolve(paths.cast_dir, "src/receiver/entrypoint.ts");
|
||||
}
|
||||
|
||||
return createWebpackConfig({
|
||||
entry,
|
||||
outputRoot: paths.cast_root,
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
});
|
||||
return createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
|
||||
};
|
||||
|
||||
const createHassioConfig = ({ isProdBuild, latestBuild }) => {
|
||||
if (latestBuild) {
|
||||
throw new Error("Hass.io does not support latest build!");
|
||||
}
|
||||
const config = createWebpackConfig({
|
||||
entry: {
|
||||
entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.js"),
|
||||
},
|
||||
outputRoot: "",
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
});
|
||||
|
||||
config.output.path = paths.hassio_root;
|
||||
config.output.publicPath = paths.hassio_publicPath;
|
||||
|
||||
return config;
|
||||
return createWebpackConfig(
|
||||
bundle.config.hassio({ isProdBuild, latestBuild })
|
||||
);
|
||||
};
|
||||
|
||||
const createGalleryConfig = ({ isProdBuild, latestBuild }) => {
|
||||
if (!latestBuild) {
|
||||
throw new Error("Gallery only supports latest build!");
|
||||
}
|
||||
const config = createWebpackConfig({
|
||||
entry: {
|
||||
entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"),
|
||||
},
|
||||
outputRoot: paths.gallery_root,
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
});
|
||||
|
||||
return config;
|
||||
return createWebpackConfig(
|
||||
bundle.config.gallery({ isProdBuild, latestBuild })
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 186 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
10
cast/rollup.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const rollup = require("../build-scripts/rollup.js");
|
||||
const env = require("../build-scripts/env.js");
|
||||
|
||||
const config = rollup.createCastConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
latestBuild: true,
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
});
|
||||
|
||||
module.exports = { ...config.inputOptions, output: config.outputOptions };
|
@@ -37,18 +37,21 @@
|
||||
<body>
|
||||
<%= renderTemplate('_js_base') %>
|
||||
|
||||
<script type="module" crossorigin="use-credentials">
|
||||
import "<%= latestLauncherJS %>";
|
||||
<script>
|
||||
import("<%= latestLauncherJS %>");
|
||||
window.latestJS = true;
|
||||
</script>
|
||||
|
||||
<script nomodule>
|
||||
(function() {
|
||||
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
|
||||
if (!isS101) {
|
||||
_ls("/static/polyfills/custom-elements-es5-adapter.js");
|
||||
<script>
|
||||
if (!window.latestJS) {
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5LauncherJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5LauncherJS %>");
|
||||
}
|
||||
})();
|
||||
<% } %>
|
||||
}
|
||||
</script>
|
||||
|
||||
<hc-layout subtitle="FAQ">
|
||||
@@ -209,13 +212,8 @@
|
||||
Chromecast is a technology developed by Google, and is available on:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Google Chrome (all platforms except on iOS)</li>
|
||||
<li>
|
||||
Microsoft Edge (all platforms,
|
||||
<a href="https://www.microsoftedgeinsider.com" target="_blank"
|
||||
>dev and canary builds only</a
|
||||
>)
|
||||
</li>
|
||||
<li>Google Chrome (all platforms except iOS)</li>
|
||||
<li>Microsoft Edge (all platforms)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -248,7 +246,7 @@ http:
|
||||
|
||||
<script>
|
||||
var _gaq = [["_setAccount", "UA-57927901-9"], ["_trackPageview"]];
|
||||
(function(d, t) {
|
||||
(function (d, t) {
|
||||
var g = d.createElement(t),
|
||||
s = d.getElementsByTagName(t)[0];
|
||||
g.src =
|
||||
|
@@ -28,18 +28,21 @@
|
||||
|
||||
<hc-connect></hc-connect>
|
||||
|
||||
<script type="module" crossorigin="use-credentials">
|
||||
import "<%= latestLauncherJS %>";
|
||||
<script>
|
||||
import("<%= latestLauncherJS %>");
|
||||
window.latestJS = true;
|
||||
</script>
|
||||
|
||||
<script nomodule>
|
||||
(function() {
|
||||
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
|
||||
if (!isS101) {
|
||||
_ls("/static/polyfills/custom-elements-es5-adapter.js");
|
||||
<script>
|
||||
if (!window.latestJS) {
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5LauncherJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5LauncherJS %>");
|
||||
}
|
||||
})();
|
||||
<% } %>
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "../../../src/resources/safari-14-attachshadow-patch";
|
||||
import "../../../src/resources/ha-style";
|
||||
import "../../../src/resources/roboto";
|
||||
import "../../../src/components/ha-iconset-svg";
|
||||
import "../../../src/resources/hass-icons";
|
||||
import "./layout/hc-connect";
|
||||
|
@@ -1,49 +1,54 @@
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { Auth, Connection } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
html,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import { Connection, Auth } from "home-assistant-js-websocket";
|
||||
import "@polymer/iron-icon";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "../../../../src/components/ha-icon";
|
||||
import {
|
||||
enableWrite,
|
||||
askWrite,
|
||||
saveTokens,
|
||||
} from "../../../../src/common/auth/token_storage";
|
||||
import {
|
||||
ensureConnectedCastSession,
|
||||
castSendShowLovelaceView,
|
||||
} from "../../../../src/cast/receiver_messages";
|
||||
import "../../../../src/layouts/loading-screen";
|
||||
import { CastManager } from "../../../../src/cast/cast_manager";
|
||||
import {
|
||||
LovelaceConfig,
|
||||
getLovelaceCollection,
|
||||
} from "../../../../src/data/lovelace";
|
||||
import "./hc-layout";
|
||||
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
|
||||
castSendShowLovelaceView,
|
||||
ensureConnectedCastSession,
|
||||
} from "../../../../src/cast/receiver_messages";
|
||||
import {
|
||||
askWrite,
|
||||
enableWrite,
|
||||
saveTokens,
|
||||
} from "../../../../src/common/auth/token_storage";
|
||||
import { atLeastVersion } from "../../../../src/common/config/version";
|
||||
import { toggleAttribute } from "../../../../src/common/dom/toggle_attribute";
|
||||
import "../../../../src/components/ha-icon";
|
||||
import {
|
||||
getLegacyLovelaceCollection,
|
||||
getLovelaceCollection,
|
||||
LovelaceConfig,
|
||||
} from "../../../../src/data/lovelace";
|
||||
import "../../../../src/layouts/hass-loading-screen";
|
||||
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
|
||||
import "./hc-layout";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
|
||||
@customElement("hc-cast")
|
||||
class HcCast extends LitElement {
|
||||
@property() public auth!: Auth;
|
||||
@property() public connection!: Connection;
|
||||
@property() public castManager!: CastManager;
|
||||
@property() private askWrite = false;
|
||||
@property() private lovelaceConfig?: LovelaceConfig | null;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
@property() public connection!: Connection;
|
||||
|
||||
@property() public castManager!: CastManager;
|
||||
|
||||
@internalProperty() private askWrite = false;
|
||||
|
||||
@internalProperty() private lovelaceConfig?: LovelaceConfig | null;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this.lovelaceConfig === undefined) {
|
||||
return html`
|
||||
<loading-screen></loading-screen>>
|
||||
`;
|
||||
return html` <hass-loading-screen no-toolbar></hass-loading-screen>> `;
|
||||
}
|
||||
|
||||
const error =
|
||||
@@ -73,14 +78,12 @@ class HcCast extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
${error
|
||||
? html`
|
||||
<div class="card-content">${error}</div>
|
||||
`
|
||||
? html` <div class="card-content">${error}</div> `
|
||||
: !this.castManager.status
|
||||
? html`
|
||||
<p class="center-item">
|
||||
<mwc-button raised @click=${this._handleLaunch}>
|
||||
<iron-icon icon="hass:cast"></iron-icon>
|
||||
<ha-icon icon="hass:cast"></ha-icon>
|
||||
Start Casting
|
||||
</mwc-button>
|
||||
</p>
|
||||
@@ -118,7 +121,7 @@ class HcCast extends LitElement {
|
||||
${this.castManager.status
|
||||
? html`
|
||||
<mwc-button @click=${this._handleLaunch}>
|
||||
<iron-icon icon="hass:cast-connected"></iron-icon>
|
||||
<ha-icon icon="hass:cast-connected"></ha-icon>
|
||||
Manage
|
||||
</mwc-button>
|
||||
`
|
||||
@@ -133,7 +136,9 @@ class HcCast extends LitElement {
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
const llColl = getLovelaceCollection(this.connection);
|
||||
const llColl = atLeastVersion(this.connection.haVersion, 0, 107)
|
||||
? getLovelaceCollection(this.connection)
|
||||
: getLegacyLovelaceCollection(this.connection);
|
||||
// We first do a single refresh because we need to check if there is LL
|
||||
// configuration.
|
||||
llColl.refresh().then(
|
||||
@@ -238,7 +243,7 @@ class HcCast extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
mwc-button iron-icon {
|
||||
mwc-button ha-icon {
|
||||
margin-right: 8px;
|
||||
height: 18px;
|
||||
}
|
||||
|
@@ -1,35 +1,35 @@
|
||||
import {
|
||||
LitElement,
|
||||
customElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
html,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import {
|
||||
getAuth,
|
||||
createConnection,
|
||||
Auth,
|
||||
getAuthOptions,
|
||||
ERR_HASS_HOST_REQUIRED,
|
||||
ERR_INVALID_HTTPS_TO_HTTP,
|
||||
Connection,
|
||||
ERR_CANNOT_CONNECT,
|
||||
ERR_INVALID_AUTH,
|
||||
} from "home-assistant-js-websocket";
|
||||
import "@polymer/iron-icon";
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
Auth,
|
||||
Connection,
|
||||
createConnection,
|
||||
ERR_CANNOT_CONNECT,
|
||||
ERR_HASS_HOST_REQUIRED,
|
||||
ERR_INVALID_AUTH,
|
||||
ERR_INVALID_HTTPS_TO_HTTP,
|
||||
getAuth,
|
||||
getAuthOptions,
|
||||
} from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
internalProperty,
|
||||
} from "lit-element";
|
||||
import { CastManager, getCastManager } from "../../../../src/cast/cast_manager";
|
||||
import { castSendShowDemo } from "../../../../src/cast/receiver_messages";
|
||||
import {
|
||||
loadTokens,
|
||||
saveTokens,
|
||||
} from "../../../../src/common/auth/token_storage";
|
||||
import "../../../../src/layouts/loading-screen";
|
||||
import { CastManager, getCastManager } from "../../../../src/cast/cast_manager";
|
||||
import "./hc-layout";
|
||||
import { castSendShowDemo } from "../../../../src/cast/receiver_messages";
|
||||
import "../../../../src/components/ha-icon";
|
||||
import "../../../../src/layouts/hass-loading-screen";
|
||||
import { registerServiceWorker } from "../../../../src/util/register-service-worker";
|
||||
import "./hc-layout";
|
||||
|
||||
const seeFAQ = (qid) => html`
|
||||
See <a href="./faq.html${qid ? `#${qid}` : ""}">the FAQ</a> for more
|
||||
@@ -60,17 +60,23 @@ const INTRO = html`
|
||||
|
||||
@customElement("hc-connect")
|
||||
export class HcConnect extends LitElement {
|
||||
@property() private loading = false;
|
||||
@internalProperty() private loading = false;
|
||||
|
||||
// If we had stored credentials but we cannot connect,
|
||||
// show a screen asking retry or logout.
|
||||
@property() private cannotConnect = false;
|
||||
@property() private error?: string | TemplateResult;
|
||||
@property() private auth?: Auth;
|
||||
@property() private connection?: Connection;
|
||||
@property() private castManager?: CastManager | null;
|
||||
@internalProperty() private cannotConnect = false;
|
||||
|
||||
@internalProperty() private error?: string | TemplateResult;
|
||||
|
||||
@internalProperty() private auth?: Auth;
|
||||
|
||||
@internalProperty() private connection?: Connection;
|
||||
|
||||
@internalProperty() private castManager?: CastManager | null;
|
||||
|
||||
private openDemo = false;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
protected render(): TemplateResult {
|
||||
if (this.cannotConnect) {
|
||||
const tokens = loadTokens();
|
||||
return html`
|
||||
@@ -92,9 +98,7 @@ export class HcConnect extends LitElement {
|
||||
}
|
||||
|
||||
if (this.castManager === undefined || this.loading) {
|
||||
return html`
|
||||
<loading-screen></loading-screen>
|
||||
`;
|
||||
return html` <hass-loading-screen no-toolbar></hass-loading-screen> `;
|
||||
}
|
||||
|
||||
if (this.castManager === null) {
|
||||
@@ -127,20 +131,16 @@ export class HcConnect extends LitElement {
|
||||
@keydown=${this._handleInputKeyDown}
|
||||
></paper-input>
|
||||
</p>
|
||||
${this.error
|
||||
? html`
|
||||
<p class="error">${this.error}</p>
|
||||
`
|
||||
: ""}
|
||||
${this.error ? html` <p class="error">${this.error}</p> ` : ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._handleDemo}>
|
||||
Show Demo
|
||||
<iron-icon
|
||||
<ha-icon
|
||||
.icon=${this.castManager.castState === "CONNECTED"
|
||||
? "hass:cast-connected"
|
||||
: "hass:cast"}
|
||||
></iron-icon>
|
||||
></ha-icon>
|
||||
</mwc-button>
|
||||
<div class="spacer"></div>
|
||||
<mwc-button @click=${this._handleConnect}>Authorize</mwc-button>
|
||||
@@ -184,7 +184,7 @@ export class HcConnect extends LitElement {
|
||||
this.castManager = null;
|
||||
}
|
||||
);
|
||||
registerServiceWorker(false);
|
||||
registerServiceWorker(this, false);
|
||||
}
|
||||
|
||||
private async _handleDemo() {
|
||||
@@ -211,7 +211,8 @@ export class HcConnect extends LitElement {
|
||||
if (value === "") {
|
||||
this.error = "Please enter a Home Assistant URL.";
|
||||
return;
|
||||
} else if (value.indexOf("://") === -1) {
|
||||
}
|
||||
if (value.indexOf("://") === -1) {
|
||||
this.error =
|
||||
"Please enter your full URL, including the protocol part (https://).";
|
||||
return;
|
||||
@@ -315,7 +316,7 @@ export class HcConnect extends LitElement {
|
||||
color: darkred;
|
||||
}
|
||||
|
||||
mwc-button iron-icon {
|
||||
mwc-button ha-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
|
@@ -1,33 +1,36 @@
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
html,
|
||||
CSSResult,
|
||||
css,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import {
|
||||
Auth,
|
||||
Connection,
|
||||
HassUser,
|
||||
getUser,
|
||||
HassUser,
|
||||
} from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../src/components/ha-card";
|
||||
|
||||
@customElement("hc-layout")
|
||||
class HcLayout extends LitElement {
|
||||
@property() public subtitle?: string | undefined;
|
||||
|
||||
@property() public auth?: Auth;
|
||||
|
||||
@property() public connection?: Connection;
|
||||
|
||||
@property() public user?: HassUser;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="layout">
|
||||
<img class="hero" src="/images/google-nest-hub.png" />
|
||||
<div class="card-header">
|
||||
<h1 class="card-header">
|
||||
Home Assistant Cast${this.subtitle ? ` – ${this.subtitle}` : ""}
|
||||
${this.auth
|
||||
? html`
|
||||
@@ -37,26 +40,21 @@ class HcLayout extends LitElement {
|
||||
this.auth.data.hassUrl.indexOf("//") + 2
|
||||
)}</a
|
||||
>
|
||||
${this.user
|
||||
? html`
|
||||
– ${this.user.name}
|
||||
`
|
||||
: ""}
|
||||
${this.user ? html` – ${this.user.name} ` : ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</h1>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</ha-card>
|
||||
<div class="footer">
|
||||
<a href="./faq.html">Frequently Asked Questions</a> – Found a bug? Let
|
||||
@balloob know
|
||||
<!-- <a
|
||||
<a href="./faq.html">Frequently Asked Questions</a> – Found a bug?
|
||||
<a
|
||||
href="https://github.com/home-assistant/home-assistant-polymer/issues"
|
||||
target="_blank"
|
||||
>Let us know!</a
|
||||
> -->
|
||||
>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@@ -1 +1,2 @@
|
||||
/* eslint-disable no-undef */
|
||||
export const castContext = cast.framework.CastReceiverContext.getInstance();
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Entity, convertEntities } from "../../../../src/fake_data/entity";
|
||||
import { convertEntities, Entity } from "../../../../src/fake_data/entity";
|
||||
|
||||
export const castDemoEntities: () => Entity[] = () =>
|
||||
convertEntities({
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
LovelaceConfig,
|
||||
LovelaceCardConfig,
|
||||
LovelaceConfig,
|
||||
} from "../../../../src/data/lovelace";
|
||||
import { castContext } from "../cast_context";
|
||||
|
||||
|
@@ -1,17 +1,65 @@
|
||||
/* eslint-disable no-undef */
|
||||
import { CAST_NS } from "../../../src/cast/const";
|
||||
import { HassMessage } from "../../../src/cast/receiver_messages";
|
||||
import "../../../src/resources/custom-card-support";
|
||||
import { castContext } from "./cast_context";
|
||||
import { ReceivedMessage } from "./types";
|
||||
import { HassMessage } from "../../../src/cast/receiver_messages";
|
||||
import { HcMain } from "./layout/hc-main";
|
||||
import { CAST_NS } from "../../../src/cast/const";
|
||||
import { ReceivedMessage } from "./types";
|
||||
|
||||
const controller = new HcMain();
|
||||
document.body.append(controller);
|
||||
const lovelaceController = new HcMain();
|
||||
document.body.append(lovelaceController);
|
||||
|
||||
const mediaPlayer = document.createElement("cast-media-player");
|
||||
mediaPlayer.style.display = "none";
|
||||
document.body.append(mediaPlayer);
|
||||
const playerStylesAdded = false;
|
||||
|
||||
let controls: HTMLElement | null;
|
||||
|
||||
const setTouchControlsVisibility = (visible: boolean) => {
|
||||
if (!castContext.getDeviceCapabilities().touch_input_supported) {
|
||||
return;
|
||||
}
|
||||
controls =
|
||||
controls ||
|
||||
(document.body.querySelector("touch-controls") as HTMLElement | null);
|
||||
if (controls) {
|
||||
controls.style.display = visible ? "initial" : "none";
|
||||
}
|
||||
};
|
||||
|
||||
const showLovelaceController = () => {
|
||||
mediaPlayer.style.display = "none";
|
||||
lovelaceController.style.display = "initial";
|
||||
document.body.setAttribute("style", "overflow-y: auto !important");
|
||||
setTouchControlsVisibility(false);
|
||||
};
|
||||
|
||||
const showMediaPlayer = () => {
|
||||
lovelaceController.style.display = "none";
|
||||
mediaPlayer.style.display = "initial";
|
||||
document.body.removeAttribute("style");
|
||||
setTouchControlsVisibility(true);
|
||||
if (!playerStylesAdded) {
|
||||
const style = document.createElement("style");
|
||||
style.innerHTML = `
|
||||
body {
|
||||
--logo-image: url('https://www.home-assistant.io/images/home-assistant-logo.svg');
|
||||
--logo-repeat: no-repeat;
|
||||
--playback-logo-image: url('https://www.home-assistant.io/images/home-assistant-logo.svg');
|
||||
--theme-hue: 200;
|
||||
--progress-color: #03a9f4;
|
||||
--splash-image: url('https://home-assistant.io/images/cast/splash.png');
|
||||
--splash-size: cover;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
};
|
||||
|
||||
const options = new cast.framework.CastReceiverOptions();
|
||||
options.disableIdleTimeout = true;
|
||||
options.customNamespaces = {
|
||||
// @ts-ignore
|
||||
[CAST_NS]: cast.framework.system.MessageType.JSON,
|
||||
};
|
||||
|
||||
@@ -29,13 +77,61 @@ options.uiConfig = new cast.framework.ui.UiConfig();
|
||||
// @ts-ignore
|
||||
options.uiConfig.touchScreenOptimizedApp = true;
|
||||
|
||||
castContext.setInactivityTimeout(86400); // 1 day
|
||||
|
||||
castContext.addCustomMessageListener(
|
||||
CAST_NS,
|
||||
// @ts-ignore
|
||||
(ev: ReceivedMessage<HassMessage>) => {
|
||||
// We received a show Lovelace command, stop media from playing, hide media player and show Lovelace controller
|
||||
if (
|
||||
playerManager.getPlayerState() !==
|
||||
cast.framework.messages.PlayerState.IDLE
|
||||
) {
|
||||
playerManager.stop();
|
||||
} else {
|
||||
showLovelaceController();
|
||||
}
|
||||
const msg = ev.data;
|
||||
msg.senderId = ev.senderId;
|
||||
controller.processIncomingMessage(msg);
|
||||
lovelaceController.processIncomingMessage(msg);
|
||||
}
|
||||
);
|
||||
|
||||
const playerManager = castContext.getPlayerManager();
|
||||
|
||||
playerManager.setMessageInterceptor(
|
||||
cast.framework.messages.MessageType.LOAD,
|
||||
(loadRequestData) => {
|
||||
// We received a play media command, hide Lovelace and show media player
|
||||
showMediaPlayer();
|
||||
const media = loadRequestData.media;
|
||||
// Special handling if it came from Google Assistant
|
||||
if (media.entity) {
|
||||
media.contentId = media.entity;
|
||||
media.streamType = cast.framework.messages.StreamType.LIVE;
|
||||
media.contentType = "application/vnd.apple.mpegurl";
|
||||
// @ts-ignore
|
||||
media.hlsVideoSegmentFormat =
|
||||
cast.framework.messages.HlsVideoSegmentFormat.FMP4;
|
||||
}
|
||||
return loadRequestData;
|
||||
}
|
||||
);
|
||||
|
||||
playerManager.addEventListener(
|
||||
cast.framework.events.EventType.MEDIA_STATUS,
|
||||
(event) => {
|
||||
if (
|
||||
event.mediaStatus?.playerState ===
|
||||
cast.framework.messages.PlayerState.IDLE &&
|
||||
event.mediaStatus?.idleReason &&
|
||||
event.mediaStatus?.idleReason !==
|
||||
cast.framework.messages.IdleReason.INTERRUPTED
|
||||
) {
|
||||
// media finished or stopped, return to default Lovelace
|
||||
showLovelaceController();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
@@ -1,22 +1,29 @@
|
||||
import { HassElement } from "../../../../src/state/hass-element";
|
||||
import "./hc-lovelace";
|
||||
import { customElement, TemplateResult, html, property } from "lit-element";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { mockHistory } from "../../../../demo/src/stubs/history";
|
||||
import { LovelaceConfig } from "../../../../src/data/lovelace";
|
||||
import {
|
||||
MockHomeAssistant,
|
||||
provideHass,
|
||||
} from "../../../../src/fake_data/provide_hass";
|
||||
import { HassElement } from "../../../../src/state/hass-element";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { LovelaceConfig } from "../../../../src/data/lovelace";
|
||||
import { castDemoEntities } from "../demo/cast-demo-entities";
|
||||
import { castDemoLovelace } from "../demo/cast-demo-lovelace";
|
||||
import { mockHistory } from "../../../../demo/src/stubs/history";
|
||||
import "./hc-lovelace";
|
||||
|
||||
@customElement("hc-demo")
|
||||
class HcDemo extends HassElement {
|
||||
@property() public lovelacePath!: string;
|
||||
@property() private _lovelaceConfig?: LovelaceConfig;
|
||||
@property({ attribute: false }) public lovelacePath!: string;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
@internalProperty() private _lovelaceConfig?: LovelaceConfig;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._lovelaceConfig) {
|
||||
return html``;
|
||||
}
|
||||
@@ -28,6 +35,7 @@ class HcDemo extends HassElement {
|
||||
></hc-lovelace>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._initialize();
|
||||
|
@@ -1,20 +1,21 @@
|
||||
import {
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
html,
|
||||
customElement,
|
||||
CSSResult,
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
@customElement("hc-launch-screen")
|
||||
class HcLaunchScreen extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public error?: string;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="container">
|
||||
<img
|
||||
@@ -22,11 +23,7 @@ class HcLaunchScreen extends LitElement {
|
||||
/>
|
||||
<div class="status">
|
||||
${this.hass ? "Connected" : "Not Connected"}
|
||||
${this.error
|
||||
? html`
|
||||
<p>Error: ${this.error}</p>
|
||||
`
|
||||
: ""}
|
||||
${this.error ? html` <p>Error: ${this.error}</p> ` : ""}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@@ -1,28 +1,29 @@
|
||||
import {
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
html,
|
||||
customElement,
|
||||
CSSResult,
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { LovelaceConfig } from "../../../../src/data/lovelace";
|
||||
import "../../../../src/panels/lovelace/views/hui-view";
|
||||
import "../../../../src/panels/lovelace/views/hui-panel-view";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { Lovelace } from "../../../../src/panels/lovelace/types";
|
||||
import "../../../../src/panels/lovelace/views/hui-view";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "./hc-launch-screen";
|
||||
|
||||
@customElement("hc-lovelace")
|
||||
class HcLovelace extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public lovelaceConfig!: LovelaceConfig;
|
||||
@property({ attribute: false }) public lovelaceConfig!: LovelaceConfig;
|
||||
|
||||
@property() public viewPath?: string | number;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
public urlPath?: string | null;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const index = this._viewIndex;
|
||||
if (index === undefined) {
|
||||
return html`
|
||||
@@ -35,27 +36,21 @@ class HcLovelace extends LitElement {
|
||||
const lovelace: Lovelace = {
|
||||
config: this.lovelaceConfig,
|
||||
editMode: false,
|
||||
urlPath: this.urlPath!,
|
||||
enableFullEditMode: () => undefined,
|
||||
mode: "storage",
|
||||
language: "en",
|
||||
saveConfig: async () => undefined,
|
||||
deleteConfig: async () => undefined,
|
||||
setEditMode: () => undefined,
|
||||
};
|
||||
return this.lovelaceConfig.views[index].panel
|
||||
? html`
|
||||
<hui-panel-view
|
||||
.hass=${this.hass}
|
||||
.config=${this.lovelaceConfig.views[index]}
|
||||
></hui-panel-view>
|
||||
`
|
||||
: html`
|
||||
<hui-view
|
||||
.hass=${this.hass}
|
||||
.lovelace=${lovelace}
|
||||
.index=${index}
|
||||
columns="2"
|
||||
></hui-view>
|
||||
`;
|
||||
return html`
|
||||
<hui-view
|
||||
.hass=${this.hass}
|
||||
.lovelace=${lovelace}
|
||||
.index=${index}
|
||||
></hui-view>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps) {
|
||||
@@ -71,7 +66,7 @@ class HcLovelace extends LitElement {
|
||||
|
||||
if (configBackground) {
|
||||
(this.shadowRoot!.querySelector(
|
||||
"hui-view, hui-panel-view"
|
||||
"hui-view"
|
||||
) as HTMLElement)!.style.setProperty(
|
||||
"--lovelace-background",
|
||||
configBackground
|
||||
|
@@ -1,40 +1,53 @@
|
||||
import {
|
||||
getAuth,
|
||||
createConnection,
|
||||
getAuth,
|
||||
UnsubscribeFunc,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { customElement, TemplateResult, html, property } from "lit-element";
|
||||
import { HassElement } from "../../../../src/state/hass-element";
|
||||
import {
|
||||
HassMessage,
|
||||
ConnectMessage,
|
||||
ShowLovelaceViewMessage,
|
||||
GetStatusMessage,
|
||||
ShowDemoMessage,
|
||||
} from "../../../../src/cast/receiver_messages";
|
||||
import {
|
||||
LovelaceConfig,
|
||||
getLovelaceCollection,
|
||||
} from "../../../../src/data/lovelace";
|
||||
import "./hc-launch-screen";
|
||||
import { castContext } from "../cast_context";
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { CAST_NS } from "../../../../src/cast/const";
|
||||
import {
|
||||
ConnectMessage,
|
||||
GetStatusMessage,
|
||||
HassMessage,
|
||||
ShowDemoMessage,
|
||||
ShowLovelaceViewMessage,
|
||||
} from "../../../../src/cast/receiver_messages";
|
||||
import { ReceiverStatusMessage } from "../../../../src/cast/sender_messages";
|
||||
import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/load-resources";
|
||||
import { atLeastVersion } from "../../../../src/common/config/version";
|
||||
import { isNavigationClick } from "../../../../src/common/dom/is-navigation-click";
|
||||
import {
|
||||
fetchResources,
|
||||
getLegacyLovelaceCollection,
|
||||
getLovelaceCollection,
|
||||
LegacyLovelaceConfig,
|
||||
LovelaceConfig,
|
||||
} from "../../../../src/data/lovelace";
|
||||
import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/load-resources";
|
||||
import { HassElement } from "../../../../src/state/hass-element";
|
||||
import { castContext } from "../cast_context";
|
||||
import "./hc-launch-screen";
|
||||
|
||||
let resourcesLoaded = false;
|
||||
|
||||
@customElement("hc-main")
|
||||
export class HcMain extends HassElement {
|
||||
@property() private _showDemo = false;
|
||||
@internalProperty() private _showDemo = false;
|
||||
|
||||
@property() private _lovelaceConfig?: LovelaceConfig;
|
||||
@internalProperty() private _lovelaceConfig?: LovelaceConfig;
|
||||
|
||||
@property() private _lovelacePath: string | number | null = null;
|
||||
@internalProperty() private _lovelacePath: string | number | null = null;
|
||||
|
||||
@property() private _error?: string;
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
private _unsubLovelace?: UnsubscribeFunc;
|
||||
|
||||
private _urlPath?: string | null;
|
||||
|
||||
public processIncomingMessage(msg: HassMessage) {
|
||||
if (msg.type === "connect") {
|
||||
this._handleConnectMessage(msg);
|
||||
@@ -45,16 +58,14 @@ export class HcMain extends HassElement {
|
||||
} else if (msg.type === "show_demo") {
|
||||
this._handleShowDemo(msg);
|
||||
} else {
|
||||
// tslint:disable-next-line: no-console
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("unknown msg type", msg);
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
protected render(): TemplateResult {
|
||||
if (this._showDemo) {
|
||||
return html`
|
||||
<hc-demo .lovelacePath=${this._lovelacePath}></hc-demo>
|
||||
`;
|
||||
return html` <hc-demo .lovelacePath=${this._lovelacePath}></hc-demo> `;
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -76,6 +87,8 @@ export class HcMain extends HassElement {
|
||||
.hass=${this.hass}
|
||||
.lovelaceConfig=${this._lovelaceConfig}
|
||||
.viewPath=${this._lovelacePath}
|
||||
.urlPath=${this._urlPath}
|
||||
@config-refresh=${this._generateLovelaceConfig}
|
||||
></hc-lovelace>
|
||||
`;
|
||||
}
|
||||
@@ -84,15 +97,17 @@ export class HcMain extends HassElement {
|
||||
super.firstUpdated(changedProps);
|
||||
import("../second-load");
|
||||
window.addEventListener("location-changed", () => {
|
||||
if (location.pathname.startsWith("/lovelace/")) {
|
||||
this._lovelacePath = location.pathname.substr(10);
|
||||
const panelPath = `/${this._urlPath || "lovelace"}/`;
|
||||
if (location.pathname.startsWith(panelPath)) {
|
||||
this._lovelacePath = location.pathname.substr(panelPath.length);
|
||||
this._sendStatus();
|
||||
}
|
||||
});
|
||||
document.body.addEventListener("click", (ev) => {
|
||||
const panelPath = `/${this._urlPath || "lovelace"}/`;
|
||||
const href = isNavigationClick(ev);
|
||||
if (href && href.startsWith("/lovelace/")) {
|
||||
this._lovelacePath = href.substr(10);
|
||||
if (href && href.startsWith(panelPath)) {
|
||||
this._lovelacePath = href.substr(panelPath.length);
|
||||
this._sendStatus();
|
||||
}
|
||||
});
|
||||
@@ -108,6 +123,7 @@ export class HcMain extends HassElement {
|
||||
if (this.hass) {
|
||||
status.hassUrl = this.hass.auth.data.hassUrl;
|
||||
status.lovelacePath = this._lovelacePath!;
|
||||
status.urlPath = this._urlPath;
|
||||
}
|
||||
|
||||
if (senderId) {
|
||||
@@ -163,8 +179,17 @@ export class HcMain extends HassElement {
|
||||
this._error = "Cannot show Lovelace because we're not connected.";
|
||||
return;
|
||||
}
|
||||
if (!this._unsubLovelace) {
|
||||
const llColl = getLovelaceCollection(this.hass!.connection);
|
||||
if (msg.urlPath === "lovelace") {
|
||||
msg.urlPath = null;
|
||||
}
|
||||
if (!this._unsubLovelace || this._urlPath !== msg.urlPath) {
|
||||
this._urlPath = msg.urlPath;
|
||||
if (this._unsubLovelace) {
|
||||
this._unsubLovelace();
|
||||
}
|
||||
const llColl = atLeastVersion(this.hass.connection.haVersion, 0, 107)
|
||||
? getLovelaceCollection(this.hass!.connection, msg.urlPath)
|
||||
: getLegacyLovelaceCollection(this.hass!.connection);
|
||||
// We first do a single refresh because we need to check if there is LL
|
||||
// configuration.
|
||||
try {
|
||||
@@ -173,33 +198,40 @@ export class HcMain extends HassElement {
|
||||
this._handleNewLovelaceConfig(lovelaceConfig)
|
||||
);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.log("Error fetching Lovelace configuration", err, msg);
|
||||
// Generate a Lovelace config.
|
||||
this._unsubLovelace = () => undefined;
|
||||
const {
|
||||
generateLovelaceConfigFromHass,
|
||||
} = await import("../../../../src/panels/lovelace/common/generate-lovelace-config");
|
||||
this._handleNewLovelaceConfig(
|
||||
await generateLovelaceConfigFromHass(this.hass!)
|
||||
);
|
||||
await this._generateLovelaceConfig();
|
||||
}
|
||||
}
|
||||
if (!resourcesLoaded) {
|
||||
resourcesLoaded = true;
|
||||
const resources = atLeastVersion(this.hass.connection.haVersion, 0, 107)
|
||||
? await fetchResources(this.hass!.connection)
|
||||
: (this._lovelaceConfig as LegacyLovelaceConfig).resources;
|
||||
if (resources) {
|
||||
loadLovelaceResources(resources, this.hass!.auth.data.hassUrl);
|
||||
}
|
||||
}
|
||||
this._showDemo = false;
|
||||
this._lovelacePath = msg.viewPath;
|
||||
if (castContext.getDeviceCapabilities().touch_input_supported) {
|
||||
this._breakFree();
|
||||
}
|
||||
|
||||
this._sendStatus();
|
||||
}
|
||||
|
||||
private async _generateLovelaceConfig() {
|
||||
const { generateLovelaceConfigFromHass } = await import(
|
||||
"../../../../src/panels/lovelace/common/generate-lovelace-config"
|
||||
);
|
||||
this._handleNewLovelaceConfig(
|
||||
await generateLovelaceConfigFromHass(this.hass!)
|
||||
);
|
||||
}
|
||||
|
||||
private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) {
|
||||
castContext.setApplicationState(lovelaceConfig.title!);
|
||||
this._lovelaceConfig = lovelaceConfig;
|
||||
if (lovelaceConfig.resources) {
|
||||
loadLovelaceResources(
|
||||
lovelaceConfig.resources,
|
||||
this.hass!.auth.data.hassUrl
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleShowDemo(_msg: ShowDemoMessage) {
|
||||
@@ -207,9 +239,6 @@ export class HcMain extends HassElement {
|
||||
this._showDemo = true;
|
||||
this._lovelacePath = "overview";
|
||||
this._sendStatus();
|
||||
if (castContext.getDeviceCapabilities().touch_input_supported) {
|
||||
this._breakFree();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -230,14 +259,6 @@ export class HcMain extends HassElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _breakFree() {
|
||||
const controls = document.body.querySelector("touch-controls");
|
||||
if (controls) {
|
||||
controls.remove();
|
||||
}
|
||||
document.body.setAttribute("style", "overflow-y: auto !important");
|
||||
}
|
||||
|
||||
private sendMessage(senderId: string, response: any) {
|
||||
castContext.sendCustomMessage(CAST_NS, senderId, response);
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "web-animations-js/web-animations-next-lite.min";
|
||||
import "../../../src/resources/hass-icons";
|
||||
import "../../../src/resources/roboto";
|
||||
import "../../../src/components/ha-iconset-svg";
|
||||
import "../../../src/resources/ha-style";
|
||||
import "./layout/hc-lovelace";
|
||||
|
@@ -1,11 +1,8 @@
|
||||
const { createCastConfig } = require("../build-scripts/webpack.js");
|
||||
const { isProdBuild } = require("../build-scripts/env.js");
|
||||
|
||||
// File just used for stats builds
|
||||
|
||||
const latestBuild = true;
|
||||
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
|
||||
|
||||
module.exports = createCastConfig({
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isProdBuild: isProdBuild(),
|
||||
isStatsBuild: isStatsBuild(),
|
||||
latestBuild: true,
|
||||
});
|
||||
|
Before Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 9.6 KiB |