mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-08-24 22:13:33 +00:00
Compare commits
2609 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
018cb91526 | ||
|
e16db2233f | ||
|
5d90a08011 | ||
|
2d3849a671 | ||
|
e0dcdc2110 | ||
|
22fcee2a16 | ||
|
8c0141367b | ||
|
6d829fa575 | ||
|
c9d0bd2d7f | ||
|
ea31d34649 | ||
|
7a3ea37798 | ||
|
fdd389d6b7 | ||
|
b91fccc0e3 | ||
|
d911b075ab | ||
|
4339cdd8a4 | ||
|
dd32d3a492 | ||
|
c4bc3e2687 | ||
|
2c5909a138 | ||
|
42c13fa584 | ||
|
3b442d4bfc | ||
|
9831f81b6e | ||
|
d3282506c9 | ||
|
2afff6c432 | ||
|
be3616abe2 | ||
|
daa6f5051c | ||
|
1e5bd98f02 | ||
|
7e5bfa8dd2 | ||
|
53363b0618 | ||
|
b2f71a2ce1 | ||
|
5d4a575919 | ||
|
7d521ed3ce | ||
|
bb9ad3daa9 | ||
|
442f270ee0 | ||
|
7ab74c6cc9 | ||
|
6d60baa2d6 | ||
|
6d3308621f | ||
|
fd49be1d9b | ||
|
c40a5648dd | ||
|
d9a8d2627a | ||
|
c7a88e2f12 | ||
|
963b1aa6b1 | ||
|
008ac2876b | ||
|
2330b166f6 | ||
|
23c0e01565 | ||
|
13073bc98d | ||
|
8c319903dd | ||
|
2334cbd78a | ||
|
0cae954f80 | ||
|
c60446a015 | ||
|
d0c6a4ee6d | ||
|
f2d03a511e | ||
|
367233c318 | ||
|
9461c1692a | ||
|
3b32605b5e | ||
|
4d21f8d022 | ||
|
aac67570d4 | ||
|
5f2c465274 | ||
|
fdaa0bc876 | ||
|
a692d6be09 | ||
|
efbb9648c4 | ||
|
3d73153e59 | ||
|
8fa2256fb0 | ||
|
4fe974e7a8 | ||
|
21f76a8f27 | ||
|
aeb287fa1d | ||
|
1405e8821c | ||
|
37e31bac5b | ||
|
d31b696846 | ||
|
cc01c1f0db | ||
|
4a7076e01c | ||
|
d306bb25dc | ||
|
457c80fe76 | ||
|
bb972f8449 | ||
|
e6ef64968b | ||
|
6f3b87cfd1 | ||
|
f449feb3f8 | ||
|
b179c8e2b7 | ||
|
6aa0a4a47f | ||
|
766140f483 | ||
|
52aa8b868a | ||
|
8a3a4d6fae | ||
|
94f212a411 | ||
|
4e1dce70a3 | ||
|
bbf48a0dd0 | ||
|
a35e8f3315 | ||
|
205de7233e | ||
|
abb5dc5739 | ||
|
3a5a29efc0 | ||
|
c202c0d705 | ||
|
8a1f9b7de6 | ||
|
18820c383d | ||
|
b38b879ee3 | ||
|
7fc7d626bc | ||
|
c8a0f1d0de | ||
|
c8aedc6f29 | ||
|
1e7ce8bb5c | ||
|
5386ca1592 | ||
|
08ed019426 | ||
|
55c6a5aa27 | ||
|
bd6bbf864a | ||
|
703335c2f1 | ||
|
c04fa496bf | ||
|
b9d19cfcb4 | ||
|
d8f093b226 | ||
|
63a2d83d26 | ||
|
7e42e98cb6 | ||
|
93deebe923 | ||
|
1eecf4b2c7 | ||
|
e7fc4ef1e7 | ||
|
3c54d4af15 | ||
|
ebfbf0e1f8 | ||
|
14aa4036e0 | ||
|
0c60d54c3f | ||
|
e510d279a2 | ||
|
be6f1f9c4a | ||
|
b1fb177e4b | ||
|
ec187fe109 | ||
|
9ec329b7ae | ||
|
d08bd4e866 | ||
|
941d5d7cd9 | ||
|
ef8d85773c | ||
|
e668d488b4 | ||
|
d4eb5facfd | ||
|
a1285b120b | ||
|
8650876e2b | ||
|
81ab12c89c | ||
|
4ddb8cec85 | ||
|
711c566498 | ||
|
cc5336900c | ||
|
291a8f5c1f | ||
|
8c0e98ed43 | ||
|
7a976c0239 | ||
|
fb557b9130 | ||
|
b56a930f60 | ||
|
5819ef346b | ||
|
b3dcc82c71 | ||
|
06e8308dc2 | ||
|
c8a9c9b84e | ||
|
e85b49c573 | ||
|
c7c6dc4e67 | ||
|
172d668416 | ||
|
b651dc845b | ||
|
c755f44153 | ||
|
ed28ce1874 | ||
|
194af0e985 | ||
|
ab3015df6b | ||
|
f36df159e0 | ||
|
61462cf57e | ||
|
427d186c86 | ||
|
84e9c47a67 | ||
|
18989d593a | ||
|
fc624359c5 | ||
|
1914573634 | ||
|
1fd36458a6 | ||
|
37ee0e568f | ||
|
3f8363a5b2 | ||
|
e6c0011789 | ||
|
eb37a495a2 | ||
|
54542f7f07 | ||
|
8e38910dd8 | ||
|
31b3f778fc | ||
|
ca3275757b | ||
|
501a314597 | ||
|
447c0bffdc | ||
|
46df59d77a | ||
|
8c85e240b7 | ||
|
2464d01891 | ||
|
66642a19c5 | ||
|
f7f4e92e0a | ||
|
2674bf22d8 | ||
|
351e0f3a0b | ||
|
ff7dc95e93 | ||
|
4e4d3418b3 | ||
|
3659b5b5c9 | ||
|
65e1b60fb7 | ||
|
e2427fe299 | ||
|
b08f882324 | ||
|
2a31ece0c6 | ||
|
cafa4211a6 | ||
|
82b50d3059 | ||
|
39cb582e07 | ||
|
fe6645a420 | ||
|
a49816f4eb | ||
|
7ac97f854c | ||
|
8f203852ca | ||
|
c342f7bce6 | ||
|
fe1c5df9d5 | ||
|
8aa7a55559 | ||
|
e9461586cb | ||
|
a91f2de26a | ||
|
5a4ae99283 | ||
|
437d030502 | ||
|
f3d45eff69 | ||
|
f22e39e22b | ||
|
7470a3b813 | ||
|
0b2e836e3d | ||
|
c3d57eef4f | ||
|
ca2571b438 | ||
|
6c926b8876 | ||
|
5517a913d4 | ||
|
ef7720ff06 | ||
|
daa13b4b30 | ||
|
9d4e237c09 | ||
|
883c169ef7 | ||
|
2b2b07f6b4 | ||
|
f338b68f36 | ||
|
c877b5fc70 | ||
|
6aeec81072 | ||
|
3f78c664eb | ||
|
3b7df1d5c2 | ||
|
b6fc063c75 | ||
|
7b56817ae6 | ||
|
75c8c3f50b | ||
|
be07ff2129 | ||
|
56846b258c | ||
|
f67310c8fa | ||
|
3dff3a1d4a | ||
|
142d30b1c6 | ||
|
c925e88475 | ||
|
56392e41d0 | ||
|
cd7b6450c6 | ||
|
de6a9f5811 | ||
|
d7295948fd | ||
|
f4c3e412a2 | ||
|
0d461e14d9 | ||
|
845c2571fe | ||
|
d5c02d1031 | ||
|
30e93a9372 | ||
|
e5cdcd190c | ||
|
271f1cd71b | ||
|
4abb790e12 | ||
|
89c9d4dfc4 | ||
|
5511c15921 | ||
|
e160f9a0f6 | ||
|
65ea341ed0 | ||
|
561827e896 | ||
|
1315aceb44 | ||
|
19afd89df5 | ||
|
cbcc893f64 | ||
|
a41bae3132 | ||
|
8ef8388c32 | ||
|
e2d4a0fde8 | ||
|
65b1d61295 | ||
|
1b38e73eb2 | ||
|
e2afee3275 | ||
|
7f2ecbd04f | ||
|
21dbaa1a03 | ||
|
09c1ea992c | ||
|
c8c327b6ab | ||
|
3bb801f579 | ||
|
ca49d8adb3 | ||
|
88b15797a0 | ||
|
28ebbd229c | ||
|
d7f6f46805 | ||
|
c41d6965ab | ||
|
602119fa41 | ||
|
4c2a9b47f5 | ||
|
5a26fefd6b | ||
|
85d26f7320 | ||
|
9487af1852 | ||
|
a5b09d7846 | ||
|
6ba5056c96 | ||
|
50b4d05ef5 | ||
|
13ebe6d0e1 | ||
|
95dedf0d80 | ||
|
89a9368166 | ||
|
c10e6159da | ||
|
c30693b8a8 | ||
|
7fc4f61182 | ||
|
b44ab88927 | ||
|
2ca81f965a | ||
|
9f4dc1e382 | ||
|
5ec9704e7f | ||
|
7ad0cfd5f6 | ||
|
ac8f679dfd | ||
|
a7ee2ef3a6 | ||
|
6e7edd9824 | ||
|
81b310086f | ||
|
cce57c7229 | ||
|
9cffa3dd67 | ||
|
7b3a59455d | ||
|
fefe5659d3 | ||
|
db72e22e61 | ||
|
a85ed709af | ||
|
e24d1a1261 | ||
|
d6f1bf18de | ||
|
97e5d923fa | ||
|
36762a6e46 | ||
|
40a9f9dd85 | ||
|
85a52523cf | ||
|
5ed7974099 | ||
|
70ca9ce2e0 | ||
|
af9f555e8f | ||
|
08b9e7b5b5 | ||
|
84f74c53b5 | ||
|
762bae907c | ||
|
2b738fa14b | ||
|
8aa745471b | ||
|
63950f572a | ||
|
81aab7e86f | ||
|
1fd5fd4832 | ||
|
ceaf478a84 | ||
|
61256f98ca | ||
|
818ed5d189 | ||
|
0ca00e5059 | ||
|
a2c743167d | ||
|
786641662c | ||
|
8380896deb | ||
|
1152689f34 | ||
|
d2afdc82f1 | ||
|
5bf90ae31d | ||
|
b533e3d3e1 | ||
|
4d9df309c8 | ||
|
65126365a6 | ||
|
0afe403057 | ||
|
d500353722 | ||
|
eea8b4dfbf | ||
|
20a9150ea3 | ||
|
c51e613568 | ||
|
69a887fb05 | ||
|
01c1d150c6 | ||
|
c51c09348e | ||
|
0a4df191a7 | ||
|
760995773e | ||
|
94ff8a9b04 | ||
|
10d5529f79 | ||
|
2b511b67c3 | ||
|
b595cdd31e | ||
|
e64870ca58 | ||
|
a40f8d5b3a | ||
|
61b8871ead | ||
|
4b5a3ed44d | ||
|
7d6b7b2691 | ||
|
3dba46b74f | ||
|
2767e31a28 | ||
|
40886bcf08 | ||
|
04dde74572 | ||
|
29f3ac065f | ||
|
d2b30b4f9c | ||
|
3a42663dea | ||
|
905d0ca409 | ||
|
7e2c78666e | ||
|
6a5f0225fe | ||
|
ddb01fca31 | ||
|
684a3cd49e | ||
|
ad5e42115b | ||
|
2d6b8d0c47 | ||
|
61471e5449 | ||
|
b6f7ab7a9c | ||
|
2d061be98e | ||
|
d6e97b8c76 | ||
|
9e44660746 | ||
|
03e6ca58ab | ||
|
e884cc1f75 | ||
|
94c63b0554 | ||
|
c7c8c40a70 | ||
|
91c726c706 | ||
|
c9805e7ac9 | ||
|
a4db0e40e3 | ||
|
305b55cb2a | ||
|
272ca1ac4f | ||
|
bbc6a1b38f | ||
|
525c235d3b | ||
|
0e93713464 | ||
|
8f60e103f9 | ||
|
9cc702241d | ||
|
0d61c44232 | ||
|
9a8faac316 | ||
|
05f710cb5c | ||
|
c6184769e7 | ||
|
515d1bd920 | ||
|
b19cb17bba | ||
|
c1e808bce6 | ||
|
d8b7292d4b | ||
|
edd5e2f5bc | ||
|
efc6cb73b2 | ||
|
ecd79dc34b | ||
|
cdfc8b825d | ||
|
301344c96d | ||
|
5279995c3b | ||
|
5176b06b59 | ||
|
ed61ac624d | ||
|
4da6e3ecee | ||
|
9e5561936a | ||
|
843cd0eff6 | ||
|
6b6ee934a1 | ||
|
aea57ffaf4 | ||
|
87b48661fa | ||
|
556fa44858 | ||
|
1c3e196508 | ||
|
9b6812ad0c | ||
|
b7944c7fa4 | ||
|
2fbedca746 | ||
|
340d04a48c | ||
|
460a383ffc | ||
|
52c4e3a256 | ||
|
4338f11eb1 | ||
|
42bab052e0 | ||
|
0cb377618e | ||
|
5e4d25b957 | ||
|
153d1853fa | ||
|
22a3448461 | ||
|
2cad869680 | ||
|
bc912a8ea4 | ||
|
db9176c284 | ||
|
06308210c0 | ||
|
63d9904370 | ||
|
caaeff5cb7 | ||
|
d8c93d3455 | ||
|
cb28e5fddc | ||
|
7d7ec1a00b | ||
|
a5bc8dfa3f | ||
|
d74f055180 | ||
|
73be6c35a6 | ||
|
24f74e1400 | ||
|
8900e069f3 | ||
|
1dfec119bb | ||
|
9e23d35f01 | ||
|
cd2b240308 | ||
|
749f366a4a | ||
|
420aaa92fa | ||
|
985698bbc3 | ||
|
f9c9139f20 | ||
|
f308e7541f | ||
|
cb7ccd7854 | ||
|
a4953028d0 | ||
|
3d6485588d | ||
|
51bb9fede7 | ||
|
269c429959 | ||
|
fcdc84a12a | ||
|
ecdd9734eb | ||
|
84fc2c65af | ||
|
c8849a17b6 | ||
|
d8b49218e9 | ||
|
e95023e8cc | ||
|
878710e2cf | ||
|
0677d0a810 | ||
|
9c98fea8f4 | ||
|
e958f33450 | ||
|
937080b011 | ||
|
aee5803dd2 | ||
|
f1f394b871 | ||
|
81a32b56f0 | ||
|
a6aae70a55 | ||
|
823eb23773 | ||
|
1ff51822df | ||
|
500147e130 | ||
|
d90de18d99 | ||
|
ba4f48662f | ||
|
d208437c05 | ||
|
01faa2e1d7 | ||
|
c630c387d6 | ||
|
a774718607 | ||
|
9430c70d0d | ||
|
f3e893fddb | ||
|
11e144ca64 | ||
|
fbceab707e | ||
|
5b2efc43b9 | ||
|
8d85d1aa2d | ||
|
b469fc7577 | ||
|
9db54831c8 | ||
|
9b88bde09a | ||
|
aad03a74c5 | ||
|
55eb6e2e5c | ||
|
ca07355873 | ||
|
11a59e26b2 | ||
|
ec6d9e3521 | ||
|
230accd31e | ||
|
a24a4a747e | ||
|
1e97b5c27a | ||
|
a314ea1aa3 | ||
|
a77128d5f7 | ||
|
1b2673367e | ||
|
ce10e91a60 | ||
|
5244b37d2c | ||
|
1239c6716b | ||
|
67f6258ab0 | ||
|
9f63172b43 | ||
|
bd8bfeb525 | ||
|
4918c4ef4b | ||
|
00d9ea9344 | ||
|
33537cde76 | ||
|
3d5db5c9ca | ||
|
fdf339514d | ||
|
937c4e485a | ||
|
837c060e1f | ||
|
00bacd7dde | ||
|
781031775e | ||
|
97aee3d375 | ||
|
34698751f2 | ||
|
3c31460f2f | ||
|
d5cb60b19c | ||
|
3a7cfe3208 | ||
|
f079cdad64 | ||
|
2822303138 | ||
|
e38de75520 | ||
|
2723604d3e | ||
|
82ee051c1a | ||
|
7bdf49b7e0 | ||
|
32521aba6b | ||
|
819c4cde1c | ||
|
776c486b1a | ||
|
bcd97120a4 | ||
|
312bfb8509 | ||
|
11c9a50931 | ||
|
94c0656bcd | ||
|
13313d0b25 | ||
|
3a20db1d76 | ||
|
00148b4cc8 | ||
|
a31546b1ff | ||
|
361b62b8e2 | ||
|
37327b77a7 | ||
|
7ef8a5bb11 | ||
|
11cfb8af32 | ||
|
7315f7d283 | ||
|
7d58eb718e | ||
|
651be76776 | ||
|
4a5c6f1d39 | ||
|
5745d71d6a | ||
|
db62b7421a | ||
|
4084c57789 | ||
|
f90bec985a | ||
|
90f911c529 | ||
|
6c88b106db | ||
|
cef69d1b97 | ||
|
f76a7fb331 | ||
|
3e7b8b0663 | ||
|
a6eb3ad037 | ||
|
9468749384 | ||
|
37417fa1bb | ||
|
217146351e | ||
|
818ec33cef | ||
|
cd1671830a | ||
|
a5fca87dd0 | ||
|
f06ce55626 | ||
|
853085e755 | ||
|
2b7accaf68 | ||
|
5533d93172 | ||
|
f0e8c865fe | ||
|
36400c0a83 | ||
|
c5383557b5 | ||
|
b645007884 | ||
|
63ac137206 | ||
|
808cbf8e0b | ||
|
8ed77ba0c7 | ||
|
66c74c51e4 | ||
|
7a272ef0ab | ||
|
60b817ec8e | ||
|
a41ecaf7cc | ||
|
d41afa0e53 | ||
|
a6284e05e5 | ||
|
e56f61441d | ||
|
7b4b7dffa2 | ||
|
77a214ef9c | ||
|
cf2723aafb | ||
|
499e99cfc5 | ||
|
a7b83e9fe3 | ||
|
964504b9c3 | ||
|
ec65e66c58 | ||
|
e694b080be | ||
|
70894b3938 | ||
|
fb7115fc13 | ||
|
a619fc4fef | ||
|
7c6c5fd06f | ||
|
2970568eab | ||
|
62cb3a610e | ||
|
f600c163ca | ||
|
77cb68e5ac | ||
|
c6314576aa | ||
|
515c183070 | ||
|
63b9c0e6b8 | ||
|
84893b1664 | ||
|
835668d96d | ||
|
2bce15dc6e | ||
|
8f1a212b52 | ||
|
5c08bde0fa | ||
|
98a84c031e | ||
|
ea1715384e | ||
|
d9a4ee4f65 | ||
|
62017c4661 | ||
|
702b98f510 | ||
|
69aafd7d6a | ||
|
c1559dd8c8 | ||
|
4df1895560 | ||
|
cac92da6e4 | ||
|
5d39d85215 | ||
|
99b4c43fd5 | ||
|
b2f59d6813 | ||
|
c7d79bb893 | ||
|
aa80c468c4 | ||
|
6008cba2db | ||
|
caf56671dc | ||
|
44eccf5ee4 | ||
|
8f96e4847c | ||
|
ae3e307f33 | ||
|
3fe0c758ed | ||
|
7240fb32d2 | ||
|
e23a3461ba | ||
|
727eb0cfd7 | ||
|
3796076360 | ||
|
ef9576f8c4 | ||
|
94fc4cb8a2 | ||
|
ccb248db91 | ||
|
e7de447725 | ||
|
7ff5429cb7 | ||
|
17c581b4aa | ||
|
41e5c2939f | ||
|
7e2ab51298 | ||
|
03f917fd9c | ||
|
d24e10a728 | ||
|
a17ac1c16e | ||
|
dd6b972be4 | ||
|
7d0c9ba0d9 | ||
|
6fa211634f | ||
|
1ca24c7f38 | ||
|
bcfbccae59 | ||
|
20b75ce6ed | ||
|
2ea15d7bf5 | ||
|
18e14c597f | ||
|
06d75999d7 | ||
|
7047a7cae6 | ||
|
20d2124867 | ||
|
3c7a85361e | ||
|
1ffbbdac99 | ||
|
eeccca8842 | ||
|
e2d2dbd2ba | ||
|
c61f0409fb | ||
|
806be39a6d | ||
|
6170b0d059 | ||
|
bca838495e | ||
|
e31a747250 | ||
|
4cf430e146 | ||
|
ef554cf6ec | ||
|
fcd91daee6 | ||
|
396c78b46a | ||
|
4677a3fd89 | ||
|
834ab5c6b9 | ||
|
1599e8f7ff | ||
|
7430704002 | ||
|
4d7b19c8cb | ||
|
40f535cf3c | ||
|
17425dcaf7 | ||
|
6b87fc64af | ||
|
35174b0348 | ||
|
fd53541719 | ||
|
7c68bff9f5 | ||
|
6c64991951 | ||
|
ca04ff0f37 | ||
|
7742575cab | ||
|
cdcdce702d | ||
|
c80e04fe8d | ||
|
c3b3ea107a | ||
|
1dc530c549 | ||
|
8b0b70e757 | ||
|
b508a629e8 | ||
|
abb0dadead | ||
|
e6fb18df56 | ||
|
43ba13f3bc | ||
|
ba705f5563 | ||
|
34e188ec1f | ||
|
b0d97dd170 | ||
|
7caeae61f5 | ||
|
416ace4c86 | ||
|
979041ee91 | ||
|
f0939b8af5 | ||
|
d7a7002bdd | ||
|
d9601de075 | ||
|
ef570558cf | ||
|
db3e81408f | ||
|
e8771cdea8 | ||
|
057eab2173 | ||
|
e2a7024eeb | ||
|
4650986dfa | ||
|
868b5e4617 | ||
|
f12860c7b1 | ||
|
8f751812a6 | ||
|
07a5092eb3 | ||
|
29c9c92ba6 | ||
|
edfa327158 | ||
|
869a6e66cc | ||
|
36abbfc048 | ||
|
cfc3e6d2f4 | ||
|
e38dbee6a6 | ||
|
68a7c857c0 | ||
|
80eef2ab8c | ||
|
1d652aa746 | ||
|
b07c43aa36 | ||
|
3880c8dc2c | ||
|
c0ab2ac297 | ||
|
de684dcb63 | ||
|
29ef1db86b | ||
|
5dfd8a61be | ||
|
358e2b3ccf | ||
|
4203065a06 | ||
|
bc4e0190a0 | ||
|
dd7004cbc9 | ||
|
bdc5c8f620 | ||
|
02d36e22ee | ||
|
331e8c4aa6 | ||
|
d622277c11 | ||
|
8f781ea4ab | ||
|
ebc1e5bf12 | ||
|
ce9a61622e | ||
|
e4891e699f | ||
|
d8765578c8 | ||
|
3a034ecec8 | ||
|
a3dea45089 | ||
|
de99c8a5e4 | ||
|
d3b8dbeea0 | ||
|
cff2f64155 | ||
|
7b8de35405 | ||
|
02ae0df2cc | ||
|
b386cea69d | ||
|
758ffb75a9 | ||
|
78fbc7f392 | ||
|
9c58472576 | ||
|
f8c4afc228 | ||
|
b169d65619 | ||
|
9bf0d4f804 | ||
|
4443f57f8a | ||
|
ea5d8590d5 | ||
|
5d5feb4c71 | ||
|
feb5351ec3 | ||
|
7630c25ef3 | ||
|
24238094e5 | ||
|
7cc9a03db8 | ||
|
2b2e8508d9 | ||
|
a70716f225 | ||
|
d9fcc46994 | ||
|
2d8acec6f0 | ||
|
cd06d8c63a | ||
|
a06ca55107 | ||
|
9686a9ba77 | ||
|
f7f4043ccd | ||
|
b7b55173a6 | ||
|
954253c7e2 | ||
|
cbe4d2cd7f | ||
|
40101129b5 | ||
|
4bb32c6d09 | ||
|
6e09ceeda6 | ||
|
d6a6a53623 | ||
|
4a97052708 | ||
|
3a4902ad4a | ||
|
77d14bc218 | ||
|
1d2a39a855 | ||
|
98b53b6b3d | ||
|
0148d8beaf | ||
|
5bfd84d3be | ||
|
351eb95feb | ||
|
56788f0933 | ||
|
017a376616 | ||
|
c5888cec66 | ||
|
3d5ad29eac | ||
|
c608636b7a | ||
|
1a97107b2d | ||
|
5ca3fbeaea | ||
|
44896db668 | ||
|
12efb87a23 | ||
|
9181be86ba | ||
|
053b01e036 | ||
|
86041d0968 | ||
|
bd87f63e91 | ||
|
b79b49e8f3 | ||
|
a0dde39d97 | ||
|
2e03868021 | ||
|
29384c2ba3 | ||
|
320743ab8d | ||
|
399e171083 | ||
|
184164b677 | ||
|
a8bd196234 | ||
|
239d425940 | ||
|
baa3c1461c | ||
|
fa8e398e90 | ||
|
6d9675a299 | ||
|
91e8ce62d4 | ||
|
06e641015f | ||
|
1c83059482 | ||
|
90b24d824a | ||
|
62457d0e48 | ||
|
90c96f7479 | ||
|
f87adebe41 | ||
|
8f24cc8d13 | ||
|
992802d196 | ||
|
8546d6730c | ||
|
0092289105 | ||
|
7c3923ad00 | ||
|
ef82039401 | ||
|
b01b9758e0 | ||
|
88b00f689b | ||
|
0a340d5d57 | ||
|
50545a83b8 | ||
|
4a57ff40d8 | ||
|
0238455a5a | ||
|
a53e963001 | ||
|
984608e23f | ||
|
f680c83d2d | ||
|
a79e51c76f | ||
|
2dfb349609 | ||
|
766f21b525 | ||
|
733dfa1467 | ||
|
63aa840b55 | ||
|
8b2d544576 | ||
|
d6046d2422 | ||
|
409939360f | ||
|
1d21f39fbc | ||
|
a477140a4b | ||
|
1bbf2d8ce6 | ||
|
40a65eec51 | ||
|
8431ebf2e8 | ||
|
bdcc0c5373 | ||
|
9cbf331533 | ||
|
77640714cc | ||
|
9457d95c3f | ||
|
c2ff949f2d | ||
|
ba8685a122 | ||
|
55464ed0dd | ||
|
fdf3691c87 | ||
|
aa6699cf3e | ||
|
b79b48baac | ||
|
5d22dbd99e | ||
|
4686bb5584 | ||
|
1f62b8f0b6 | ||
|
5759ed3728 | ||
|
827fbfb78f | ||
|
dc363de610 | ||
|
b9d6a235e3 | ||
|
ebc57fe494 | ||
|
e224ec4ae0 | ||
|
a5da347177 | ||
|
a257b15f86 | ||
|
a70cc53d82 | ||
|
1df2de9202 | ||
|
9e394ea349 | ||
|
b55685d610 | ||
|
2156aac046 | ||
|
6914465e3d | ||
|
f3847ec6f3 | ||
|
e1fe8d1d89 | ||
|
675c937a4a | ||
|
c8f53bdf8e | ||
|
3541d5adde | ||
|
b52da7c9fc | ||
|
de57daa3cd | ||
|
e70e011a9c | ||
|
99febb99f1 | ||
|
874d79be36 | ||
|
b33663c9f8 | ||
|
7e69fa39eb | ||
|
de06490539 | ||
|
40a30c24a0 | ||
|
de04c12d3c | ||
|
38fb53b058 | ||
|
ed617c5943 | ||
|
986337da0c | ||
|
8dd7621f29 | ||
|
88d862303d | ||
|
cc274ffebe | ||
|
28a108f79b | ||
|
b9f75bf7d2 | ||
|
39994d5797 | ||
|
8e28be6558 | ||
|
8a65bef004 | ||
|
b94dc5044b | ||
|
b853c00dd4 | ||
|
7a0bc81f48 | ||
|
10bc326490 | ||
|
1920f8158e | ||
|
0ed2ba0183 | ||
|
95adc0aec1 | ||
|
ebee80d10e | ||
|
63836185d9 | ||
|
d0195e0509 | ||
|
5d9bcd9918 | ||
|
db04c26d24 | ||
|
56b399655e | ||
|
24e15c0568 | ||
|
f0c516e82d | ||
|
f38203ef62 | ||
|
a77c026803 | ||
|
5b6306671c | ||
|
25610222bc | ||
|
c17f941fb9 | ||
|
ae6ab1d203 | ||
|
92accf99b4 | ||
|
b02702fe80 | ||
|
5c549ec6e5 | ||
|
af459a5a28 | ||
|
07770601f6 | ||
|
cc96b86b3a | ||
|
1547f4d8b2 | ||
|
75054fcc70 | ||
|
390b3b173b | ||
|
78daa65d28 | ||
|
3bbdd08d24 | ||
|
cec1f12918 | ||
|
d923ae2107 | ||
|
85931155e6 | ||
|
4fd87aca09 | ||
|
600e0ec7e3 | ||
|
51fbff1a4a | ||
|
03b1389ee5 | ||
|
8f014e9d82 | ||
|
cecc6f7561 | ||
|
62ba81c6a6 | ||
|
c5e3422fcd | ||
|
bd5a46b4ab | ||
|
3a972bbbab | ||
|
7768ea28bd | ||
|
75add44e86 | ||
|
7d94365cbf | ||
|
2d830fb8e7 | ||
|
633bf36fe7 | ||
|
c0a5e23d95 | ||
|
d3798344dd | ||
|
ed37460402 | ||
|
9b6ba65cdb | ||
|
42a9631926 | ||
|
676a8a6421 | ||
|
cdbf022ce0 | ||
|
db79e1271e | ||
|
d2b3efacf9 | ||
|
a2ab94f971 | ||
|
a0d92d764b | ||
|
649b78e3f2 | ||
|
e7df1c3e56 | ||
|
53833ae0c3 | ||
|
66b914774a | ||
|
fc89feec4e | ||
|
d311dbd9d5 | ||
|
f97aa67100 | ||
|
9a8add780c | ||
|
3b48f1d042 | ||
|
332b54e7a5 | ||
|
007b2f0c88 | ||
|
3f083862e7 | ||
|
39619d5277 | ||
|
d4fe01f9b9 | ||
|
6db61b4357 | ||
|
f245cbf7f2 | ||
|
6f2b04669f | ||
|
9a46081d0b | ||
|
3c7e507ca1 | ||
|
7117725e69 | ||
|
ba428c6cfe | ||
|
d76c924ad1 | ||
|
cad7debc5b | ||
|
40725aa2a2 | ||
|
6034891fed | ||
|
9dd9862d33 | ||
|
48c72e319b | ||
|
4b6208fd9c | ||
|
168904a159 | ||
|
28f1498ec3 | ||
|
d3028e10d3 | ||
|
5eb0b77a8a | ||
|
4aace5b95a | ||
|
044dbd4a65 | ||
|
5dbae7c9d7 | ||
|
ec44cb2761 | ||
|
b06cf55c0b | ||
|
b601f6a138 | ||
|
ddebc63488 | ||
|
35440822be | ||
|
f4c6bcfb8e | ||
|
6365c5c9ef | ||
|
dd0334d30d | ||
|
c462a44973 | ||
|
6f88f5db83 | ||
|
93617f62a2 | ||
|
61d5f39408 | ||
|
c1fddaa7dd | ||
|
188aa14d82 | ||
|
5c25dd5b6d | ||
|
7c579cf7b7 | ||
|
c755c823fa | ||
|
116588c237 | ||
|
f02c1e4dc7 | ||
|
c4e8cc1641 | ||
|
93e68ad147 | ||
|
7ba88a83f0 | ||
|
c9293327ce | ||
|
fa1f35a89e | ||
|
845ce7a711 | ||
|
2b40007563 | ||
|
217034c4a7 | ||
|
0b9d4f17ab | ||
|
7fb0ec12dd | ||
|
3581158a7b | ||
|
facfa73214 | ||
|
0ef4a86d42 | ||
|
d4ec4795c3 | ||
|
b13d0aa283 | ||
|
a6965342e7 | ||
|
752dfa5b7f | ||
|
87aa283f22 | ||
|
6598ae080f | ||
|
7c5e8a66e4 | ||
|
c9577bcdc5 | ||
|
8254c2e83c | ||
|
20a9ac841d | ||
|
ae86b75d89 | ||
|
93a0afe612 | ||
|
439027220b | ||
|
4a07272d7a | ||
|
81432b54a3 | ||
|
b84a6e0c02 | ||
|
37dc5a00e8 | ||
|
e6edf85fbe | ||
|
cb533a26f2 | ||
|
a7278f76a8 | ||
|
80bd32382f | ||
|
db21ced104 | ||
|
717c6555cb | ||
|
a412e4af5c | ||
|
3350bf1ac6 | ||
|
4aa3353a1d | ||
|
ff48a58537 | ||
|
08fa511d17 | ||
|
c295115ffc | ||
|
5fb14610ec | ||
|
e87c2350b7 | ||
|
44e691e840 | ||
|
b5a7234cf3 | ||
|
d12509957f | ||
|
f01e7b7e20 | ||
|
6aa156d956 | ||
|
ef5ea93de1 | ||
|
b4913f51f2 | ||
|
dc3e960e79 | ||
|
0fe79b5288 | ||
|
1f76bd1942 | ||
|
3545f80920 | ||
|
0b2d1564ef | ||
|
fdacf824b3 | ||
|
5c01a44644 | ||
|
4eb49d872b | ||
|
34e5f29419 | ||
|
f4910f0a8e | ||
|
c8c14611dc | ||
|
491201991e | ||
|
401f3574fd | ||
|
173a86172c | ||
|
9ecbff024a | ||
|
1b5be34be4 | ||
|
ceb3a997b6 | ||
|
b1ead7fec8 | ||
|
d534dbb006 | ||
|
e56377117b | ||
|
63483dc6c3 | ||
|
66ceafd010 | ||
|
dd793650c3 | ||
|
afd829307d | ||
|
09abdc0f12 | ||
|
ed4d17f578 | ||
|
aeeeb5a37b | ||
|
dcb2e51587 | ||
|
cbc2eaf908 | ||
|
8808031e7c | ||
|
23ac7213d3 | ||
|
add7b44d0b | ||
|
1e4b7599a7 | ||
|
54443b038a | ||
|
c3f03e3f95 | ||
|
18135624f6 | ||
|
11238d6b71 | ||
|
848f94b1e0 | ||
|
d47cfe9504 | ||
|
70dccff293 | ||
|
e40873710b | ||
|
b140ef3b7a | ||
|
3b7b74aa67 | ||
|
de8e5b2d69 | ||
|
afea33b0e3 | ||
|
b44fbc1e4f | ||
|
c4dee3dd8d | ||
|
091e024032 | ||
|
0e030f7f48 | ||
|
3341c9e3bf | ||
|
91ddc00f7e | ||
|
3049ba0b24 | ||
|
55a161fafe | ||
|
349af24c81 | ||
|
788f1c4b3e | ||
|
889af461c6 | ||
|
df86e59089 | ||
|
c6bf69cce4 | ||
|
e492012004 | ||
|
94c46f9881 | ||
|
1eaa9d32ea | ||
|
0e2e8d2e2a | ||
|
cfb39c6364 | ||
|
173499f496 | ||
|
1081049074 | ||
|
961dc85514 | ||
|
6434acd492 | ||
|
ccb27c89d8 | ||
|
8053256203 | ||
|
4abd7301fd | ||
|
d6fe5ab417 | ||
|
a739fbdf1d | ||
|
c90a1ab6dc | ||
|
05ef68e079 | ||
|
dee4a7f3c7 | ||
|
75753df0d8 | ||
|
30c5d78647 | ||
|
6bb4db3842 | ||
|
cc0907fcd7 | ||
|
20b018bcc7 | ||
|
aafe2fa8d0 | ||
|
ee7bd73b3f | ||
|
b67f3bd629 | ||
|
38b81e79f8 | ||
|
b73c549131 | ||
|
d21d9f0141 | ||
|
7bb85032a1 | ||
|
d90446ad28 | ||
|
8b5e2f5528 | ||
|
1bcc3ab7f1 | ||
|
3359c3cd45 | ||
|
af812f3c90 | ||
|
1e6201093b | ||
|
959ea69427 | ||
|
cea744a914 | ||
|
e8baf48764 | ||
|
41242a2ae1 | ||
|
f1dee488a7 | ||
|
3b4ff1818e | ||
|
497145b1b5 | ||
|
10eb41d319 | ||
|
4daf2e4a3d | ||
|
f3266a5111 | ||
|
27cac4e8b8 | ||
|
60b9a5b9da | ||
|
eaaa62a7f3 | ||
|
6ce732ec3d | ||
|
fb0cc61e09 | ||
|
be29b5daf8 | ||
|
f010adabd0 | ||
|
c93b263b1f | ||
|
15f34d6b54 | ||
|
d0eeb55999 | ||
|
08c0d39b23 | ||
|
d0ecde3277 | ||
|
45ec57afd7 | ||
|
b0cd053083 | ||
|
698a11be58 | ||
|
efb08fb1e1 | ||
|
f89bc8422e | ||
|
4bf4889a08 | ||
|
01ab8ba38e | ||
|
ae6d15e812 | ||
|
1abfbe1d34 | ||
|
d3095297c2 | ||
|
79d40d5644 | ||
|
008e305a84 | ||
|
0379611edd | ||
|
96d883c1c7 | ||
|
be0f262e37 | ||
|
1676adf071 | ||
|
a5d5630067 | ||
|
275956caba | ||
|
bf8ed87fc9 | ||
|
fb3afac097 | ||
|
eda8b037a9 | ||
|
8ef14f7a54 | ||
|
9974e35656 | ||
|
15bc5431b6 | ||
|
f767531d89 | ||
|
7285ada9dd | ||
|
b9b9773df9 | ||
|
c29a83b259 | ||
|
fa45e66da6 | ||
|
7cbcdddac9 | ||
|
a2f17900da | ||
|
fb7e97b8ad | ||
|
0c92a8a8e9 | ||
|
dcc59380e5 | ||
|
f9bf25f96d | ||
|
cbcbea8b08 | ||
|
62eb4f20da | ||
|
43d5311e5e | ||
|
a6f08a09d5 | ||
|
d93f5d7785 | ||
|
c7170e6dc2 | ||
|
bcf3ca7339 | ||
|
0388f5787a | ||
|
6514df9d96 | ||
|
8f5b9869dc | ||
|
580c5fe23f | ||
|
b0af5b26ba | ||
|
9e898932f6 | ||
|
1f873b93f6 | ||
|
f414707f11 | ||
|
505825056c | ||
|
38f7716738 | ||
|
a69d08b554 | ||
|
3ccdb64833 | ||
|
78d8bff599 | ||
|
a756fed70b | ||
|
3e2a1e3548 | ||
|
96b2f2b3a4 | ||
|
d81d7d4f68 | ||
|
20244c4fb5 | ||
|
b50d31ffe2 | ||
|
d709a44960 | ||
|
4e4d07ced6 | ||
|
d775bc9d7e | ||
|
85528761eb | ||
|
ad3eac9ddb | ||
|
613f9fccd2 | ||
|
3d1741c904 | ||
|
26be14ba67 | ||
|
daa0755920 | ||
|
305d60e09b | ||
|
d0029efd02 | ||
|
fb4d42bf5b | ||
|
20eec53b14 | ||
|
8343db44db | ||
|
649652e373 | ||
|
e37ed7c32d | ||
|
f9a525068b | ||
|
aa11e6d62e | ||
|
6802d152da | ||
|
3b40f393d8 | ||
|
020443ae8a | ||
|
1d0baccffc | ||
|
5426f0f329 | ||
|
790249dd1a | ||
|
446a201d25 | ||
|
b6538d5e18 | ||
|
edd6043059 | ||
|
fe4ffeb7f1 | ||
|
27b3875bfb | ||
|
e2dbe8a0a2 | ||
|
29fc7910b7 | ||
|
22e2fdc707 | ||
|
584786eb9f | ||
|
d803d9eaf9 | ||
|
bad6575d83 | ||
|
fbcb7ae836 | ||
|
2c1a1b10c8 | ||
|
155fb16a8a | ||
|
b1ab2ce96a | ||
|
f299ba6218 | ||
|
d167ad1923 | ||
|
93626e8154 | ||
|
d5040c091a | ||
|
868daef0f0 | ||
|
dc8e85e7f2 | ||
|
22d32d7ca3 | ||
|
2d500f8074 | ||
|
452cdc17c6 | ||
|
bcbfee0321 | ||
|
dab2e7ede3 | ||
|
d91acb8352 | ||
|
373dd8058e | ||
|
de6310e52a | ||
|
8c297a4a4c | ||
|
4eb5c817bc | ||
|
07e4b26b9e | ||
|
b63aa62985 | ||
|
ca701c0580 | ||
|
e37043a6a8 | ||
|
7c26975d14 | ||
|
47f8a43637 | ||
|
1238c0cefe | ||
|
4c35fda045 | ||
|
780124c2f5 | ||
|
38e0af41ce | ||
|
601e99eec0 | ||
|
745a2adee7 | ||
|
9e83234df1 | ||
|
e45eeadf7d | ||
|
9a778cea6b | ||
|
20823bfc87 | ||
|
be3d703692 | ||
|
e2df5739f0 | ||
|
7bb11d6436 | ||
|
80b84212cc | ||
|
4a1bee769b | ||
|
de99a7aeaf | ||
|
a0a02701b0 | ||
|
1314ae1555 | ||
|
67cf0e745c | ||
|
538a2acbf5 | ||
|
8d0e453666 | ||
|
ace04f0b30 | ||
|
f8e25d6c4a | ||
|
b2bc43da4f | ||
|
cb12e540d2 | ||
|
39955af2fa | ||
|
86e1f0615d | ||
|
dc81ab6dee | ||
|
f8cf6a65ae | ||
|
ba909c696d | ||
|
df0515cebf | ||
|
46c0e14d67 | ||
|
97d7733464 | ||
|
afda84ef09 | ||
|
61d6e74102 | ||
|
8d74258ce2 | ||
|
5dfba0b834 | ||
|
056370ec08 | ||
|
4e2c254558 | ||
|
38c2bdb447 | ||
|
0cee4717e2 | ||
|
221b04c466 | ||
|
237e9b7191 | ||
|
9457e44a88 | ||
|
80a9d40a44 | ||
|
0715325a63 | ||
|
0a026fef0f | ||
|
5fbf650d2d | ||
|
dabdde0c3f | ||
|
b6ca92a7ef | ||
|
62f7339170 | ||
|
eaec682ea7 | ||
|
12110a4442 | ||
|
df597f53b2 | ||
|
d7d40254d4 | ||
|
e7c4a2cce6 | ||
|
0eb1c0cea6 | ||
|
07d35a8513 | ||
|
16c887814e | ||
|
22a50b72fd | ||
|
a79e1b6ca1 | ||
|
995296ef53 | ||
|
f4d5996a88 | ||
|
ab732b5435 | ||
|
241ff5cb6e | ||
|
ec2169e079 | ||
|
c75662e720 | ||
|
d567fd4842 | ||
|
0776dfc80e | ||
|
79a662fb93 | ||
|
8b009b7ee9 | ||
|
c4face30cc | ||
|
d4dbb5cb51 | ||
|
1e27187652 | ||
|
3ff278291f | ||
|
4e8bf216df | ||
|
e7b9100f1c | ||
|
08aa9790f3 | ||
|
5f13fd2dca | ||
|
f4c6f42c38 | ||
|
6a10d08189 | ||
|
f646360af6 | ||
|
95f265ebbf | ||
|
39d0142993 | ||
|
da16172244 | ||
|
71aded0fae | ||
|
e173b3ea41 | ||
|
6c4f9466b9 | ||
|
664196c5ef | ||
|
c8d5044e7a | ||
|
d8c31fe560 | ||
|
516db855f5 | ||
|
9cdcf08ab1 | ||
|
326fa73b22 | ||
|
79cacefd07 | ||
|
07e28bfee6 | ||
|
980b017fbe | ||
|
4567fd1eb0 | ||
|
8c150c23f3 | ||
|
b4fd570269 | ||
|
681a845ef3 | ||
|
6e051d73c1 | ||
|
e381e1a313 | ||
|
2d03ff63cf | ||
|
309de631ed | ||
|
ad240cf52f | ||
|
99e3a47dde | ||
|
3fa810b7b8 | ||
|
bad7316b80 | ||
|
4757c36233 | ||
|
9ca6180207 | ||
|
30179ad977 | ||
|
b799609749 | ||
|
efb6994ae7 | ||
|
cd129fb055 | ||
|
880a977dd6 | ||
|
6eceffb403 | ||
|
795e33881c | ||
|
d310c3857f | ||
|
837e275bfd | ||
|
7be6031e19 | ||
|
8c53908cc4 | ||
|
589b54984a | ||
|
3f30ed5251 | ||
|
3f9181905a | ||
|
29f3a81666 | ||
|
5efc43260e | ||
|
e01794a07f | ||
|
5fde095a6f | ||
|
3237af2d85 | ||
|
953b666ebd | ||
|
417bb4bf37 | ||
|
a55213d88a | ||
|
ec84d190f4 | ||
|
1b0bad72de | ||
|
931055708d | ||
|
15fd570b49 | ||
|
ff9c6bac0a | ||
|
713111254b | ||
|
5b1462a3e8 | ||
|
2bf18d8bda | ||
|
7f2e643e62 | ||
|
ef172592b8 | ||
|
5e51a438a7 | ||
|
7f768059e6 | ||
|
ea70175a17 | ||
|
74a736691a | ||
|
86ff44adfc | ||
|
58b763e935 | ||
|
5c90cc59aa | ||
|
710ab44073 | ||
|
b28c5e6807 | ||
|
e5ead9ed44 | ||
|
d99897cf9d | ||
|
701a7cae02 | ||
|
068b6a5470 | ||
|
45d597ac49 | ||
|
a518d3f33f | ||
|
2f05228d91 | ||
|
335ae0105f | ||
|
62ce7a0e37 | ||
|
afa0fb8da1 | ||
|
763d835f4e | ||
|
0289872dcd | ||
|
2810141bd6 | ||
|
93ea22c69d | ||
|
daf76a311c | ||
|
b94271a725 | ||
|
1456aeedf2 | ||
|
4c6589a57e | ||
|
5b9a61b7db | ||
|
96e71695c5 | ||
|
2e7dd1bde3 | ||
|
2de543f5f9 | ||
|
a6ee20fca4 | ||
|
2d2f159e04 | ||
|
5568e0c2ad | ||
|
cb2cc0cb9e | ||
|
5e573ca980 | ||
|
dcb4a315a6 | ||
|
4bd36fc29e | ||
|
1a7971ec82 | ||
|
c1eda034b3 | ||
|
2294d722c7 | ||
|
7d053be6d1 | ||
|
7a508661eb | ||
|
c976242ce5 | ||
|
2b77c372a3 | ||
|
b61cc67997 | ||
|
de8db1a86d | ||
|
abfdf0e1c2 | ||
|
1d662e354b | ||
|
663e9a9b5e | ||
|
8535409962 | ||
|
cd5623b348 | ||
|
673e051bd5 | ||
|
66c949057e | ||
|
aed09f0c64 | ||
|
c05f306b0d | ||
|
739fb99ced | ||
|
7fc82ccead | ||
|
8ae947f59c | ||
|
d34b493b7d | ||
|
4df6e0ee7d | ||
|
aac4ef05e4 | ||
|
ea2f53e166 | ||
|
f144ec67ab | ||
|
7e42dcf0b5 | ||
|
87c4dc313a | ||
|
0d29339898 | ||
|
6d83f18490 | ||
|
0ba125c2d7 | ||
|
6abd120a5c | ||
|
5cf7e89ce6 | ||
|
353786cb61 | ||
|
586beea21e | ||
|
b80454a1f4 | ||
|
19f80cf506 | ||
|
d01100bb15 | ||
|
e0414e4eb9 | ||
|
1eb10e3c22 | ||
|
4772fbcd4a | ||
|
18b61e35be | ||
|
bb7faf0fb3 | ||
|
1603742adc | ||
|
8a830e40bb | ||
|
d8adcf5a84 | ||
|
1d9a404f77 | ||
|
26dcaf6078 | ||
|
a91e4014bf | ||
|
06af327e5e | ||
|
35e3b889c3 | ||
|
5ef6ba0258 | ||
|
12754ff135 | ||
|
d09a1e254f | ||
|
b89ee67daf | ||
|
7bd256c311 | ||
|
4add7cd0b3 | ||
|
5ac20cc4cf | ||
|
9d7b0487d5 | ||
|
59aa84f6c8 | ||
|
765b03c868 | ||
|
41ce3db8f9 | ||
|
00e3ef757c | ||
|
84dc0b2959 | ||
|
2e48099070 | ||
|
77779bbcd9 | ||
|
e08bc01c33 | ||
|
bf24ee369f | ||
|
876f12db89 | ||
|
8b84459e13 | ||
|
dccfcc9663 | ||
|
203e1cc9b9 | ||
|
b612f0cdec | ||
|
961301fbec | ||
|
2a38d99cf1 | ||
|
7903328c2d | ||
|
56d2b4a80c | ||
|
2c6ecaab5b | ||
|
45d289a65e | ||
|
318c8c68b0 | ||
|
fbd47a7f3b | ||
|
07533f5658 | ||
|
66b7e3e1f5 | ||
|
4fee4d1903 | ||
|
90d70beea2 | ||
|
a7297d2685 | ||
|
86ae704e86 | ||
|
2d1e993fe1 | ||
|
04b550e435 | ||
|
c492ea77f1 | ||
|
61d9112ba7 | ||
|
d7fe36de71 | ||
|
db0bd3fa2d | ||
|
551619e772 | ||
|
29bae230a4 | ||
|
83be49156f | ||
|
561ae102fb | ||
|
a05e69b855 | ||
|
8eb772d80b | ||
|
1590693547 | ||
|
66f93ee541 | ||
|
8893df118e | ||
|
2c77cb5ca5 | ||
|
8a101f9e9a | ||
|
ce98d0184d | ||
|
402dea3c8b | ||
|
223cf4b2b2 | ||
|
8fb7e79bb3 | ||
|
5a2d386976 | ||
|
96622533e0 | ||
|
8814ce05a9 | ||
|
c15148fc07 | ||
|
3404ebbbb8 | ||
|
51494cabc8 | ||
|
1eb326683e | ||
|
98bcfbef7e | ||
|
9416980096 | ||
|
b34505c086 | ||
|
a65edf52ca | ||
|
d1513f2575 | ||
|
12d20c35be | ||
|
9f822c0991 | ||
|
96c338859b | ||
|
a2464dce73 | ||
|
53476b723d | ||
|
ca92a0af5c | ||
|
6e6cd90f6d | ||
|
adc84e1f93 | ||
|
20687d915a | ||
|
12a34f0b09 | ||
|
e5e49e4347 | ||
|
9f61256e5e | ||
|
ac6e370a78 | ||
|
a9c2c2178a | ||
|
c67419bb55 | ||
|
631c270fc7 | ||
|
3c82dfc0a5 | ||
|
063574023b | ||
|
5f539b133b | ||
|
0bb52a6058 | ||
|
0db40bbb32 | ||
|
f66114e203 | ||
|
2fcc064b0f | ||
|
a4c441a1b7 | ||
|
cbb6e4d6f3 | ||
|
34361ccd1c | ||
|
c953936798 | ||
|
6fd2a6ef34 | ||
|
be05f1a71f | ||
|
0de65d9c0f | ||
|
a60f4e3bad | ||
|
ab30ea766b | ||
|
099a7c46b7 | ||
|
a7afcd9644 | ||
|
7c80eec755 | ||
|
07c50c20b6 | ||
|
08d841f6c5 | ||
|
6b144c5816 | ||
|
322cec0980 | ||
|
73a47b2853 | ||
|
0036ec2214 | ||
|
98dc69893e | ||
|
af48af5085 | ||
|
0b2279a113 | ||
|
ba63a44ec8 | ||
|
7c20d2f97c | ||
|
8e7369f0b5 | ||
|
5f80deb5b7 | ||
|
826fdbf342 | ||
|
7e5186c3c7 | ||
|
2bc5253725 | ||
|
f58787bb50 | ||
|
88a01d39ee | ||
|
ee88897b18 | ||
|
b26f9e316d | ||
|
0e486b45e1 | ||
|
f730d2fc27 | ||
|
26303c8617 | ||
|
a6485b61a4 | ||
|
7db07bca35 | ||
|
19bbe0fc1f | ||
|
2b2c2de583 | ||
|
b05534472c | ||
|
30aa8d469c | ||
|
c4282a3593 | ||
|
778107aae9 | ||
|
581af762f9 | ||
|
31e63b576b | ||
|
efb592dce5 | ||
|
fa5b936d7b | ||
|
4904bd53ef | ||
|
320ce372f5 | ||
|
7a803abec8 | ||
|
f34407fc43 | ||
|
b41bda569d | ||
|
fd2919fd1c | ||
|
dce8edf742 | ||
|
298e32aada | ||
|
2b2136867d | ||
|
c1830aa37c | ||
|
a3d4049c9c | ||
|
058b4bbe6c | ||
|
c4c7b2ed5b | ||
|
aee607ea81 | ||
|
5eea5939c0 | ||
|
ac53d64ffc | ||
|
9ebee8c03e | ||
|
93965fd98b | ||
|
59c25ef915 | ||
|
7e9c4848fb | ||
|
467b1ad4f1 | ||
|
f45bd18cc2 | ||
|
4f844abc0c | ||
|
b688dcd4ba | ||
|
d5e902679b | ||
|
eae21e1371 | ||
|
947c2e556d | ||
|
d68d4c2c76 | ||
|
1ef451c048 | ||
|
ec20748594 | ||
|
764f05b42a | ||
|
5c41e24b99 | ||
|
9725091233 | ||
|
8f8dfabe0f | ||
|
b9749bad61 | ||
|
31609a8aba | ||
|
28e8cc2675 | ||
|
033d3c92ab | ||
|
3484fcfd2a | ||
|
a8e18d7f99 | ||
|
db30b58ee9 | ||
|
3748488200 | ||
|
c53a20a577 | ||
|
dfcb2b610a | ||
|
95278a78ff | ||
|
400d577546 | ||
|
60cf6cec5e | ||
|
13901b9028 | ||
|
23454a918d | ||
|
fa64ecf513 | ||
|
bb8c0f1d9c | ||
|
6e33e063da | ||
|
e44893f91e | ||
|
f2c3fc20de | ||
|
d903fe400f | ||
|
22e4e4125a | ||
|
badddfe3d7 | ||
|
e14816dcfa | ||
|
4b83722a66 | ||
|
c798913fd2 | ||
|
636dbe5b95 | ||
|
ab28d0e09f | ||
|
a74efd285c | ||
|
3e1eb03517 | ||
|
fb90574d44 | ||
|
c0fb337dfb | ||
|
e7775bb9d9 | ||
|
64f2a67573 | ||
|
154e33a2c3 | ||
|
63ae2b8095 | ||
|
6165a6af6c | ||
|
4a70e3cd31 | ||
|
5ccf053cba | ||
|
5a6f5133af | ||
|
1a2b4f8260 | ||
|
5436050df1 | ||
|
d3f3946971 | ||
|
597a7f7b40 | ||
|
02f549d1dd | ||
|
0c096bb090 | ||
|
8bc6cd7fc9 | ||
|
65d9e5d1fc | ||
|
47c356692f | ||
|
ef9157174c | ||
|
7345807871 | ||
|
19b9d3737e | ||
|
db3be3d80a | ||
|
60c7522f22 | ||
|
60002793b0 | ||
|
9e5a4189d7 | ||
|
945cbdd3a7 | ||
|
f974fd0660 | ||
|
af9fdfa224 | ||
|
77c72d2bb3 | ||
|
8404617090 | ||
|
cc9a429689 | ||
|
44f50eba5b | ||
|
f4509e24c6 | ||
|
e17a40fdb2 | ||
|
181cb235df | ||
|
e3456cb74f | ||
|
e377552f12 | ||
|
9ab9e4894d | ||
|
5782979203 | ||
|
a4bb279da8 | ||
|
8df9549a42 | ||
|
de873c5f6f | ||
|
3425e929e9 | ||
|
60c5d96037 | ||
|
4b4c3ddb2f | ||
|
1ea50ea917 | ||
|
29f68d218d | ||
|
daad8bca69 | ||
|
f4408aa72c | ||
|
0117cd478b | ||
|
9ad22d7405 | ||
|
de4d0989e9 | ||
|
0c884c2669 | ||
|
5d7cfc1c10 | ||
|
fd2b070a6a | ||
|
455819566b | ||
|
b39113f0ae | ||
|
af0f1939a3 | ||
|
becb9fc43e | ||
|
8b9c274fdd | ||
|
0701f3496b | ||
|
09c03e8ca7 | ||
|
9285da6c12 | ||
|
6a0e16885d | ||
|
f0db135b1d | ||
|
4fb86bd699 | ||
|
c62082b924 | ||
|
e8f592c6e0 | ||
|
61935942cf | ||
|
f1d8bb84b7 | ||
|
7918448be2 | ||
|
f89f704a69 | ||
|
ae33de7e7c | ||
|
b23e474648 | ||
|
611bc26685 | ||
|
0dc39166ac | ||
|
8d970b7858 | ||
|
7033c4a182 | ||
|
2c0ca78265 | ||
|
0d3c03d1e3 | ||
|
b4dfce4a44 | ||
|
91ca3939b3 | ||
|
92db5d3a35 | ||
|
f252960aaf | ||
|
4aec39df7a | ||
|
15b19925f2 | ||
|
bfb376505b | ||
|
ec75ff5292 | ||
|
28eb0be4ad | ||
|
4d3b6b1486 | ||
|
d3fd9a188d | ||
|
41a15f90cb | ||
|
9c604a7886 | ||
|
8ad1d6fd97 | ||
|
a18e7eb089 | ||
|
1f5ea40bf6 | ||
|
44509e027c | ||
|
fe3758b1bb | ||
|
ebe2a30463 | ||
|
1216a26d13 | ||
|
65800853f4 | ||
|
ccb81179ab | ||
|
f2ea701d34 | ||
|
1d4aa27f1c | ||
|
bb08fe8113 | ||
|
e2099f0749 | ||
|
3c60feed02 | ||
|
5466e1b733 | ||
|
328f15c2ea | ||
|
873125abe1 | ||
|
5df818a19c | ||
|
a5dc3cd018 | ||
|
afe0e3c1d6 | ||
|
91e1f958f2 | ||
|
2a94ee55cc | ||
|
ccf612f536 | ||
|
c6fa0cc072 | ||
|
c282bb2fe1 | ||
|
72d18fd7e1 | ||
|
6802873cd1 | ||
|
1bdc46969c | ||
|
989ee0e281 | ||
|
0f27d646bb | ||
|
81aca500b3 | ||
|
50f2dded64 | ||
|
d26d94b62a | ||
|
2811f76714 | ||
|
67b21ad766 | ||
|
6d07d5dccb | ||
|
da0e3d5c8b | ||
|
b129fe908c | ||
|
f5c57e84c7 | ||
|
ceb4ef2642 | ||
|
1c235aa761 | ||
|
cfc8117c3c | ||
|
aa1f515fcf | ||
|
4fdd12bc48 | ||
|
34f04b1946 | ||
|
5770b9dc0e | ||
|
afe2b934de | ||
|
11fe6cfbb0 | ||
|
db10226005 | ||
|
a15b8077a3 | ||
|
a3836b5e17 | ||
|
f8d80422b2 | ||
|
9848f80630 | ||
|
6ea1732630 | ||
|
d97571ce0c | ||
|
1e3b866c8b | ||
|
2168838365 | ||
|
219021873d | ||
|
120b505361 | ||
|
347a2977fa | ||
|
c2e90864ac | ||
|
1027efd6e5 | ||
|
bd0de83d31 | ||
|
4d3523b677 | ||
|
03f15e0075 | ||
|
043779f4af | ||
|
25b5daf6a5 | ||
|
db444f5d7e | ||
|
16499d2fb8 | ||
|
ddff2b2982 | ||
|
e697ee72fa | ||
|
f98c59172b | ||
|
3badafaa82 | ||
|
cbf65a9355 | ||
|
365bc900b0 | ||
|
bd0da63f4c | ||
|
db0b663a3d | ||
|
fd7fe129e2 | ||
|
27253c360b | ||
|
ab226d16c8 | ||
|
d6394402b8 | ||
|
6be5fac953 | ||
|
c4435279de | ||
|
a9bdc8ad85 | ||
|
df2a5fc789 | ||
|
db87f9e15b | ||
|
5af2768d33 | ||
|
474695643f | ||
|
97ab88b39a | ||
|
3773d40201 | ||
|
b3fd01fe04 | ||
|
75c4ca77c2 | ||
|
692ddc60c7 | ||
|
1a296a8ca1 | ||
|
f5595dd4c3 | ||
|
11c0221f81 | ||
|
2d0aad3da9 | ||
|
ff19cb68b4 | ||
|
e62df3b3b1 | ||
|
1d024cc339 | ||
|
8bda91aafb | ||
|
fd9963e7eb | ||
|
dc81ca4f53 | ||
|
fb754f9bc7 | ||
|
7045f6571f | ||
|
50d9bccacf | ||
|
9064769185 | ||
|
5fc16bdbfb | ||
|
172e4e939d | ||
|
f914b728ff | ||
|
54857e843b | ||
|
64b34e98c7 | ||
|
1ccf74bca1 | ||
|
49a534a61b | ||
|
b8889c6a1f | ||
|
ccbc5c9d17 | ||
|
555e01ec87 | ||
|
7869f472c3 | ||
|
90f60f95f7 | ||
|
0f0a71a109 | ||
|
a43c8a2def | ||
|
cbafaf5d56 | ||
|
62e4e13f5a | ||
|
e69908abef | ||
|
6cb3cf8747 | ||
|
2f71a43420 | ||
|
f57ad57e62 | ||
|
03f5d9b102 | ||
|
6b17e1820c | ||
|
e0f6fca987 | ||
|
1fe95a5fe4 | ||
|
1257ecf100 | ||
|
4163304b24 | ||
|
d35f2c4a4f | ||
|
e5d5c5f9a7 | ||
|
6fcaec3ca8 | ||
|
a2892ad097 | ||
|
79c79146a5 | ||
|
cd37ba308c | ||
|
b35c0f7e39 | ||
|
14b1b649cb | ||
|
14c0307c09 | ||
|
42f22119f2 | ||
|
75f4771616 | ||
|
83f7cb2033 | ||
|
43a4c6198c | ||
|
8c2fafecd7 | ||
|
d004c0ccd1 | ||
|
406ae4e8c3 | ||
|
cd8bee1371 | ||
|
123392c549 | ||
|
42ffe213fd | ||
|
fd738e5c8b | ||
|
6f95b2c2ad | ||
|
f66c2b078c | ||
|
c6f5c120ba | ||
|
677e93430d | ||
|
a9a90c5545 | ||
|
ee0418e719 | ||
|
6fc1141477 | ||
|
b02920c43c | ||
|
049d249609 | ||
|
fc2a554415 | ||
|
42b806b500 | ||
|
aeb3ccaf09 | ||
|
86fdd91597 | ||
|
e68fea185d | ||
|
a3eaf9f473 | ||
|
e6a2b9f06e | ||
|
a78973702b | ||
|
354b745c39 | ||
|
ce6cd6acdc | ||
|
8e129143f1 | ||
|
6d50cbba6f | ||
|
7731878f36 | ||
|
9f659eef1b | ||
|
14cc642e54 | ||
|
c47852cd0a | ||
|
c3bfaa31ee | ||
|
832505a315 | ||
|
cb71667336 | ||
|
1032e97d58 | ||
|
b4271da13e | ||
|
e2dc5ef4f2 | ||
|
c75ee042a8 | ||
|
d474d518ca | ||
|
52b8dbcbb1 | ||
|
0d8d8f0426 | ||
|
ed12deae25 | ||
|
7f1e7c981d | ||
|
cc26688d82 | ||
|
a0fa3a6063 | ||
|
110a1a640d | ||
|
bbdc43c750 | ||
|
c73b9b4882 | ||
|
e249092f91 | ||
|
5c0b04bfc8 | ||
|
bc257f4951 | ||
|
e738ee0812 | ||
|
ebb2db17f3 | ||
|
4214293b76 | ||
|
ce3ee909bf | ||
|
09ba1e2470 | ||
|
68444b81cc | ||
|
d3c0b9a438 | ||
|
6e2f1f72c6 | ||
|
c82eec09b7 | ||
|
d4093d8c98 | ||
|
90616c82b6 | ||
|
de69fe1745 | ||
|
9bd42ac221 | ||
|
2f9a272696 | ||
|
552ea0356e | ||
|
ff6bd91ef7 | ||
|
835f20f872 | ||
|
1e9b35d18f | ||
|
3818e48218 | ||
|
5d63065057 | ||
|
ae41ed1d51 | ||
|
36ead2251a | ||
|
dc7093f68a | ||
|
6c79d2b008 | ||
|
422349c2d1 | ||
|
7dbfa0b203 | ||
|
57ea2ac039 | ||
|
a9e664e89d | ||
|
c0e2210da3 | ||
|
b13c6f283a | ||
|
8181b9a31c | ||
|
105e4f990d | ||
|
9670d74345 | ||
|
86e553e756 | ||
|
566ea9a110 | ||
|
f59f035a7e | ||
|
4c6d4f3caf | ||
|
8af87bac22 | ||
|
690567659c | ||
|
d9c7ee8976 | ||
|
95cf554de4 | ||
|
47a87a14f5 | ||
|
5e860b6850 | ||
|
9a983e7565 | ||
|
00197d131d | ||
|
9758f3c9d2 | ||
|
e7d7425b6e | ||
|
b566c81ae7 | ||
|
742837e7a1 | ||
|
5087dbd756 | ||
|
b893ae7c14 | ||
|
1f7863057f | ||
|
3dd3afd21f | ||
|
387d3b7335 | ||
|
782bfd058b | ||
|
c0d936fa4d | ||
|
63819c5757 | ||
|
08cbac6277 | ||
|
68c0a9aec4 | ||
|
1c585815a1 | ||
|
853ec7320f | ||
|
b8917a3c0e | ||
|
c4d70e7f9b | ||
|
7f1b11d19b | ||
|
02a32dea40 | ||
|
d9e20ea17a | ||
|
039c2fe0a6 | ||
|
a967e73f9e | ||
|
222e4154a1 | ||
|
56413ee94e | ||
|
93c07b2b1e | ||
|
27ed64a1af | ||
|
ad68106c3c | ||
|
a591cf1d21 | ||
|
03a4b8f5cf | ||
|
2d8d25808d | ||
|
a82fcce734 | ||
|
0734e136d0 | ||
|
2913120ff7 | ||
|
bd277b087a | ||
|
fc00966b8b | ||
|
495a76353f | ||
|
6ea225ed2a | ||
|
c1a5f59c42 | ||
|
49fb9108e9 | ||
|
4cb3b514ab | ||
|
c95a37130a | ||
|
29e82cc509 | ||
|
a4cb53fdb4 | ||
|
865dce6f68 | ||
|
1d02154d99 | ||
|
3a8d72db31 | ||
|
ec57a240d5 | ||
|
949f7587dc | ||
|
55cce646e0 | ||
|
4c3dce694a | ||
|
46d5b70391 | ||
|
08a102d182 | ||
|
1cf8e08d4b | ||
|
65a8b83150 | ||
|
f4c8db654c | ||
|
5c4d1c0259 | ||
|
a459f8b84c | ||
|
0d672420f7 | ||
|
2f7be0559a | ||
|
0623ae1f87 | ||
|
8ae946c2c5 | ||
|
bb75f6ffce | ||
|
fcfe83f0cc | ||
|
5cbcded929 | ||
|
2a95f792ec | ||
|
daa2808448 | ||
|
fc06ffdcee | ||
|
1601179081 | ||
|
7fe374b67e | ||
|
7a353bbe48 | ||
|
c103ff271b | ||
|
ab9e0f891a | ||
|
af85ef8cfb | ||
|
3cea6e5d0c | ||
|
6d1b0ae498 | ||
|
07d5b07748 | ||
|
d47b9cd5c0 | ||
|
d542c063d7 | ||
|
4bb3d33907 | ||
|
62bec25b6e | ||
|
3ba16f1773 | ||
|
c302030301 | ||
|
49abe2b11f | ||
|
544e7fce88 | ||
|
b9e7f018e2 | ||
|
f31049cf69 | ||
|
2b3b73b3f1 | ||
|
704bb5a848 | ||
|
01f7d7add2 | ||
|
831d8b6d36 | ||
|
2e9b6ead2e | ||
|
d71fd0ff2d | ||
|
6466bd4ba7 | ||
|
a97fa1abb6 | ||
|
6171e5cc6e | ||
|
456502893c | ||
|
443a90c7ba | ||
|
b8a72245dc | ||
|
d019f88e26 | ||
|
01cf4cc1ed | ||
|
396bbd83c8 | ||
|
92e0affb85 | ||
|
6552157894 | ||
|
af41e4892f | ||
|
09531b399a | ||
|
6251b77b28 | ||
|
0099b6d4a2 | ||
|
adb8de9754 | ||
|
8ac8b666bf | ||
|
095feddf73 | ||
|
4882a801d2 | ||
|
1a1736ca0e | ||
|
a7619771a6 | ||
|
6febcf3b94 | ||
|
3947deb7bd | ||
|
98855de71f | ||
|
8010e6220d | ||
|
09e6a6a4ad | ||
|
da772e6649 | ||
|
65b8f78ca1 | ||
|
23bcac2e55 | ||
|
e6a88eef35 | ||
|
6654c461a6 | ||
|
a48cb052c2 | ||
|
809d932087 | ||
|
19d906c5e4 | ||
|
2f933a90fd | ||
|
3607875714 | ||
|
fec8829041 | ||
|
f58e758a41 | ||
|
cebed0824f | ||
|
97120bd1f1 | ||
|
4237d0c7e3 | ||
|
243cdfefa2 | ||
|
a12a4cd8f2 | ||
|
980b8a2924 | ||
|
989dca5792 | ||
|
c9eb06bb70 | ||
|
af89906b81 | ||
|
de71b1b397 | ||
|
2decee1d87 | ||
|
6a184b069b | ||
|
48db615ac8 | ||
|
bb9caa4838 | ||
|
3c11c3fc16 | ||
|
6c1dbc5e61 | ||
|
21bf1dd1a3 | ||
|
78aa2b491c | ||
|
53d2c7e89f | ||
|
cdf78db629 | ||
|
15c5725829 | ||
|
96653170a2 | ||
|
eade89da48 | ||
|
9b86049964 | ||
|
9e2bbd50b6 | ||
|
0d18266ad1 | ||
|
83ee0534f2 | ||
|
8ad00025ad | ||
|
7d08a7c9cd | ||
|
c8d5a5319e | ||
|
a08fa721cf | ||
|
89bc3137ba | ||
|
4d8d30f897 | ||
|
d67e9468c0 | ||
|
7dda36ab97 | ||
|
32df3e80b1 | ||
|
3c33969d23 | ||
|
c2aec5ae36 | ||
|
7afc908c32 | ||
|
c66ecbdd29 | ||
|
e61bccab36 | ||
|
cfeef98261 | ||
|
c949548150 | ||
|
829ccfca88 | ||
|
950fa84d1c | ||
|
5666729fcf | ||
|
3a13886af9 | ||
|
db6e0ccf63 | ||
|
35f2ebc2aa | ||
|
60ac4a3e86 | ||
|
c966d34b07 | ||
|
a05d7059fb | ||
|
facc1dc944 | ||
|
df2ebbf384 | ||
|
e210471c7a | ||
|
f42d552dad | ||
|
edfd9e46b6 | ||
|
27644776f3 | ||
|
a4797dcc73 | ||
|
677312648b | ||
|
ace460c69b | ||
|
b922fe35ab | ||
|
222e2166ff | ||
|
7dc25ec425 | ||
|
980835981f | ||
|
e425adc230 | ||
|
2ea7ef327e | ||
|
6c9d33f05c | ||
|
c584b51726 | ||
|
3c8fa3382b | ||
|
e5c75259e0 | ||
|
f78e437a42 | ||
|
8f61b1d320 | ||
|
84f3c6f7e6 | ||
|
bfb6c001a0 | ||
|
f0fa9f2033 | ||
|
c82cc30968 | ||
|
b0cb14b515 | ||
|
7f86d2aa0a | ||
|
482dd4db76 | ||
|
0f44fd2290 | ||
|
690d461693 | ||
|
f1ca72aee9 | ||
|
a3a6fc0d15 | ||
|
4f4fe5f06b | ||
|
c59f47f4b5 | ||
|
0ae810b8c6 | ||
|
ac9f42a8fe | ||
|
eab7e56ece | ||
|
7329515a4d | ||
|
6d6bf2df3a | ||
|
3008bfbe0a | ||
|
9399517cfb | ||
|
b117fe222d | ||
|
62567264f0 | ||
|
c054b3b0de | ||
|
83fb5b4d1b | ||
|
07a86800c9 | ||
|
688b5f29b6 | ||
|
e311f17062 | ||
|
b4cdb22e24 | ||
|
b63194c0aa | ||
|
0bab32315c | ||
|
7e881dd850 | ||
|
d6aaafb069 | ||
|
731fcb8829 | ||
|
af0e94a4b7 | ||
|
deda869cc5 | ||
|
434089395a | ||
|
048985bdf4 | ||
|
70d6d4246d | ||
|
70befe900c | ||
|
9fd81bf6c7 | ||
|
a388fe3acb | ||
|
d4946d931a | ||
|
9812654bfa | ||
|
557426246c | ||
|
d0a3e8f789 | ||
|
50b37f2ddd | ||
|
e4197012f6 | ||
|
df00a1e0a3 | ||
|
41327bfb03 | ||
|
66ef72a040 | ||
|
af660bc631 | ||
|
18edb13e76 | ||
|
e05431c847 | ||
|
5c61f95093 | ||
|
9a407c727a | ||
|
cad655a15b | ||
|
1fa5c53b72 | ||
|
d01b1a706c | ||
|
678d5fc532 | ||
|
c4e01fa566 | ||
|
e694622b83 | ||
|
1d16c96ca4 | ||
|
ab1f34726b | ||
|
c9f201755e | ||
|
094ef592a7 | ||
|
e7493c38d1 | ||
|
6fd0fcfdb5 | ||
|
6da534a961 | ||
|
f26e43144e | ||
|
427529171c | ||
|
cd146cf822 | ||
|
ced412372c | ||
|
2f6e650c1b | ||
|
5c125ca9b4 | ||
|
89c0939d58 | ||
|
8ea955fb23 | ||
|
73a297f23f | ||
|
95f1382fbf | ||
|
80df01c8ee | ||
|
c2df864119 | ||
|
2cc9356c32 | ||
|
89234c0163 | ||
|
1b7fe286a6 | ||
|
7c2d797ed0 | ||
|
4d2eedc56b | ||
|
ece0d9301f | ||
|
cc10cd88d3 | ||
|
b9f308c832 | ||
|
6816bd8bad | ||
|
0e026de5bd | ||
|
a419ad201d | ||
|
a66a4f62bd | ||
|
e721d2204b | ||
|
f293cf6e81 | ||
|
0cde1d4969 | ||
|
bd49bd6e33 | ||
|
84dc1fa151 | ||
|
5858e862d9 | ||
|
8b004a549a | ||
|
4a53e4207c | ||
|
6510de5d29 | ||
|
a6191320a1 | ||
|
a75ff0c061 | ||
|
0d45eb91db | ||
|
95edbc16bb | ||
|
15a5e3c779 | ||
|
0433432e62 | ||
|
e7ca73bbad | ||
|
8d8374b8e2 | ||
|
4dc5bbe601 | ||
|
55371f9c78 | ||
|
af63f4098f | ||
|
2b4939a875 | ||
|
d5d22844ab | ||
|
7dab00be87 | ||
|
67582d388a | ||
|
0671e7d456 | ||
|
c9e6af96d0 | ||
|
ee48a56603 | ||
|
4c03f39437 | ||
|
d8d425c963 | ||
|
ec64ac9f1d | ||
|
7ed682c186 | ||
|
9dcb579741 | ||
|
19f837aa41 | ||
|
b267491934 | ||
|
a369dddde8 | ||
|
29d87d7c48 | ||
|
4847f50093 | ||
|
7c848b1253 | ||
|
607aa46032 | ||
|
430193891f | ||
|
cbb82ed635 | ||
|
1dfb72703a | ||
|
5edf377f4b | ||
|
43a33d267a | ||
|
c4048f4c7b | ||
|
501c89ae3a | ||
|
5899497aa7 | ||
|
2c758a9981 | ||
|
66eb99e506 | ||
|
b583140077 | ||
|
f378c93dd3 | ||
|
5d29fa5e62 | ||
|
54f04c9141 | ||
|
fc0c706100 | ||
|
02ac9fa0d7 | ||
|
ca95c75df3 | ||
|
4ff86795b9 | ||
|
30eaec29d3 | ||
|
742d38c9ec | ||
|
56a4597784 | ||
|
b2a7d3584b | ||
|
b8167f89e5 | ||
|
c916472b22 | ||
|
bdfe7c5e69 | ||
|
749475799a | ||
|
daf9c807b5 | ||
|
5dfecfdee3 | ||
|
d267c03ee2 | ||
|
864e3c7a50 | ||
|
747e1a818d | ||
|
f468dbf427 | ||
|
88572e90d4 | ||
|
41fc7dd73d | ||
|
c87c588c79 | ||
|
5ab4b57411 | ||
|
3c936c6957 | ||
|
0ca903a146 | ||
|
4e91919990 | ||
|
e023a68780 | ||
|
17452cd5b6 | ||
|
c76f2c8483 | ||
|
90207f9b68 | ||
|
0ec5371b61 | ||
|
8549aeb466 | ||
|
504d3cd131 | ||
|
83ed298eba | ||
|
a90f2cb156 | ||
|
1a54bafdd5 | ||
|
3cc186f7e4 | ||
|
a2497d7564 | ||
|
c01c882081 | ||
|
af279df5c9 | ||
|
1ea7ce2589 | ||
|
476c577bc1 | ||
|
e3b91c4d71 | ||
|
1e2d7acf4a | ||
|
1def120616 | ||
|
25ddf2c651 | ||
|
e5d384e808 | ||
|
63351553d8 | ||
|
4630740ac7 | ||
|
0ff4884d3a | ||
|
2dfdedf7b0 | ||
|
e1fa5fb180 | ||
|
4e47f9eb68 | ||
|
c4c8955bc2 | ||
|
f47b808478 | ||
|
493367da5e | ||
|
4ca185cf45 | ||
|
8f4effbb8d | ||
|
a4ba7f277e | ||
|
c3f97a0cf1 | ||
|
c1b8fc1233 | ||
|
a481638c03 | ||
|
c12dfc7b4d | ||
|
34bc527709 | ||
|
ee134d0d7c | ||
|
f795ee7fd9 | ||
|
1bba75881f | ||
|
34db2d9efa | ||
|
91dd308952 | ||
|
3d97e26cde | ||
|
104c1ecbec | ||
|
88266ec4e3 | ||
|
f5be159187 | ||
|
1194d86460 | ||
|
76d6dca63f | ||
|
1b69e62e2e | ||
|
e89839359f | ||
|
177d113cd9 | ||
|
853fa87012 | ||
|
a592ca25ff | ||
|
a8c9926fa7 | ||
|
bf5587fe09 | ||
|
7c8a27e894 | ||
|
d248b11ffc | ||
|
1282e010ee | ||
|
d5d1ca948e | ||
|
a08704e8ed | ||
|
2a8c0c0c08 | ||
|
2ae0c98b4c | ||
|
c427050a92 | ||
|
43a0829f44 | ||
|
e9b8b91861 | ||
|
6f104f5056 | ||
|
e720efabdc | ||
|
45cb770e41 | ||
|
3f1cc66767 | ||
|
d1f5118bb5 | ||
|
b90f1f786e | ||
|
c6f424201b | ||
|
a7db8cf7cd | ||
|
e2ed0f5e55 | ||
|
d000e3c8a9 | ||
|
cf465a1184 | ||
|
f3119824f4 | ||
|
ef069c1689 | ||
|
68e5a9dfda | ||
|
fd1a31afb6 | ||
|
6d7d4a15d9 | ||
|
518a7d0dcc | ||
|
a4bdfe6b7d | ||
|
281269aa0d | ||
|
33521ebd31 | ||
|
18ae08de8f | ||
|
3d3c4ba02f | ||
|
89917c1582 | ||
|
90c6758562 | ||
|
5be8084473 | ||
|
8a066a4ec0 | ||
|
b98237fa44 | ||
|
c4f9388668 | ||
|
0f44b27e54 | ||
|
10d05e1870 | ||
|
921557161d | ||
|
eb092b9f20 | ||
|
28ca8a9f9f | ||
|
d7be56df7a | ||
|
ce2c163f4b | ||
|
5272462368 | ||
|
42354a6728 | ||
|
754fe3df91 | ||
|
a7a0350c1a | ||
|
a8b2ca7cfe | ||
|
e82867820b | ||
|
13305d111e | ||
|
ae37fa2bc5 | ||
|
c81db752f6 | ||
|
5d9d71dee8 | ||
|
1f558d46a4 | ||
|
36eb899c3b | ||
|
e223d752cb | ||
|
fcb503e885 | ||
|
c1055234d8 | ||
|
35ec98cf13 | ||
|
66a9138666 | ||
|
279582ff21 | ||
|
a9540ffabe | ||
|
8f02cb83ed | ||
|
5e94c8be7b | ||
|
ea6e9af5da | ||
|
1bb72262ba | ||
|
61ace7bd97 | ||
|
1a417cc36f | ||
|
00a11f4343 | ||
|
491bbacddd | ||
|
1b9daa0adb | ||
|
ccdc981756 | ||
|
b44dcc9f30 | ||
|
479ceb14e4 | ||
|
50e41d1999 | ||
|
f6e2073de0 | ||
|
271aab9746 | ||
|
8d9bf5cf17 | ||
|
ceb7ed093e | ||
|
abdbcbb741 | ||
|
51ac07cd34 | ||
|
4b29698c6c | ||
|
f93285846a | ||
|
44347db52e | ||
|
f2749ff17f | ||
|
3735342dfa | ||
|
fed6e1e82f | ||
|
58a59b624a | ||
|
1338918a89 | ||
|
831e7cdb3f | ||
|
2b8583e652 | ||
|
b0220e4200 | ||
|
0d94ef10ce | ||
|
544a1381fc | ||
|
bc17ef896e | ||
|
0bcbf999fb | ||
|
0c4c44f4b6 | ||
|
486956b762 | ||
|
8d1ef5291f | ||
|
611627ca97 | ||
|
732225b781 | ||
|
58f88075c8 | ||
|
e17ef5eccf | ||
|
70c697f094 | ||
|
4b5625e0b4 | ||
|
7df48870df | ||
|
4a309c49e5 | ||
|
b45899bf90 | ||
|
5ae3c8acfe | ||
|
ef5df5f01b | ||
|
9af7451a61 | ||
|
7048c550b7 | ||
|
f035627f42 | ||
|
7659ff1f77 | ||
|
3df4062a55 | ||
|
02bbbe668b | ||
|
2b82c8fdc9 | ||
|
93abaed0c2 | ||
|
b7fff6d452 | ||
|
d09f01db90 | ||
|
e0c1a58b84 | ||
|
1b82649cca | ||
|
9317e2a36b | ||
|
51aff8ccdc | ||
|
bdd3ea45af | ||
|
3eb5c77ac4 | ||
|
ccc85784c6 | ||
|
46806e8607 | ||
|
95f914d6bd | ||
|
0f86011106 | ||
|
acdd3c0602 | ||
|
05b7237add | ||
|
51f4e9e160 | ||
|
7bbac35d6b | ||
|
d280fe457c | ||
|
b5772af4c3 | ||
|
7ab79bb76e | ||
|
00e3cc26de | ||
|
4b491c43ff | ||
|
41233a933e |
15
.editorconfig
Normal file
15
.editorconfig
Normal file
@@ -0,0 +1,15 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 250
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{js,json}]
|
||||
indent_size = 4
|
||||
indent_style = tab
|
@@ -1,7 +1 @@
|
||||
vendor/
|
||||
!/vendor/vendor.js
|
||||
/modules/**
|
||||
!/modules/default/**
|
||||
!/modules/node_helper
|
||||
!/modules/node_helper/**
|
||||
!/modules/default/defaultmodules.js
|
||||
modules/default/calendar/vendor/*
|
||||
|
@@ -1,14 +1,30 @@
|
||||
{
|
||||
"rules": {
|
||||
"indent": ["error", "tab"],
|
||||
"quotes": ["error", "double"],
|
||||
"max-len": ["error", 250],
|
||||
"curly": "error",
|
||||
"camelcase": ["error", {"properties": "never"}]
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
|
||||
"plugins": ["prettier"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
"es6": true,
|
||||
"mocha": true,
|
||||
"node": true
|
||||
},
|
||||
"globals": {
|
||||
"config": true,
|
||||
"Log": true,
|
||||
"MM": true,
|
||||
"Module": true,
|
||||
"moment": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 2017,
|
||||
"ecmaFeatures": {
|
||||
"globalReturn": true
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"eqeqeq": "error",
|
||||
"no-prototype-builtins": "off",
|
||||
"no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
51
.github/CONTRIBUTING.md
vendored
51
.github/CONTRIBUTING.md
vendored
@@ -1,33 +1,48 @@
|
||||
Contribution Policy for MagicMirror²
|
||||
====================================
|
||||
# Contribution Policy for MagicMirror²
|
||||
|
||||
Thanks for contributing to MagicMirror²!
|
||||
|
||||
We hold our code to standard, and these standards are documented below.
|
||||
We hold our code to standard, and these standards are documented below.
|
||||
|
||||
First, before you run the linters, you will need to install them all **and** install the development dependencies:
|
||||
If you wish to run our linters, use `npm run lint` without any arguments.
|
||||
|
||||
```bash
|
||||
(sudo) npm install -g jscs stylelint html-validator-cli
|
||||
npm install
|
||||
```
|
||||
### JavaScript: Run ESLint
|
||||
|
||||
### JavaScript: Run JSCS
|
||||
We use [ESLint](https://eslint.org) on our JavaScript files.
|
||||
|
||||
We use [JSCS](http://jscs.info) on our JavaScript files.
|
||||
Our ESLint configuration is in our .eslintrc.json and .eslintignore files.
|
||||
|
||||
Our JSCS configuration is in our .jscsrc file.
|
||||
|
||||
To run JSCS, use `npm run jscs`.
|
||||
To run ESLint, use `npm run lint:js`.
|
||||
|
||||
### CSS: Run StyleLint
|
||||
|
||||
We use [StyleLint](http://stylelint.io) to lint our CSS. Our configuration is in our .stylelintrc file.
|
||||
We use [StyleLint](https://stylelint.io) to lint our CSS. Our configuration is in our .stylelintrc file.
|
||||
|
||||
To run StyleLint, use `npm run stylelint`.
|
||||
To run StyleLint, use `npm run lint:style`.
|
||||
|
||||
### HTML: Run HTML Validator
|
||||
### Submitting Issues
|
||||
|
||||
We use [NU Validator](https://validator.w3.org/nu) to validate our HTML. The configuration is in the command in the package.json file.
|
||||
Please only submit reproducible issues.
|
||||
|
||||
To run HTML Validator, use `npm run htmlvalidator`.
|
||||
If you're not sure if it's a real bug or if it's just you, please open a topic on the forum: [https://forum.magicmirror.builders/category/15/bug-hunt](https://forum.magicmirror.builders/category/15/bug-hunt)
|
||||
Problems installing or configuring your MagicMirror? Check out: [https://forum.magicmirror.builders/category/10/troubleshooting](https://forum.magicmirror.builders/category/10/troubleshooting)
|
||||
|
||||
When submitting a new issue, please supply the following information:
|
||||
|
||||
**Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3, Windows, Mac, Linux, System V UNIX).
|
||||
|
||||
**Node Version**: Make sure it's version 0.12.13 or later.
|
||||
|
||||
**MagicMirror Version**: Now that the versions have split, tell us if you are using the PHP version (v1) or the newer JavaScript version (v2).
|
||||
|
||||
**Description**: Provide a detailed description about the issue and include specific details to help us understand the problem. Adding screenshots will help describing the problem.
|
||||
|
||||
**Steps to Reproduce**: List the step by step process to reproduce the issue.
|
||||
|
||||
**Expected Results**: Describe what you expected to see.
|
||||
|
||||
**Actual Results**: Describe what you actually saw.
|
||||
|
||||
**Configuration**: What does the used config.js file look like? Don't forget to remove any sensitive information!
|
||||
|
||||
**Additional Notes**: Provide any other relevant notes not previously mentioned. This is optional.
|
||||
|
44
.github/ISSUE_TEMPLATE.md
vendored
44
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,2 +1,42 @@
|
||||
* **Platform** [ Raspberry Pi 2/3, Windows, Mac OS X, Linux]:
|
||||
* **Node Version** [ 0.12.13 or later ]:
|
||||
## I'm not sure if this is a bug
|
||||
|
||||
If you're not sure if it's a real bug or if it's just you, please open a topic on the forum: [https://forum.magicmirror.builders/category/15/bug-hunt](https://forum.magicmirror.builders/category/15/bug-hunt)
|
||||
|
||||
## I'm having troubles installing or configuring MagicMirror
|
||||
|
||||
Problems installing or configuring your MagicMirror? Check out: [https://forum.magicmirror.builders/category/10/troubleshooting](https://forum.magicmirror.builders/category/10/troubleshooting)
|
||||
|
||||
## I found a bug in the MagicMirror installer
|
||||
|
||||
If you are facing an issue or found a bug while trying to install MagicMirror via the installer please report it in the respective GitHub repository:
|
||||
[https://github.com/sdetweil/MagicMirror_scripts](https://github.com/sdetweil/MagicMirror_scripts)
|
||||
|
||||
## I found a bug in the MagicMirror Docker image
|
||||
|
||||
If you are facing an issue or found a bug while running MagicMirror inside a Docker container please create an issue in the GitHub repository of the MagicMirror Docker image:
|
||||
[https://github.com/bastilimbach/docker-MagicMirror](https://github.com/bastilimbach/docker-MagicMirror)
|
||||
|
||||
---
|
||||
|
||||
## I found a bug in MagicMirror
|
||||
|
||||
Please make sure to only submit reproducible issues. You can safely remove everything above the dividing line.
|
||||
When submitting a new issue, please supply the following information:
|
||||
|
||||
**Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3, Windows, Mac, Linux, System V UNIX).
|
||||
|
||||
**Node Version**: Make sure it's version 8 or later.
|
||||
|
||||
**MagicMirror Version**: Please let us now which version of MagicMirror you are running. It can be found in the `package.log` file.
|
||||
|
||||
**Description**: Provide a detailed description about the issue and include specific details to help us understand the problem. Adding screenshots will help describing the problem.
|
||||
|
||||
**Steps to Reproduce**: List the step by step process to reproduce the issue.
|
||||
|
||||
**Expected Results**: Describe what you expected to see.
|
||||
|
||||
**Actual Results**: Describe what you actually saw.
|
||||
|
||||
**Configuration**: What does the used config.js file look like? Don't forget to remove any sensitive information!
|
||||
|
||||
**Additional Notes**: Provide any other relevant notes not previously mentioned. This is optional.
|
||||
|
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,5 +1,13 @@
|
||||
* Does the pull request solve a **related** issue? [yes | no]
|
||||
> Please send your pull requests the develop branch.
|
||||
> Don't forget to add the change to CHANGELOG.md.
|
||||
|
||||
* If so, can you reference the issue?
|
||||
**Note**: Sometimes the development moves very fast. It is highly
|
||||
recommended that you update your branch of `develop` before creating a
|
||||
pull request to send us your changes. This makes everyone's lives
|
||||
easier (including yours) and helps us out on the development team.
|
||||
Thanks!
|
||||
|
||||
* What does the pull request accomplish? (please list)
|
||||
- Does the pull request solve a **related** issue?
|
||||
- If so, can you reference the issue?
|
||||
- What does the pull request accomplish? Use a list if needed.
|
||||
- If it includes major visual changes please add screenshots.
|
||||
|
19
.github/stale.yml
vendored
Normal file
19
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- under investigation
|
||||
- pr welcome
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
30
.gitignore
vendored
30
.gitignore
vendored
@@ -8,14 +8,21 @@ pids
|
||||
*.seed
|
||||
lib-cov
|
||||
coverage
|
||||
.grunt
|
||||
.lock-wscript
|
||||
build/Release
|
||||
node_modules
|
||||
/node_modules/**/*
|
||||
fonts/node_modules/**/*
|
||||
vendor/node_modules/**/*
|
||||
jspm_modules
|
||||
.npm
|
||||
.node_repl_history
|
||||
|
||||
# Visual Studio Code ignoramuses.
|
||||
.vscode/
|
||||
|
||||
# IDE Code ignoramuses.
|
||||
.idea/
|
||||
|
||||
# Various Windows ignoramuses.
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
@@ -51,16 +58,23 @@ Temporary Items
|
||||
.directory
|
||||
.Trash-*
|
||||
|
||||
# Various Magic Mirror ignoramuses and anti-ignoramuses.
|
||||
|
||||
# Don't ignore the node_helper nore module.
|
||||
!/modules/node_helper
|
||||
!/modules/node_helper/**
|
||||
|
||||
# Ignore all modules except the default modules.
|
||||
/modules/**
|
||||
!/modules/default
|
||||
!/modules/default/**
|
||||
!/modules/README.md**
|
||||
|
||||
# Ignore changes to the custom css files.
|
||||
/css/custom.css
|
||||
|
||||
# Vim
|
||||
## swap
|
||||
[._]*.s[a-w][a-z]
|
||||
[._]s[a-w][a-z]
|
||||
|
||||
## diff patch
|
||||
*.orig
|
||||
*.rej
|
||||
*.bak
|
||||
|
||||
!/tests/node_modules/**/*
|
||||
|
5
.prettierignore
Normal file
5
.prettierignore
Normal file
@@ -0,0 +1,5 @@
|
||||
package-lock.json
|
||||
/config/**/*
|
||||
/modules/default/calendar/vendor/ical.js/**/*
|
||||
/vendor/**/*
|
||||
!/vendor/vendor.js
|
3
.prettierrc.json
Normal file
3
.prettierrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"trailingComma": "none"
|
||||
}
|
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"extends": "stylelint-config-standard",
|
||||
"font-family-name-quotes": "double-where-recommended"
|
||||
}
|
7
.stylelintrc.json
Normal file
7
.stylelintrc.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": ["stylelint-prettier/recommended"],
|
||||
"plugins": ["stylelint-prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": true
|
||||
}
|
||||
}
|
28
.travis.yml
28
.travis.yml
@@ -1,9 +1,25 @@
|
||||
dist: trusty
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
- "5.1"
|
||||
- "4"
|
||||
- "0.12"
|
||||
- 10
|
||||
- lts/*
|
||||
- node
|
||||
before_install:
|
||||
- npm i -g npm
|
||||
before_script:
|
||||
- npm install grunt-cli -g
|
||||
script: grunt
|
||||
- yarn danger ci
|
||||
- "export DISPLAY=:99.0"
|
||||
- "export ELECTRON_DISABLE_SANDBOX=1"
|
||||
- "sh -e /etc/init.d/xvfb start"
|
||||
- sleep 5
|
||||
script:
|
||||
- npm run test:prettier
|
||||
- npm run test:js
|
||||
- npm run test:css
|
||||
- npm run test:e2e
|
||||
- npm run test:unit
|
||||
after_script:
|
||||
- npm list
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
815
CHANGELOG.md
Normal file
815
CHANGELOG.md
Normal file
@@ -0,0 +1,815 @@
|
||||
# MagicMirror² Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror²
|
||||
|
||||
## [2.12.0] - 2020-07-01
|
||||
|
||||
Special thanks to the following contributors: @AndreKoepke, @andrezibaia, @bryanzzhu, @chamakura, @DarthBrento, @Ekristoffe, @khassel, @Legion2, @ndom91, @radokristof, @rejas, @XBCreepinJesus & @ZoneMR.
|
||||
|
||||
ℹ️ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`.
|
||||
|
||||
### Added
|
||||
|
||||
- Added option to config the level of logging.
|
||||
- Added prettier for an even cleaner codebase.
|
||||
- Hide Sunrise/Sunset in Weather module.
|
||||
- Hide Sunrise/Sunset in Current Weather module.
|
||||
- Added Met Office DataHub (UK) provider.
|
||||
|
||||
### Updated
|
||||
|
||||
- Cleaned up alert module code.
|
||||
- Cleaned up check_config code.
|
||||
- Replaced grunt-based linters with their non-grunt equivalents.
|
||||
- Switch to most of the eslint:recommended rules and fix warnings.
|
||||
- Replaced insecure links with https ones.
|
||||
- Cleaned up all "no-undef" warnings from eslint.
|
||||
- Added location title wrapping for calendar module.
|
||||
- Updated the BG translation.
|
||||
|
||||
### Deleted
|
||||
|
||||
- Removed truetype (ttf) fonts.
|
||||
|
||||
### Fixed
|
||||
|
||||
- The broken modules due to Socket.io change from last release. [#1973](https://github.com/MichMich/MagicMirror/issues/1973)
|
||||
- Add backward compatibility for old module code in socketclient.js. [#1973](https://github.com/MichMich/MagicMirror/issues/1973)
|
||||
- Support multiple instances of calendar module with different config. [#1109](https://github.com/MichMich/MagicMirror/issues/1109)
|
||||
- Fix the use of "maxNumberOfDays" in the module "weatherforecast". [#2018](https://github.com/MichMich/MagicMirror/issues/2018)
|
||||
- Throw error when check_config fails. [#1928](https://github.com/MichMich/MagicMirror/issues/1928)
|
||||
- Bug fix related to 'maxEntries' not displaying Calendar events. [#2050](https://github.com/MichMich/MagicMirror/issues/2050)
|
||||
- Updated ical library to latest version. [#1926](https://github.com/MichMich/MagicMirror/issues/1926)
|
||||
|
||||
## [2.11.0] - 2020-04-01
|
||||
|
||||
🚨 READ THIS BEFORE UPDATING 🚨
|
||||
|
||||
In the past years the project has grown a lot. This came with a huge downside: poor maintainability. If I let the project continue the way it was, it would eventually crash and burn. More important: I would completely lose the drive and interest to continue the project. Because of this the decision was made to simplify the core by removing all side features like automatic installers and support for exotic platforms. This release (2.11.0) is the first real release that will reflect (parts) of these changes. As a result of this, some things might break. So before you continue make sure to backup your installation. Your config, your modules or better yet: your full MagicMirror folder. In other words: update at your own risk.
|
||||
|
||||
For more information regarding this major change, please check issue [#1860](https://github.com/MichMich/MagicMirror/issues/1860).
|
||||
|
||||
### Deleted
|
||||
|
||||
- Remove installers.
|
||||
- Remove externalized scripts.
|
||||
- Remove jshint dependency, instead eslint checks your config file now
|
||||
|
||||
### Added
|
||||
|
||||
- Brazilian translation for "FEELS".
|
||||
- Ukrainian translation.
|
||||
- Finnish translation for "PRECIP", "UPDATE_INFO_MULTIPLE" and "UPDATE_INFO_SINGLE".
|
||||
- Added the ability to hide the temp label and weather icon in the `currentweather` module to allow showing only information such as wind and sunset/rise.
|
||||
- The `clock` module now optionally displays sun and moon data, including rise/set times, remaining daylight, and percent of moon illumination.
|
||||
- Added Hebrew translation.
|
||||
- Add HTTPS support and update config.js.sample
|
||||
- Run tests on long term support and latest stable version of nodejs
|
||||
- Added the ability to configure a list of modules that shouldn't be update checked.
|
||||
- Run linters on git commits
|
||||
- Added date functionality to compliments: display birthday wishes or celebrate an anniversary
|
||||
- Add HTTPS support for clientonly-mode.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Force declaration of public ip address in config file (ISSUE #1852)
|
||||
- Fixes `run-start.sh`: If running in docker-container, don't check the environment, just start electron (ISSUE #1859)
|
||||
- Fix calendar time offset for recurring events crossing Daylight Savings Time (ISSUE #1798)
|
||||
- Fix regression in currentweather module causing 'undefined' to show up when config.hideTemp is false
|
||||
- Fix FEELS translation for Croatian
|
||||
- Fixed weather tests [#1840](https://github.com/MichMich/MagicMirror/issues/1840)
|
||||
- Fixed Socket.io can't be used with Reverse Proxy in serveronly mode [#1934](https://github.com/MichMich/MagicMirror/issues/1934)
|
||||
- Fix update checking skipping 3rd party modules the first time
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove documentation from core repository and link to new dedicated docs site: [docs.magicmirror.builders](https://docs.magicmirror.builders).
|
||||
- Updated config.js.sample: Corrected some grammar on `config.js.sample` comment section.
|
||||
- Removed `run-start.sh` script and update start commands:
|
||||
- To start using electron, use `npm run start`.
|
||||
- To start in server only mode, use `npm run server`.
|
||||
- Remove redundant logging from modules.
|
||||
- Timestamp in log output now also contains the date
|
||||
- Turkish translation.
|
||||
- Option to configure the size of the currentweather module.
|
||||
|
||||
## [2.10.1] - 2020-01-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated README.md: Added links to the official documentation website and remove links to broken installer.
|
||||
|
||||
## [2.10.0] - 2020-01-01
|
||||
|
||||
Special thanks to @sdetweil for all his great contributions!
|
||||
|
||||
ℹ️ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`.
|
||||
|
||||
### Added
|
||||
|
||||
- Timestamps in log output.
|
||||
- Padding in dateheader mode of the calendar module.
|
||||
- New upgrade script to help users consume regular updates installers/upgrade-script.sh.
|
||||
- New script to help setup pm2, without install installers/fixuppm2.sh.
|
||||
|
||||
### Updated
|
||||
|
||||
- Updated lower bound of `lodash` and `helmet` dependencies for security patches.
|
||||
- Updated compliments.js to handle newline in text, as textfields to not interpolate contents.
|
||||
- Updated raspberry.sh installer script to handle new platform issues, split node/npm, pm2, and screen saver changes.
|
||||
- Improve handling for armv6l devices, where electron support has gone away, add optional serveronly config option.
|
||||
- Improved run-start.sh to handle for serveronly mode, by choice, or when electron not available.
|
||||
- Only check for xwindows running if not on macOS.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue in weatherforecast module where predicted amount of rain was not using the decimal symbol specified in config.js.
|
||||
- Module header now updates correctly, if a module need to dynamically show/hide its header based on a condition.
|
||||
- Fix handling of config.js for serverOnly mode commented out.
|
||||
- Fixed issue in calendar module where the debug script didn't work correctly with authentication.
|
||||
- Fixed issue that some full day events were not correctly recognized as such.
|
||||
- Display full day events lasting multiple days as happening today instead of some days ago if they are still ongoing.
|
||||
|
||||
## [2.9.0] - 2019-10-01
|
||||
|
||||
ℹ️ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md).
|
||||
|
||||
### Added
|
||||
|
||||
- Spanish translation for "PRECIP".
|
||||
- Adding a Malay (Malaysian) translation for MagicMirror².
|
||||
- Add test check URLs of vendors 200 and 404 HTTP CODE.
|
||||
- Add tests for new weather module and helper to stub ajax requests.
|
||||
|
||||
### Updated
|
||||
|
||||
- Updatenotification module: Display update notification for a limited (configurable) time.
|
||||
- Enabled e2e/vendor_spec.js tests.
|
||||
- The css/custom.css will be renamed after the next release. We've added into `run-start.sh` an instruction by GIT to ignore with `--skip-worktree` and `rm --cached`. [#1540](https://github.com/MichMich/MagicMirror/issues/1540)
|
||||
- Disable sending of notification CLOCK_SECOND when displaySeconds is false.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Updatenotification module: Properly handle race conditions, prevent crash.
|
||||
- Send `NEWS_FEED` notification also for the first news messages which are shown.
|
||||
- Fixed issue where weather module would not refresh data after a network or API outage. [#1722](https://github.com/MichMich/MagicMirror/issues/1722)
|
||||
- Fixed weatherforecast module not displaying rain amount on fallback endpoint.
|
||||
- Notifications CLOCK_SECOND & CLOCK_MINUTE being from startup instead of matched against the clock and avoid drifting.
|
||||
|
||||
## [2.8.0] - 2019-07-01
|
||||
|
||||
ℹ️ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md).
|
||||
|
||||
### Added
|
||||
|
||||
- Option to show event location in calendar
|
||||
- Finnish translation for "Feels" and "Weeks"
|
||||
- Russian translation for “Feels”
|
||||
- Calendar module: added `nextDaysRelative` config option
|
||||
- Add `broadcastPastEvents` config option for calendars to include events from the past `maximumNumberOfDays` in event broadcasts
|
||||
- Added feature to broadcast news feed items `NEWS_FEED` and updated news items `NEWS_FEED_UPDATED` in default [newsfeed](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/newsfeed) module (when news is updated) with documented default and `config.js` options in [README.md](https://github.com/MichMich/MagicMirror/blob/develop/modules/default/newsfeed/README.md)
|
||||
- Added notifications to default `clock` module broadcasting `CLOCK_SECOND` and `CLOCK_MINUTE` for the respective time elapsed.
|
||||
- Added UK Met Office Datapoint feed as a provider in the default weather module.
|
||||
- Added new provider class
|
||||
- Added suncalc.js dependency to calculate sun times (not provided in UK Met Office feed)
|
||||
- Added "tempUnits" and "windUnits" to allow, for example, temp in metric (i.e. celsius) and wind in imperial (i.e. mph). These will override "units" if specified, otherwise the "units" value will be used.
|
||||
- Use Feels Like temp from feed if present
|
||||
- Optionally display probability of precipitation (PoP) in current weather (UK Met Office data)
|
||||
- Automatically try to fix eslint errors by passing `--fix` option to it
|
||||
- Added sunrise and sunset times to weathergov weather provider [#1705](https://github.com/MichMich/MagicMirror/issues/1705)
|
||||
- Added "useLocationAsHeader" to display "location" in `config.js` as header when location name is not returned
|
||||
- Added to `newsfeed.js`: in order to design the news article better with css, three more class-names were introduced: newsfeed-desc, newsfeed-desc, newsfeed-desc
|
||||
|
||||
### Updated
|
||||
|
||||
- English translation for "Feels" to "Feels like"
|
||||
- Fixed the example calendar url in `config.js.sample`
|
||||
- Update `ical.js` to solve various calendar issues.
|
||||
- Update weather city list url [#1676](https://github.com/MichMich/MagicMirror/issues/1676)
|
||||
- Only update clock once per minute when seconds aren't shown
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed uncaught exception, race condition on module update
|
||||
- Fixed issue [#1696](https://github.com/MichMich/MagicMirror/issues/1696), some ical files start date to not parse to date type
|
||||
- Allowance HTML5 autoplay-policy (policy is changed from Chrome 66 updates)
|
||||
- Handle SIGTERM messages
|
||||
- Fixes sliceMultiDayEvents so it respects maximumNumberOfDays
|
||||
- Minor types in default NewsFeed [README.md](https://github.com/MichMich/MagicMirror/blob/develop/modules/default/newsfeed/README.md)
|
||||
- Fix typos and small syntax errors, cleanup dependencies, remove multiple-empty-lines, add semi-rule
|
||||
- Fixed issues with calendar not displaying one-time changes to repeating events
|
||||
- Updated the fetchedLocationName variable in currentweather.js so that city shows up in the header
|
||||
|
||||
### Updated installer
|
||||
|
||||
- give non-pi2+ users (pi0, odroid, jetson nano, mac, windows, ...) option to continue install
|
||||
- use current username vs hardcoded 'pi' to support non-pi install
|
||||
- check for npm installed. node install doesn't do npm anymore
|
||||
- check for mac as part of PM2 install, add install option string
|
||||
- update pm2 config with current username instead of hard coded 'pi'
|
||||
- check for screen saver config, "/etc/xdg/lxsession", bypass if not setup
|
||||
|
||||
## [2.7.1] - 2019-04-02
|
||||
|
||||
Fixed `package.json` version number.
|
||||
|
||||
## [2.7.0] - 2019-04-01
|
||||
|
||||
ℹ️ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md).
|
||||
|
||||
### Added
|
||||
|
||||
- Italian translation for "Feels"
|
||||
- Basic Klingon (tlhIngan Hol) translations
|
||||
- Disabled the screensaver on raspbian with installation script
|
||||
- Added option to truncate the number of vertical lines a calendar item can span if `wrapEvents` is enabled.
|
||||
- Danish translation for "Feels" and "Weeks"
|
||||
- Added option to split multiple day events in calendar to separate numbered events
|
||||
- Slovakian translation
|
||||
- Alerts now can contain Font Awesome icons
|
||||
- Notifications display time can be set in request
|
||||
- Newsfeed: added support for `ARTICLE_INFO_REQUEST` notification
|
||||
- Add `name` config option for calendars to be sent along with event broadcasts
|
||||
|
||||
### Updated
|
||||
|
||||
- Bumped the Electron dependency to v3.0.13 to support the most recent Raspbian. [#1500](https://github.com/MichMich/MagicMirror/issues/1500)
|
||||
- Updated modernizr code in alert module, fixed a small typo there too
|
||||
- More verbose error message on console if the config is malformed
|
||||
- Updated installer script to install Node.js version 10.x
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed temperature displays in currentweather and weatherforecast modules [#1503](https://github.com/MichMich/MagicMirror/issues/1503), [#1511](https://github.com/MichMich/MagicMirror/issues/1511).
|
||||
- Fixed unhandled error on bad git data in updatenotification module [#1285](https://github.com/MichMich/MagicMirror/issues/1285).
|
||||
- Weather forecast now works with openweathermap in new weather module. Daily data are displayed, see issue [#1504](https://github.com/MichMich/MagicMirror/issues/1504).
|
||||
- Fixed analogue clock border display issue where non-black backgrounds used (previous fix for issue 611)
|
||||
- Fixed compatibility issues caused when modules request different versions of Font Awesome, see issue [#1522](https://github.com/MichMich/MagicMirror/issues/1522). MagicMirror now uses [Font Awesome 5 with v4 shims included for backwards compatibility](https://fontawesome.com/how-to-use/on-the-web/setup/upgrading-from-version-4#shims).
|
||||
- Installation script problems with raspbian
|
||||
- Calendar: only show repeating count if the event is actually repeating [#1534](https://github.com/MichMich/MagicMirror/pull/1534)
|
||||
- Calendar: Fix exdate handling when multiple values are specified (comma separated)
|
||||
- Calendar: Fix relative date handling for fulldate events, calculate difference always from start of day [#1572](https://github.com/MichMich/MagicMirror/issues/1572)
|
||||
- Fix null dereference in moduleNeedsUpdate when the module isn't visible
|
||||
- Calendar: Fixed event end times by setting default calendarEndTime to "LT" (Local time format). [#1479]
|
||||
- Calendar: Fixed missing calendar fetchers after server process restarts [#1589](https://github.com/MichMich/MagicMirror/issues/1589)
|
||||
- Notification: fixed background color (was white text on white background)
|
||||
- Use getHeader instead of data.header when creating the DOM so overwriting the function also propagates into it
|
||||
- Fix documentation of `useKMPHwind` option in currentweather
|
||||
|
||||
### New weather module
|
||||
|
||||
- Fixed weather forecast table display [#1499](https://github.com/MichMich/MagicMirror/issues/1499).
|
||||
- Dimmed loading indicator for weather forecast.
|
||||
- Implemented config option `decimalSymbol` [#1499](https://github.com/MichMich/MagicMirror/issues/1499).
|
||||
- Aligned indoor values in current weather vertical [#1499](https://github.com/MichMich/MagicMirror/issues/1499).
|
||||
- Added humidity support to nunjuck unit filter.
|
||||
- Do not display degree symbol for temperature in Kelvin [#1503](https://github.com/MichMich/MagicMirror/issues/1503).
|
||||
- Weather forecast now works with openweathermap for both, `/forecast` and `/forecast/daily`, in new weather module. If you use the `/forecast`-weatherEndpoint, the hourly data are converted to daily data, see issues [#1504](https://github.com/MichMich/MagicMirror/issues/1504), [#1513](https://github.com/MichMich/MagicMirror/issues/1513).
|
||||
- Added fade, fadePoint and maxNumberOfDays properties to the forecast mode [#1516](https://github.com/MichMich/MagicMirror/issues/1516)
|
||||
- Fixed Loading string and decimalSymbol string replace [#1538](https://github.com/MichMich/MagicMirror/issues/1538)
|
||||
- Show Snow amounts in new weather module [#1545](https://github.com/MichMich/MagicMirror/issues/1545)
|
||||
- Added weather.gov as a new weather provider for US locations
|
||||
|
||||
## [2.6.0] - 2019-01-01
|
||||
|
||||
ℹ️ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues updating, make sure you are running the latest version of Node.
|
||||
|
||||
### ✨ Experimental ✨
|
||||
|
||||
- New default [module weather](modules/default/weather). This module will eventually replace the current `currentweather` and `weatherforecast` modules. The new module is still pretty experimental, but it's included so you can give it a try and help us improve this module. Please give us you feedback using [this forum post](https://forum.magicmirror.builders/topic/9335/default-weather-module-refactoring).
|
||||
|
||||
A huge, huge, huge thanks to user @fewieden for all his hard work on the new `weather` module!
|
||||
|
||||
### Added
|
||||
|
||||
- Possibility to add classes to the cell of symbol, title and time of the events of calendar.
|
||||
- Font-awesome 5, still has 4 for backwards compatibility.
|
||||
- Missing `showEnd` in calendar documentation
|
||||
- Screenshot for the new feed module
|
||||
- Screenshot for the compliments module
|
||||
- Screenshot for the clock module
|
||||
- Screenshot for the current weather
|
||||
- Screenshot for the weather forecast module
|
||||
- Portuguese translation for "Feels"
|
||||
- Croatian translation
|
||||
- Fading for dateheaders timeFormat in Calendar [#1464](https://github.com/MichMich/MagicMirror/issues/1464)
|
||||
- Documentation for the existing `scale` option in the Weather Forecast module.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Allow parsing recurring calendar events where the start date is before 1900
|
||||
- Fixed Polish translation for Single Update Info
|
||||
- Ignore entries with unparseable details in the calendar module
|
||||
- Bug showing FullDayEvents one day too long in calendar fixed
|
||||
- Bug in newsfeed when `removeStartTags` is used on the description [#1478](https://github.com/MichMich/MagicMirror/issues/1478)
|
||||
|
||||
### Updated
|
||||
|
||||
- The default calendar setting `showEnd` is changed to `false`.
|
||||
|
||||
### Changed
|
||||
|
||||
- The Weather Forecast module by default displays the ° symbol after every numeric value to be consistent with the Current Weather module.
|
||||
|
||||
## [2.5.0] - 2018-10-01
|
||||
|
||||
### Added
|
||||
|
||||
- Romanian translation for "Feels"
|
||||
- Support multi-line compliments
|
||||
- Simplified Chinese translation for "Feels"
|
||||
- Polish translate for "Feels"
|
||||
- French translate for "Feels"
|
||||
- Translations for newsfeed module
|
||||
- Support for toggling news article in fullscreen
|
||||
- Hungarian translation for "Feels" and "Week"
|
||||
- Spanish translation for "Feels"
|
||||
- Add classes instead of inline style to the message from the module Alert
|
||||
- Support for events having a duration instead of an end
|
||||
- Support for showing end of events through config parameters showEnd and dateEndFormat
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed gzip encoded calendar loading issue #1400.
|
||||
- Mixup between german and spanish translation for newsfeed.
|
||||
- Fixed close dates to be absolute, if no configured in the config.js - module Calendar
|
||||
- Fixed the updatenotification module message about new commits in the repository, so they can be correctly localized in singular and plural form.
|
||||
- Fix for weatherforecast rainfall rounding [#1374](https://github.com/MichMich/MagicMirror/issues/1374)
|
||||
- Fix calendar parsing issue for Midori on RasperryPi Zero w, related to issue #694.
|
||||
- Fix weather city ID link in sample config
|
||||
- Fixed issue with clientonly not updating with IP address and port provided on command line.
|
||||
|
||||
### Updated
|
||||
|
||||
- Updated Simplified Chinese translation
|
||||
- Swedish translations
|
||||
- Hungarian translations for the updatenotification module
|
||||
- Updated Norsk bokmål translation
|
||||
- Updated Norsk nynorsk translation
|
||||
- Consider multi days event as full day events
|
||||
|
||||
## [2.4.1] - 2018-07-04
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix weather parsing issue #1332.
|
||||
|
||||
## [2.4.0] - 2018-07-01
|
||||
|
||||
⚠️ **Warning:** This release includes an updated version of Electron. This requires a Raspberry Pi configuration change to allow the best performance and prevent the CPU from overheating. Please read the information on the [MagicMirror Wiki](https://github.com/michmich/magicmirror/wiki/configuring-the-raspberry-pi#enable-the-open-gl-driver-to-decrease-electrons-cpu-usage).
|
||||
|
||||
ℹ️ **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`
|
||||
|
||||
### Added
|
||||
|
||||
- Enabled translation of feelsLike for module currentweather
|
||||
- Added support for on-going calendar events
|
||||
- Added scroll up in fullscreen newsfeed article view
|
||||
- Changed fullscreen newsfeed width from 100% to 100vw (better results)
|
||||
- Added option to calendar module that colors only the symbol instead of the whole line
|
||||
- Added option for new display format in the calendar module with date headers with times/events below.
|
||||
- Ability to fetch compliments from a remote server
|
||||
- Add regex filtering to calendar module
|
||||
- Customize classes for table
|
||||
- Added option to newsfeed module to only log error parsing a news article if enabled
|
||||
- Add update translations for Português Brasileiro
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgrade to Electron 2.0.0.
|
||||
- Remove yarn-or-npm which breaks production builds.
|
||||
- Invoke module suspend even if no dom content. [#1308](https://github.com/MichMich/MagicMirror/issues/1308)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue where wind chill could not be displayed in Fahrenheit. [#1247](https://github.com/MichMich/MagicMirror/issues/1247)
|
||||
- Fixed issues where a module crashes when it tries to dismiss a non existing alert. [#1240](https://github.com/MichMich/MagicMirror/issues/1240)
|
||||
- In default module currentWeather/currentWeather.js line 296, 300, self.config.animationSpeed can not be found because the notificationReceived function does not have "self" variable.
|
||||
- Fixed browser-side code to work on the Midori browser.
|
||||
- Fixed issue where heat index was reporting incorrect values in Celsius and Fahrenheit. [#1263](https://github.com/MichMich/MagicMirror/issues/1263)
|
||||
- Fixed weatherforecast to use dt_txt field instead of dt to handle timezones better
|
||||
- Newsfeed now remembers to show the description when `"ARTICLE_LESS_DETAILS"` is called if the user wants to always show the description. [#1282](https://github.com/MichMich/MagicMirror/issues/1282)
|
||||
- `clientonly/*.js` is now linted, and one linting error is fixed
|
||||
- Fix issue #1196 by changing underscore to hyphen in locale id, in align with momentjs.
|
||||
- Fixed issue where heat index and wind chill were reporting incorrect values in Kelvin. [#1263](https://github.com/MichMich/MagicMirror/issues/1263)
|
||||
|
||||
### Updated
|
||||
|
||||
- Updated Italian translation
|
||||
- Updated German translation
|
||||
- Updated Dutch translation
|
||||
|
||||
## [2.3.1] - 2018-04-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- Downgrade electron to 1.4.15 to solve the black screen issue.[#1243](https://github.com/MichMich/MagicMirror/issues/1243)
|
||||
|
||||
## [2.3.0] - 2018-04-01
|
||||
|
||||
### Added
|
||||
|
||||
- Add new settings in compliments module: setting time intervals for morning and afternoon
|
||||
- Add system notification `MODULE_DOM_CREATED` for notifying each module when their Dom has been fully loaded.
|
||||
- Add types for module.
|
||||
- Implement Danger.js to notify contributors when CHANGELOG.md is missing in PR.
|
||||
- Allow scrolling in full page article view of default newsfeed module with gesture events from [MMM-Gestures](https://github.com/thobach/MMM-Gestures)
|
||||
- Changed 'compliments.js' - update DOM if remote compliments are loaded instead of waiting one updateInterval to show custom compliments
|
||||
- Automated unit tests utils, deprecated, translator, cloneObject(lockstrings)
|
||||
- Automated integration tests translations
|
||||
- Add advanced filtering to the excludedEvents configuration of the default calendar module
|
||||
- New currentweather module config option: `showFeelsLike`: Shows how it actually feels like. (wind chill or heat index)
|
||||
- New currentweather module config option: `useKMPHwind`: adds an option to see wind speed in Kmph instead of just m/s or Beaufort.
|
||||
- Add dc:date to parsing in newsfeed module, which allows parsing of more rss feeds.
|
||||
|
||||
### Changed
|
||||
|
||||
- Add link to GitHub repository which contains the respective Dockerfile.
|
||||
- Optimized automated unit tests cloneObject, cmpVersions
|
||||
- Update notifications use now translation templates instead of normal strings.
|
||||
- Yarn can be used now as an installation tool
|
||||
- Changed Electron dependency to v1.7.13.
|
||||
|
||||
### Fixed
|
||||
|
||||
- News article in fullscreen (iframe) is now shown in front of modules.
|
||||
- Forecast respects maxNumberOfDays regardless of endpoint.
|
||||
- Fix exception on translation of objects.
|
||||
|
||||
## [2.2.2] - 2018-01-02
|
||||
|
||||
### Added
|
||||
|
||||
- Add missing `package-lock.json`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed Electron dependency to v1.7.10.
|
||||
|
||||
## [2.2.1] - 2018-01-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed linting errors.
|
||||
|
||||
## [2.2.0] - 2018-01-01
|
||||
|
||||
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`
|
||||
|
||||
### Changed
|
||||
|
||||
- Calendar week is now handled with a variable translation in order to move number language specific.
|
||||
- Reverted the Electron dependency back to 1.4.15 since newer version don't seem to work on the Raspberry Pi very well.
|
||||
|
||||
### Added
|
||||
|
||||
- Add option to use [Nunjucks](https://mozilla.github.io/nunjucks/) templates in modules. (See `helloworld` module as an example.)
|
||||
- Add Bulgarian translations for MagicMirror² and Alert module.
|
||||
- Add graceful shutdown of modules by calling `stop` function of each `node_helper` on SIGINT before exiting.
|
||||
- Link update subtext to Github diff of current version versus tracking branch.
|
||||
- Add Catalan translation.
|
||||
- Add ability to filter out newsfeed items based on prohibited words found in title (resolves #1071)
|
||||
- Add options to truncate description support of a feed in newsfeed module
|
||||
- Add reloadInterval option for particular feed in newsfeed module
|
||||
- Add no-cache entries of HTTP headers in newsfeed module (fetcher)
|
||||
- Add Czech translation.
|
||||
- Add option for decimal symbols other than the decimal point for temperature values in both default weather modules: WeatherForecast and CurrentWeather.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue with calendar module showing more than `maximumEntries` allows
|
||||
- WeatherForecast and CurrentWeather are now using HTTPS instead of HTTP
|
||||
- Correcting translation for Indonesian language
|
||||
- Fix issue where calendar icons wouldn't align correctly
|
||||
|
||||
## [2.1.3] - 2017-10-01
|
||||
|
||||
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove Roboto fonts files inside `fonts` and these are installed by npm install command.
|
||||
|
||||
### Added
|
||||
|
||||
- Add `clientonly` script to start only the electron client for a remote server.
|
||||
- Add symbol and color properties of event when `CALENDAR_EVENTS` notification is broadcasted from `default/calendar` module.
|
||||
- Add `.vscode/` folder to `.gitignore` to keep custom Visual Studio Code config out of git.
|
||||
- Add unit test the capitalizeFirstLetter function of newsfeed module.
|
||||
- Add new unit tests for function `shorten` in calendar module.
|
||||
- Add new unit tests for function `getLocaleSpecification` in calendar module.
|
||||
- Add unit test for js/class.js.
|
||||
- Add unit tests for function `roundValue` in currentweather module.
|
||||
- Add test e2e showWeek feature in spanish language.
|
||||
- Add warning Log when is used old authentication method in the calendar module.
|
||||
- Add test e2e for helloworld module with default config text.
|
||||
- Add ability for `currentweather` module to display indoor humidity via INDOOR_HUMIDITY notification.
|
||||
- Add Welsh (Cymraeg) translation.
|
||||
- Add Slack badge to Readme.
|
||||
|
||||
### Updated
|
||||
|
||||
- Changed 'default.js' - listen on all attached interfaces by default.
|
||||
- Add execution of `npm list` after the test are ran in Travis CI.
|
||||
- Change hooks for the vendors e2e tests.
|
||||
- Add log when clientonly failed on starting.
|
||||
- Add warning color when are using full ip whitelist.
|
||||
- Set version of the `express-ipfilter` on 0.3.1.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue with incorrect alignment of analog clock when displayed in the center column of the MM.
|
||||
- Fixed ipWhitelist behaviour to make empty whitelist ([]) allow any and all hosts access to the MM.
|
||||
- Fixed issue with calendar module where 'excludedEvents' count towards 'maximumEntries'.
|
||||
- Fixed issue with calendar module where global configuration of maximumEntries was not overridden by calendar specific config (see module doc).
|
||||
- Fixed issue where `this.file(filename)` returns a path with two hashes.
|
||||
- Workaround for the WeatherForecast API limitation.
|
||||
|
||||
## [2.1.2] - 2017-07-01
|
||||
|
||||
### Changed
|
||||
|
||||
- Revert Docker related changes in favor of [docker-MagicMirror](https://github.com/bastilimbach/docker-MagicMirror). All Docker images are outsourced. ([#856](https://github.com/MichMich/MagicMirror/pull/856))
|
||||
- Change Docker base image (Debian + Node) to an arm based distro (AlpineARM + Node) ([#846](https://github.com/MichMich/MagicMirror/pull/846))
|
||||
- Fix the dockerfile to have it running from the first time.
|
||||
|
||||
### Added
|
||||
|
||||
- Add in option to wrap long calendar events to multiple lines using `wrapEvents` configuration option.
|
||||
- Add test e2e `show title newsfeed` for newsfeed module.
|
||||
- Add task to check configuration file.
|
||||
- Add test check URLs of vendors.
|
||||
- Add test of match current week number on clock module with showWeek configuration.
|
||||
- Add test default modules present modules/default/defaultmodules.js.
|
||||
- Add unit test calendar_modules function capFirst.
|
||||
- Add test for check if exists the directories present in defaults modules.
|
||||
- Add support for showing wind direction as an arrow instead of abbreviation in currentWeather module.
|
||||
- Add support for writing translation functions to support flexible word order
|
||||
- Add test for check if exits the directories present in defaults modules.
|
||||
- Add calendar option to set a separate date format for full day events.
|
||||
- Add ability for `currentweather` module to display indoor temperature via INDOOR_TEMPERATURE notification
|
||||
- Add ability to change the path of the `custom.css`.
|
||||
- Add translation Dutch to Alert module.
|
||||
- Added Romanian translation.
|
||||
|
||||
### Updated
|
||||
|
||||
- Added missing keys to Polish translation.
|
||||
- Added missing key to German translation.
|
||||
- Added better translation with flexible word order to Finnish translation.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix instruction in README for using automatically installer script.
|
||||
- Bug of duplicated compliments as described in [here](https://forum.magicmirror.builders/topic/2381/compliments-module-stops-cycling-compliments).
|
||||
- Fix double message about port when server is starting
|
||||
- Corrected Swedish translations for TODAY/TOMORROW/DAYAFTERTOMORROW.
|
||||
- Removed unused import from js/electron.js
|
||||
- Made calendar.js respect config.timeFormat irrespective of locale setting.
|
||||
- Fixed alignment of analog clock when a large calendar is displayed in the same side bar.
|
||||
|
||||
## [2.1.1] - 2017-04-01
|
||||
|
||||
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`
|
||||
|
||||
### Changed
|
||||
|
||||
- Add `anytime` group for Compliments module.
|
||||
- Compliments module can use remoteFile without default daytime arrays defined.
|
||||
- Installer: Use init config.js from config.js.sample.
|
||||
- Switched out `rrule` package for `rrule-alt` and fixes in `ical.js` in order to fix calendar issues. ([#565](https://github.com/MichMich/MagicMirror/issues/565))
|
||||
- Make mouse events pass through the region fullscreen_above to modules below.
|
||||
- Scaled the splash screen down to make it a bit more subtle.
|
||||
- Replace HTML tables with markdown tables in README files.
|
||||
- Added `DAYAFTERTOMORROW`, `UPDATE_NOTIFICATION` and `UPDATE_NOTIFICATION_MODULE` to Finnish translations.
|
||||
- Run `npm test` on Travis automatically.
|
||||
- Show the splash screen image even when is reboot or halted.
|
||||
- Added some missing translation strings in the sv.json file.
|
||||
- Run task jsonlint to check translation files.
|
||||
- Restructured Test Suite.
|
||||
|
||||
### Added
|
||||
|
||||
- Added Docker support (Pull Request [#673](https://github.com/MichMich/MagicMirror/pull/673)).
|
||||
- Calendar-specific support for `maximumEntries`, and `maximumNumberOfDays`.
|
||||
- Add loaded function to modules, providing an async callback.
|
||||
- Made default newsfeed module aware of gesture events from [MMM-Gestures](https://github.com/thobach/MMM-Gestures)
|
||||
- Add use pm2 for manager process into Installer RaspberryPi script.
|
||||
- Russian Translation.
|
||||
- Afrikaans Translation.
|
||||
- Add postinstall script to notify user that MagicMirror installed successfully despite warnings from NPM.
|
||||
- Init tests using mocha.
|
||||
- Option to use RegExp in Calendar's titleReplace.
|
||||
- Hungarian Translation.
|
||||
- Icelandic Translation.
|
||||
- Add use a script to prevent when is run by SSH session set DISPLAY environment.
|
||||
- Enable ability to set configuration file by the environment variable called MM_CONFIG_FILE.
|
||||
- Option to give each calendar a different color.
|
||||
- Option for colored min-temp and max-temp.
|
||||
- Add test e2e helloworld.
|
||||
- Add test e2e environment.
|
||||
- Add `chai-as-promised` npm module to devDependencies.
|
||||
- Basic set of tests for clock module.
|
||||
- Run e2e test in Travis.
|
||||
- Estonian Translation.
|
||||
- Add test for compliments module for parts of day.
|
||||
- Korean Translation.
|
||||
- Added console warning on startup when deprecated config options are used.
|
||||
- Add option to display temperature unit label to the current weather module.
|
||||
- Added ability to disable wrapping of news items.
|
||||
- Added in the ability to hide events in the calendar module based on simple string filters.
|
||||
- Updated Norwegian translation.
|
||||
- Added hideLoading option for News Feed module.
|
||||
- Added configurable dateFormat to clock module.
|
||||
- Added multiple calendar icon support.
|
||||
- Added tests for Translations, dev argument, version, dev console.
|
||||
- Added test anytime feature compliments module.
|
||||
- Added test ipwhitelist configuration directive.
|
||||
- Added test for calendar module: default, basic-auth, backward compatibility, fail-basic-auth.
|
||||
- Added meta tags to support fullscreen mode on iOS (for server mode)
|
||||
- Added `ignoreOldItems` and `ignoreOlderThan` options to the News Feed module
|
||||
- Added test for MM_PORT environment variable.
|
||||
- Added a configurable Week section to the clock module.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Update .gitignore to not ignore default modules folder.
|
||||
- Remove white flash on boot up.
|
||||
- Added `update` in Raspberry Pi installation script.
|
||||
- Fix an issue where the analog clock looked scrambled. ([#611](https://github.com/MichMich/MagicMirror/issues/611))
|
||||
- If units are set to imperial, the showRainAmount option of weatherforecast will show the correct unit.
|
||||
- Module currentWeather: check if temperature received from api is defined.
|
||||
- Fix an issue with module hidden status changing to `true` although lock string prevented showing it.
|
||||
- Fix newsfeed module bug (removeStartTags)
|
||||
- Fix when is set MM_PORT environment variable.
|
||||
- Fixed missing animation on `this.show(speed)` when module is alone in a region.
|
||||
|
||||
## [2.1.0] - 2016-12-31
|
||||
|
||||
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`
|
||||
|
||||
### Added
|
||||
|
||||
- Finnish translation.
|
||||
- Danish translation.
|
||||
- Turkish translation.
|
||||
- Option to limit access to certain IP addresses based on the value of `ipWhitelist` in the `config.js`, default is access from localhost only (Issue [#456](https://github.com/MichMich/MagicMirror/issues/456)).
|
||||
- Added ability to change the point of time when calendar events get relative.
|
||||
- Add Splash screen on boot.
|
||||
- Add option to show humidity in currentWeather module.
|
||||
- Add VSCode IntelliSense support.
|
||||
- Module API: Add Visibility locking to module system. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#visibility-locking) for more information.
|
||||
- Module API: Method to overwrite the module's header. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#getheader) for more information.
|
||||
- Module API: Option to define the minimum MagicMirror version to run a module. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#requiresversion) for more information.
|
||||
- Calendar module now broadcasts the event list to all other modules using the notification system. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/calendar) for more information.
|
||||
- Possibility to use the calendar feed as the source for the weather (currentweather & weatherforecast) location data. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/weatherforecast) for more information.
|
||||
- Added option to show rain amount in the weatherforecast default module
|
||||
- Add module `updatenotification` to get an update whenever a new version is available. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/updatenotification) for more information.
|
||||
- Add the ability to set timezone on the date display in the Clock Module
|
||||
- Ability to set date format in calendar module
|
||||
- Possibility to use currentweather for the compliments
|
||||
- Added option `disabled` for modules.
|
||||
- Added option `address` to set bind address.
|
||||
- Added option `onlyTemp` for currentweather module to show only current temperature and weather icon.
|
||||
- Added option `remoteFile` to compliments module to load compliment array from filesystem.
|
||||
- Added option `zoom` to scale the whole mirror display with a given factor.
|
||||
- Added option `roundTemp` for currentweather and weatherforecast modules to display temperatures rounded to nearest integer.
|
||||
- Added ability set the classes option to compliments module for style and text size of compliments.
|
||||
- Added ability to configure electronOptions
|
||||
- Calendar module: option to hide private events
|
||||
- Add root_path for global vars
|
||||
|
||||
### Updated
|
||||
|
||||
- Modified translations for Frysk.
|
||||
- Modified core English translations.
|
||||
- Updated package.json as a result of Snyk security update.
|
||||
- Improve object instantiation to prevent reference errors.
|
||||
- Improve logger. `Log.log()` now accepts multiple arguments.
|
||||
- Remove extensive logging in newsfeed node helper.
|
||||
- Calendar times are now uniformly capitalized.
|
||||
- Modules are now secure, and Helmet is now used to prevent abuse of the Mirror's API.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Solve an issue where module margins would appear when the first module of a section was hidden.
|
||||
- Solved visual display errors on chrome, if all modules in one of the right sections are hidden.
|
||||
- Global and Module default config values are no longer modified when setting config values.
|
||||
- Hide a region if all modules in a region are hidden. Prevention unwanted margins.
|
||||
- Replaced `electron-prebuilt` package with `electron` in order to fix issues that would happen after 2017.
|
||||
- Documentation of alert module
|
||||
|
||||
## [2.0.5] - 2016-09-20
|
||||
|
||||
### Added
|
||||
|
||||
- Added ability to remove tags from the beginning or end of newsfeed items in 'newsfeed.js'.
|
||||
- Added ability to define "the day after tomorrow" for calendar events (Definition for German and Dutch already included).
|
||||
- Added CII Badge (we are compliant with the CII Best Practices)
|
||||
- Add support for doing http basic auth when loading calendars
|
||||
- Add the ability to turn off and on the date display in the Clock Module
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix typo in installer.
|
||||
- Add message to unsupported Pi error to mention that Pi Zeros must use server only mode, as ARMv6 is unsupported. Closes #374.
|
||||
- Fix API url for weather API.
|
||||
|
||||
### Updated
|
||||
|
||||
- Force fullscreen when kioskmode is active.
|
||||
- Update the .github templates and information with more modern information.
|
||||
- Update the Gruntfile with a more functional StyleLint implementation.
|
||||
|
||||
## [2.0.4] - 2016-08-07
|
||||
|
||||
### Added
|
||||
|
||||
- Brazilian Portuguese Translation.
|
||||
- Option to enable Kiosk mode.
|
||||
- Added ability to start the app with Dev Tools.
|
||||
- Added ability to turn off the date display in `clock.js` when in analog mode.
|
||||
- Greek Translation
|
||||
|
||||
### Fixed
|
||||
|
||||
- Prevent `getModules()` selectors from returning duplicate entries.
|
||||
- Append endpoints of weather modules with `/` to retrieve the correct data. (Issue [#337](https://github.com/MichMich/MagicMirror/issues/337))
|
||||
- Corrected grammar in `module.js` from 'suspend' to 'suspended'.
|
||||
- Fixed openweathermap.org URL in config sample.
|
||||
- Prevent currentweather module from crashing when received data object is incorrect.
|
||||
- Fix issue where translation loading prevented the UI start-up when the language was set to 'en'. (Issue [#388](https://github.com/MichMich/MagicMirror/issues/388))
|
||||
|
||||
### Updated
|
||||
|
||||
- Updated package.json to fix possible vulnerabilities. (Using Snyk)
|
||||
- Updated weathericons
|
||||
- Updated default weatherforecast to work with the new icons.
|
||||
- More detailed error message in case config file couldn't be loaded.
|
||||
|
||||
## [2.0.3] - 2016-07-12
|
||||
|
||||
### Added
|
||||
|
||||
- Add max newsitems parameter to the newsfeed module.
|
||||
- Translations for Simplified Chinese, Traditional Chinese and Japanese.
|
||||
- Polish Translation
|
||||
- Add an analog clock in addition to the digital one.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Edit Alert Module to display title & message if they are provided in the notification (Issue [#300](https://github.com/MichMich/MagicMirror/issues/300))
|
||||
- Removed 'null' reference from updateModuleContent(). This fixes recent Edge and Internet Explorer browser displays (Issue [#319](https://github.com/MichMich/MagicMirror/issues/319))
|
||||
|
||||
### Changed
|
||||
|
||||
- Added default string to calendar titleReplace.
|
||||
|
||||
## [2.0.2] - 2016-06-05
|
||||
|
||||
### Added
|
||||
|
||||
- Norwegian Translations (nb and nn)
|
||||
- Portuguese Translation
|
||||
- Swedish Translation
|
||||
|
||||
### Fixed
|
||||
|
||||
- Added reference to Italian Translation.
|
||||
- Added the missing NE translation to all languages. [#344](https://github.com/MichMich/MagicMirror/issues/344)
|
||||
- Added proper User-Agent string to calendar call.
|
||||
|
||||
### Changed
|
||||
|
||||
- Add option to use locationID in weather modules.
|
||||
|
||||
## [2.0.1] - 2016-05-18
|
||||
|
||||
### Added
|
||||
|
||||
- Changelog
|
||||
- Italian Translation
|
||||
|
||||
### Changed
|
||||
|
||||
- Improve the installer by fetching the latest Node.js without any 3rd party interferences.
|
||||
|
||||
## [2.0.0] - 2016-05-03
|
||||
|
||||
### Initial release of MagicMirror²
|
||||
|
||||
It includes (but is not limited to) the following features:
|
||||
|
||||
- Modular system allowing 3rd party plugins.
|
||||
- An Node/Electron based application taking away the need for external servers or browsers.
|
||||
- A complete development API documentation.
|
||||
- Small cute fairies that kiss you while you sleep.
|
||||
|
||||
## [1.0.0] - 2014-02-16
|
||||
|
||||
### Initial release of MagicMirror.
|
||||
|
||||
This was part of the blogpost: [https://michaelteeuw.nl/post/83916869600/magic-mirror-part-vi-production-of-the](https://michaelteeuw.nl/post/83916869600/magic-mirror-part-vi-production-of-the)
|
28
Gruntfile.js
28
Gruntfile.js
@@ -1,28 +0,0 @@
|
||||
module.exports = function(grunt) {
|
||||
require("time-grunt")(grunt);
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON("package.json"),
|
||||
eslint: {
|
||||
options: {
|
||||
configFile: ".eslintrc.json"
|
||||
},
|
||||
target: ["js/*.js", "modules/default/*.js", "serveronly/*.js", "*.js"]
|
||||
},
|
||||
postcss: {
|
||||
lint: {
|
||||
options: {
|
||||
processors: [
|
||||
require("stylelint")({"extends": "stylelint-config-standard", "font-family-name-quotes": "double-where-recommended"}),
|
||||
require("postcss-reporter")({ clearMessages: true })
|
||||
]
|
||||
},
|
||||
dist: {
|
||||
src: "**/**/**/**/**/**/**/**.css"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
grunt.loadNpmTasks("grunt-eslint");
|
||||
grunt.loadNpmTasks("grunt-postcss");
|
||||
grunt.registerTask("default", ["eslint", "postcss:lint"]);
|
||||
};
|
@@ -1,7 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
=====================
|
||||
# The MIT License (MIT)
|
||||
|
||||
Copyright © 2016 Michael Teeuw
|
||||
Copyright © 2016-2019 Michael Teeuw
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
|
136
README.md
136
README.md
@@ -3,129 +3,43 @@
|
||||
<p align="center">
|
||||
<a href="https://david-dm.org/MichMich/MagicMirror"><img src="https://david-dm.org/MichMich/MagicMirror.svg" alt="Dependency Status"></a>
|
||||
<a href="https://david-dm.org/MichMich/MagicMirror#info=devDependencies"><img src="https://david-dm.org/MichMich/MagicMirror/dev-status.svg" alt="devDependency Status"></a>
|
||||
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-v5.10.1-brightgreen.svg" alt="Node Version"></a>
|
||||
<a href="http://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
||||
<a href="https://travis-ci.org/MichMich/MagicMirror"><img src="https://travis-ci.org/MichMich/MagicMirror.svg" alt="Travis"></a>
|
||||
<a href="https://bestpractices.coreinfrastructure.org/projects/347"><img src="https://bestpractices.coreinfrastructure.org/projects/347/badge"></a>
|
||||
<a href="https://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
||||
<a href="https://travis-ci.com/MichMich/MagicMirror"><img src="https://travis-ci.com/MichMich/MagicMirror.svg" alt="Travis"></a>
|
||||
<a href="https://snyk.io/test/github/MichMich/MagicMirror"><img src="https://snyk.io/test/github/MichMich/MagicMirror/badge.svg" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/MichMich/MagicMirror" style="max-width:100%;"></a>
|
||||
</p>
|
||||
|
||||
**MagicMirror²** is an open source modular smart mirror platform. With a growing list of installable modules, the **MagicMirror²** allows you to convert your hallway or bathroom mirror into your personal assistant. **MagicMirror²** is built by the creator of [the original MagicMirror](http://michaelteeuw.nl/tagged/magicmirror) with the incredible help of a [growing community of contributors](https://github.com/MichMich/MagicMirror/graphs/contributors).
|
||||
**MagicMirror²** is an open source modular smart mirror platform. With a growing list of installable modules, the **MagicMirror²** allows you to convert your hallway or bathroom mirror into your personal assistant. **MagicMirror²** is built by the creator of [the original MagicMirror](https://michaelteeuw.nl/tagged/magicmirror) with the incredible help of a [growing community of contributors](https://github.com/MichMich/MagicMirror/graphs/contributors).
|
||||
|
||||
MagicMirror² focuses on a modular plugin system and uses [Electron](http://electron.atom.io/) as an application wrapper. So no more web server or browser installs necessary!
|
||||
MagicMirror² focuses on a modular plugin system and uses [Electron](https://www.electronjs.org/) as an application wrapper. So no more web server or browser installs necessary!
|
||||
|
||||
**NOTE:** This version is in currently in beta stage. Please use the master branch if you need a thoroughly tested version.
|
||||
## Documentation
|
||||
|
||||
## Table Of Contents
|
||||
For the full documentation including **[installation instructions](https://docs.magicmirror.builders/getting-started/installation.html)**, please visit our dedicated documentation website: [https://docs.magicmirror.builders](https://docs.magicmirror.builders).
|
||||
|
||||
- [Usage](#usage)
|
||||
- [Configuration](#configuration)
|
||||
- [Modules](#modules)
|
||||
- [Known Issues](#known-issues)
|
||||
- [community](#community)
|
||||
- [Contributing Guidelines](#contributing-guidelines)
|
||||
## Links
|
||||
|
||||
## Usage
|
||||
|
||||
#### Raspberry Pi Support
|
||||
Electron, the app wrapper around MagicMirror², only supports the Raspberry Pi 2 & 3. The Raspberry Pi 1 is currently **not** supported. If you want to run this on a Raspberry Pi 1, use the [server only](#server-only) feature and setup a fullscreen browser yourself.
|
||||
|
||||
#### Automatic Installer (Raspberry Pi Only!)
|
||||
|
||||
Execute the following command on your Raspberry Pi to install MagicMirror²:
|
||||
````
|
||||
curl -sL https://raw.githubusercontent.com/MichMich/MagicMirror/installers/raspberry.sh | bash
|
||||
````
|
||||
|
||||
#### Manual Installation
|
||||
|
||||
1. Download and install the latest Node.js version.
|
||||
2. Clone the repository and check out the beta branch: `git clone https://github.com/MichMich/MagicMirror`
|
||||
3. Enter the repository: `cd ~/MagicMirror`
|
||||
4. Install and run the app: `npm install && npm start`
|
||||
|
||||
**Important:** `npm start` does **not** work via SSH, use `DISPLAY=:0 nohup npm start &` instead. This starts the mirror on the remote display.
|
||||
|
||||
#### Server Only
|
||||
|
||||
In some cases, you want to start the application without an actual app window. In this case, execute the following command from the MagicMirror folder: `node serveronly`. This will start the server, after which you can open the application in your browser of choice.
|
||||
|
||||
#### Raspberry Configuration & Auto Start.
|
||||
|
||||
The following wiki links are helpful in the configuration of your MagicMirror² operating system:
|
||||
- [Configuring the Raspberry Pi](https://github.com/MichMich/MagicMirror/wiki/Configuring-the-Raspberry-Pi)
|
||||
- [Auto Starting MagicMirror](https://github.com/MichMich/MagicMirror/wiki/Auto-Starting-MagicMirror)
|
||||
|
||||
#### Updating you MagicMirror²
|
||||
|
||||
If you want to update your MagicMirror² to the latest version, use your terminal to go to your Magic Mirror folder and type the following command:
|
||||
|
||||
````
|
||||
git pull
|
||||
````
|
||||
|
||||
If you changed nothing more than the config or the modules, this should work without any problems.
|
||||
Type `git status` to see your changes, if there are any, you can reset them with `git reset --hard`. After that, git pull should be possible.
|
||||
|
||||
## Configuration
|
||||
|
||||
1. Duplicate `config/config.js.sample` to `config/config.js`.
|
||||
2. Modify your required settings.
|
||||
|
||||
The following properties can be configured:
|
||||
|
||||
|
||||
| **Option** | **Description** |
|
||||
| --- | --- |
|
||||
| `port` | The port on which the MagicMirror² server will run on. The default value is `8080`. |
|
||||
| `language` | The language of the interface. (Note: Not all elements will be localized.) Possible values are `en`, `nl`, `ru`, `fr`, etc., but the default value is `en`. |
|
||||
| `timeFormat` | The form of time notation that will be used. Possible values are `12` or `24`. The default is `24`. |
|
||||
| `units` | The units that will be used in the default weather modules. Possible values are `metric` or `imperial`. The default is `metric`. |
|
||||
| `modules` | An array of active modules. **The array must contain objects. See the next table below for more information.** |
|
||||
|
||||
Module configuration:
|
||||
|
||||
| **Option** | **Description** |
|
||||
| --- | --- |
|
||||
| `module` | The name of the module. This can also contain the subfolder. Valid examples include `clock`, `default/calendar` and `custommodules/mymodule`. |
|
||||
| `position` | The location of the module in which the module will be loaded. Possible values are `top_ bar`, `top_left`, `top_center`, `top_right`, `upper_third`, `middle_center`, `lower_third`, `bottom_left`, `bottom_center`, `bottom_right`, `bottom_bar`, `fullscreen_above`, and `fullscreen_below`. This field is optional but most modules require this field to set. Check the documentation of the module for more information. Multiple modules with the same position will be ordered based on the order in the configuration file. |
|
||||
| `classes` | Additional classes which are passed to the module. The field is optional. |
|
||||
| `header` | To display a header text above the module, add the header property. This field is optional. |
|
||||
| `config` | An object with the module configuration properties. Check the documentation of the module for more information. This field is optional, unless the module requires extra configuration. |
|
||||
|
||||
## Modules
|
||||
|
||||
The following modules are installed by default.
|
||||
|
||||
- [**Clock**](modules/default/clock)
|
||||
- [**Calendar**](modules/default/calendar)
|
||||
- [**Current Weather**](modules/default/currentweather)
|
||||
- [**Weather Forecast**](modules/default/weatherforecast)
|
||||
- [**News Feed**](modules/default/newsfeed)
|
||||
- [**Compliments**](modules/default/compliments)
|
||||
- [**Hello World**](modules/default/helloworld)
|
||||
- [**Alert**](modules/default/alert)
|
||||
|
||||
For more available modules, check out out the wiki page: [MagicMirror² Modules](https://github.com/MichMich/MagicMirror/wiki/MagicMirror²-Modules). If you want to build your own modules, check out the [MagicMirror² Module Development Documentation](modules) and don't forget to add it to the wiki and the [forum](https://forum.magicmirror.builders/category/7/showcase)!
|
||||
|
||||
## Known issues
|
||||
|
||||
- Electron seems to have some issues on certain Raspberry Pi 2's. See [#145](https://github.com/MichMich/MagicMirror/issues/145).
|
||||
- MagicMirror² (Electron) sometimes quits without an error after an extended period of use. See [#150](https://github.com/MichMich/MagicMirror/issues/150).
|
||||
|
||||
## Community
|
||||
|
||||
The community around the MagicMirror² is constantly growing. We even have a [forum](https://forum.magicmirror.builders) now where you can share your ideas, ask questions, help others and get inspired by other builders. We would love to see you there!
|
||||
- Website: [https://magicmirror.builders](https://magicmirror.builders)
|
||||
- Documentation: [https://docs.magicmirror.builders](https://docs.magicmirror.builders)
|
||||
- Forum: [https://forum.magicmirror.builders](https://forum.magicmirror.builders)
|
||||
- Discord: [https://discord.gg/J5BAtvx](https://discord.gg/J5BAtvx)
|
||||
- Blog: [https://michaelteeuw.nl/tagged/magicmirror](https://michaelteeuw.nl/tagged/magicmirror)
|
||||
- Donations: [https://magicmirror.builders/#donate](https://magicmirror.builders/#donate)
|
||||
|
||||
## Contributing Guidelines
|
||||
|
||||
Contributions of all kinds are welcome, not only in the form of code but also with regards bug reports and documentation.
|
||||
Contributions of all kinds are welcome, not only in the form of code but also with regards bug reports and documentation. For the full contribution guidelines, check out: [https://docs.magicmirror.builders/getting-started/contributing.html](https://docs.magicmirror.builders/getting-started/contributing.html)
|
||||
|
||||
Please keep the following in mind:
|
||||
## Enjoying MagicMirror? Consider a donation!
|
||||
|
||||
- **Bug Reports**: Make sure you're running the latest version. If the issue(s) still persist: please open a clearly documented issue with a clear title.
|
||||
- **Minor Bug Fixes**: Please send a pull request with a clear explanation of the issue or a link to the issue it solves.
|
||||
- **Major Bug Fixes**: please discuss your approach in an GitHub issue before you start to alter a big part of the code.
|
||||
- **New Features**: please please discuss in a GitHub issue before you start to alter a big part of the code. Without discussion upfront, the pull request will not be accepted / merged.
|
||||
MagicMirror² is opensource and free. That doesn't mean we don't need any money.
|
||||
|
||||
Thanks for your help in making MagicMirror² better!
|
||||
Please consider a donation to help us cover the ongoing costs like webservers and email services.
|
||||
If we receive enough donations we might even be able to free up some working hours and spend some extra time improving the MagicMirror² core.
|
||||
|
||||
To donate, please follow [this](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G5D8E9MR5DTD2&source=url) link.
|
||||
|
||||
<p align="center">
|
||||
<br>
|
||||
<a href="https://forum.magicmirror.builders/topic/728/magicmirror-is-voted-number-1-in-the-magpi-top-50"><img src="https://magicmirror.builders/img/magpi-best-watermark-custom.png" width="150" alt="MagPi Top 50"></a>
|
||||
</p>
|
||||
|
106
clientonly/index.js
Normal file
106
clientonly/index.js
Normal file
@@ -0,0 +1,106 @@
|
||||
"use strict";
|
||||
|
||||
// Use separate scope to prevent global scope pollution
|
||||
(function () {
|
||||
var config = {};
|
||||
|
||||
// Helper function to get server address/hostname from either the commandline or env
|
||||
function getServerAddress() {
|
||||
// Helper function to get command line parameters
|
||||
// Assumes that a cmdline parameter is defined with `--key [value]`
|
||||
function getCommandLineParameter(key, defaultValue = undefined) {
|
||||
var index = process.argv.indexOf(`--${key}`);
|
||||
var value = index > -1 ? process.argv[index + 1] : undefined;
|
||||
return value !== undefined ? String(value) : defaultValue;
|
||||
}
|
||||
|
||||
// Prefer command line arguments over environment variables
|
||||
["address", "port"].forEach((key) => {
|
||||
config[key] = getCommandLineParameter(key, process.env[key.toUpperCase()]);
|
||||
});
|
||||
|
||||
// determine if "--use-tls"-flag was provided
|
||||
config["tls"] = process.argv.indexOf("--use-tls") > 0;
|
||||
}
|
||||
|
||||
function getServerConfig(url) {
|
||||
// Return new pending promise
|
||||
return new Promise((resolve, reject) => {
|
||||
// Select http or https module, depending on reqested url
|
||||
const lib = url.startsWith("https") ? require("https") : require("http");
|
||||
const request = lib.get(url, (response) => {
|
||||
var configData = "";
|
||||
|
||||
// Gather incoming data
|
||||
response.on("data", function (chunk) {
|
||||
configData += chunk;
|
||||
});
|
||||
// Resolve promise at the end of the HTTP/HTTPS stream
|
||||
response.on("end", function () {
|
||||
resolve(JSON.parse(configData));
|
||||
});
|
||||
});
|
||||
|
||||
request.on("error", function (error) {
|
||||
reject(new Error(`Unable to read config from server (${url} (${error.message}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function fail(message, code = 1) {
|
||||
if (message !== undefined && typeof message === "string") {
|
||||
console.log(message);
|
||||
} else {
|
||||
console.log("Usage: 'node clientonly --address 192.168.1.10 --port 8080 [--use-tls]'");
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
getServerAddress();
|
||||
|
||||
(config.address && config.port) || fail();
|
||||
var prefix = config.tls ? "https://" : "http://";
|
||||
|
||||
// Only start the client if a non-local server was provided
|
||||
if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) === -1) {
|
||||
getServerConfig(`${prefix}${config.address}:${config.port}/config/`)
|
||||
.then(function (configReturn) {
|
||||
// Pass along the server config via an environment variable
|
||||
var env = Object.create(process.env);
|
||||
var options = { env: env };
|
||||
configReturn.address = config.address;
|
||||
configReturn.port = config.port;
|
||||
configReturn.tls = config.tls;
|
||||
env.config = JSON.stringify(configReturn);
|
||||
|
||||
// Spawn electron application
|
||||
const electron = require("electron");
|
||||
const child = require("child_process").spawn(electron, ["js/electron.js"], options);
|
||||
|
||||
// Pipe all child process output to current stdout
|
||||
child.stdout.on("data", function (buf) {
|
||||
process.stdout.write(`Client: ${buf}`);
|
||||
});
|
||||
|
||||
// Pipe all child process errors to current stderr
|
||||
child.stderr.on("data", function (buf) {
|
||||
process.stderr.write(`Client: ${buf}`);
|
||||
});
|
||||
|
||||
child.on("error", function (err) {
|
||||
process.stdout.write(`Client: ${err}`);
|
||||
});
|
||||
|
||||
child.on("close", (code) => {
|
||||
if (code !== 0) {
|
||||
console.log(`There something wrong. The clientonly is not running code ${code}`);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function (reason) {
|
||||
fail(`Unable to connect to server: (${reason})`);
|
||||
});
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
})();
|
@@ -1,75 +1,107 @@
|
||||
/* Magic Mirror Config Sample
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*
|
||||
* For more information on how you can configure this file
|
||||
* See https://github.com/MichMich/MagicMirror#configuration
|
||||
*
|
||||
*/
|
||||
|
||||
var config = {
|
||||
address: "localhost", // Address to listen on, can be:
|
||||
// - "localhost", "127.0.0.1", "::1" to listen on loopback interface
|
||||
// - another specific IPv4/6 to listen on a specific interface
|
||||
// - "0.0.0.0", "::" to listen on any interface
|
||||
// Default, when address config is left out or empty, is "localhost"
|
||||
port: 8080,
|
||||
basePath: "/", // The URL path where MagicMirror is hosted. If you are using a Reverse proxy
|
||||
// you must set the sub path here. basePath must end with a /
|
||||
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], // Set [] to allow all IP addresses
|
||||
// or add a specific IPv4 of 192.168.1.5 :
|
||||
// ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.1.5"],
|
||||
// or IPv4 range of 192.168.3.0 --> 192.168.3.15 use CIDR format :
|
||||
// ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.3.0/28"],
|
||||
|
||||
language: 'en',
|
||||
useHttps: false, // Support HTTPS or not, default "false" will use HTTP
|
||||
httpsPrivateKey: "", // HTTPS private key path, only require when useHttps is true
|
||||
httpsCertificate: "", // HTTPS Certificate path, only require when useHttps is true
|
||||
|
||||
language: "en",
|
||||
logLevel: ["INFO", "LOG", "WARN", "ERROR"],
|
||||
timeFormat: 24,
|
||||
units: 'metric',
|
||||
units: "metric",
|
||||
// serverOnly: true/false/"local" ,
|
||||
// local for armv6l processors, default
|
||||
// starts serveronly and then starts chrome browser
|
||||
// false, default for all NON-armv6l devices
|
||||
// true, force serveronly mode, because you want to.. no UI on this device
|
||||
|
||||
modules: [
|
||||
{
|
||||
module: 'alert',
|
||||
module: "alert",
|
||||
},
|
||||
{
|
||||
module: 'clock',
|
||||
position: 'top_left'
|
||||
module: "updatenotification",
|
||||
position: "top_bar"
|
||||
},
|
||||
{
|
||||
module: 'calendar',
|
||||
header: 'US Holidays',
|
||||
position: 'top_left',
|
||||
module: "clock",
|
||||
position: "top_left"
|
||||
},
|
||||
{
|
||||
module: "calendar",
|
||||
header: "US Holidays",
|
||||
position: "top_left",
|
||||
config: {
|
||||
calendars: [
|
||||
{
|
||||
symbol: 'calendar-check-o ',
|
||||
url: 'webcal://www.calendarlabs.com/templates/ical/US-Holidays.ics'
|
||||
}
|
||||
symbol: "calendar-check",
|
||||
url: "webcal://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics" }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
module: 'compliments',
|
||||
position: 'lower_third'
|
||||
module: "compliments",
|
||||
position: "lower_third"
|
||||
},
|
||||
{
|
||||
module: 'currentweather',
|
||||
position: 'top_right',
|
||||
module: "currentweather",
|
||||
position: "top_right",
|
||||
config: {
|
||||
location: 'New York',
|
||||
appid: 'YOUR_OPENWEATHER_API_KEY'
|
||||
location: "New York",
|
||||
locationID: "", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city
|
||||
appid: "YOUR_OPENWEATHER_API_KEY"
|
||||
}
|
||||
},
|
||||
{
|
||||
module: 'weatherforecast',
|
||||
position: 'top_right',
|
||||
header: 'Weather Forecast',
|
||||
module: "weatherforecast",
|
||||
position: "top_right",
|
||||
header: "Weather Forecast",
|
||||
config: {
|
||||
location: 'New York',
|
||||
appid: 'YOUR_OPENWEATHER_API_KEY'
|
||||
location: "New York",
|
||||
locationID: "5128581", //ID from http://bulk.openweathermap.org/sample/city.list.json.gz; unzip the gz file and find your city
|
||||
appid: "YOUR_OPENWEATHER_API_KEY"
|
||||
}
|
||||
},
|
||||
{
|
||||
module: 'newsfeed',
|
||||
position: 'bottom_bar',
|
||||
module: "newsfeed",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
feeds: [
|
||||
{
|
||||
title: "New York Times",
|
||||
url: "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml"
|
||||
url: "https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml"
|
||||
}
|
||||
],
|
||||
showSourceTitle: true,
|
||||
showPublishDate: true
|
||||
showPublishDate: true,
|
||||
broadcastNewsFeeds: true,
|
||||
broadcastNewsUpdates: true
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== 'undefined') {module.exports = config;}
|
||||
if (typeof module !== "undefined") {module.exports = config;}
|
||||
|
@@ -1,14 +0,0 @@
|
||||
/*****************************************************
|
||||
* Magic Mirror *
|
||||
* Custom CSS *
|
||||
* *
|
||||
* By Michael Teeuw http://michaelteeuw.nl *
|
||||
* MIT Licensed. *
|
||||
* *
|
||||
* Add any custom CSS below. *
|
||||
* Changes to this files will be ignored by GIT. *
|
||||
*****************************************************/
|
||||
|
||||
body {
|
||||
|
||||
}
|
52
css/main.css
52
css/main.css
@@ -1,10 +1,11 @@
|
||||
html {
|
||||
cursor: none;
|
||||
overflow:hidden;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -94,7 +95,7 @@ body {
|
||||
header {
|
||||
text-transform: uppercase;
|
||||
font-size: 15px;
|
||||
font-family: "Roboto Condensed";
|
||||
font-family: "Roboto Condensed", Arial, Helvetica, sans-serif;
|
||||
font-weight: 400;
|
||||
border-bottom: 1px solid #666;
|
||||
line-height: 15px;
|
||||
@@ -113,11 +114,22 @@ sup {
|
||||
*/
|
||||
|
||||
.module {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.module:first-child {
|
||||
margin-top: 0;
|
||||
.region.bottom .module {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.no-wrap {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.pre-line {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,10 +146,16 @@ sup {
|
||||
left: -60px;
|
||||
right: -60px;
|
||||
bottom: -60px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.region.fullscreen * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.region.right {
|
||||
right: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.region.top {
|
||||
@@ -148,6 +166,10 @@ sup {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.region.bottom .container {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.region.top .container:empty {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -155,10 +177,6 @@ sup {
|
||||
.region.top.center,
|
||||
.region.bottom.center {
|
||||
left: 50%;
|
||||
-moz-transform: translateX(-50%);
|
||||
-o-transform: translateX(-50%);
|
||||
-webkit-transform: translateX(-50%);
|
||||
-ms-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
@@ -172,10 +190,6 @@ sup {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.region.bottom .container {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.region.bottom .container:empty {
|
||||
margin-top: 0;
|
||||
}
|
||||
@@ -195,10 +209,6 @@ sup {
|
||||
.region.middle.center {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
-moz-transform: translateY(-50%);
|
||||
-o-transform: translateY(-50%);
|
||||
-webkit-transform: translateY(-50%);
|
||||
-ms-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
@@ -218,10 +228,6 @@ sup {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.region.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.region table {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
|
17
dangerfile.js
Normal file
17
dangerfile.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { danger, fail, warn } from "danger";
|
||||
|
||||
// Check if the CHANGELOG.md file has been edited
|
||||
// Fail the build and post a comment reminding submitters to do so if it wasn't changed
|
||||
if (!danger.git.modified_files.includes("CHANGELOG.md")) {
|
||||
warn("Please include an updated `CHANGELOG.md` file.<br>This way we can keep track of all the contributions.");
|
||||
}
|
||||
|
||||
// Check if the PR request is send to the master branch.
|
||||
// This should only be done by MichMich.
|
||||
if (danger.github.pr.base.ref === "master" && danger.github.pr.user.login !== "MichMich") {
|
||||
// Check if the PR body or title includes the text: #accepted.
|
||||
// If not, the PR will fail.
|
||||
if ((danger.github.pr.body + danger.github.pr.title).includes("#accepted")) {
|
||||
fail("Please send all your pull requests to the `develop` branch.<br>Pull requests on the `master` branch will not be accepted.");
|
||||
}
|
||||
}
|
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
12
fonts/package-lock.json
generated
Normal file
12
fonts/package-lock.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "magicmirror-fonts",
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"roboto-fontface": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz",
|
||||
"integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g=="
|
||||
}
|
||||
}
|
||||
}
|
15
fonts/package.json
Normal file
15
fonts/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "magicmirror-fonts",
|
||||
"description": "Package for fonts use by MagicMirror Core.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/MichMich/MagicMirror.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/MichMich/MagicMirror/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"roboto-fontface": "^0.10.0"
|
||||
}
|
||||
}
|
@@ -2,94 +2,57 @@
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src:
|
||||
local("Roboto Thin"),
|
||||
local("Roboto-Thin"),
|
||||
url("Roboto-Thin/Roboto-Thin.woff2") format("woff2"),
|
||||
url("Roboto-Thin/Roboto-Thin.woff") format("woff"),
|
||||
url("Roboto-Thin/Roboto-Thin.ttf") format("truetype");
|
||||
src: local("Roboto Thin"), local("Roboto-Thin"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto Condensed";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src:
|
||||
local("Roboto Condensed Light"),
|
||||
local("RobotoCondensed-Light"),
|
||||
url("RobotoCondensed-Light/RobotoCondensed-Light.woff2") format("woff2"),
|
||||
url("RobotoCondensed-Light/RobotoCondensed-Light.woff") format("woff"),
|
||||
url("RobotoCondensed-Light/RobotoCondensed-Light.ttf") format("truetype");
|
||||
src: local("Roboto Condensed Light"), local("RobotoCondensed-Light"), url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto Condensed";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src:
|
||||
local("Roboto Condensed"),
|
||||
local("RobotoCondensed-Regular"),
|
||||
url("RobotoCondensed-Regular/RobotoCondensed-Regular.woff2") format("woff2"),
|
||||
url("RobotoCondensed-Regular/RobotoCondensed-Regular.woff") format("woff"),
|
||||
url("RobotoCondensed-Regular/RobotoCondensed-Regular.ttf") format("truetype");
|
||||
src: local("Roboto Condensed"), local("RobotoCondensed-Regular"), url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto Condensed";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src:
|
||||
local("Roboto Condensed Bold"),
|
||||
local("RobotoCondensed-Bold"),
|
||||
url("RobotoCondensed-Bold/RobotoCondensed-Bold.woff2") format("woff2"),
|
||||
url("RobotoCondensed-Bold/RobotoCondensed-Bold.woff") format("woff"),
|
||||
url("RobotoCondensed-Bold/RobotoCondensed-Bold.ttf") format("truetype");
|
||||
src: local("Roboto Condensed Bold"), local("RobotoCondensed-Bold"), url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src:
|
||||
local("Roboto"),
|
||||
local("Roboto-Regular"),
|
||||
url("Roboto-Regular/Roboto-Regular.woff2") format("woff2"),
|
||||
url("Roboto-Regular/Roboto-Regular.woff") format("woff"),
|
||||
url("Roboto-Regular/Roboto-Regular.ttf") format("truetype");
|
||||
src: local("Roboto"), local("Roboto-Regular"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src:
|
||||
local("Roboto Medium"),
|
||||
local("Roboto-Medium"),
|
||||
url("Roboto-Medium/Roboto-Medium.woff2") format("woff2"),
|
||||
url("Roboto-Medium/Roboto-Medium.woff") format("woff"),
|
||||
url("Roboto-Medium/Roboto-Medium.ttf") format("truetype");
|
||||
src: local("Roboto Medium"), local("Roboto-Medium"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src:
|
||||
local("Roboto Bold"),
|
||||
local("Roboto-Bold"),
|
||||
url("Roboto-Bold/Roboto-Bold.woff2") format("woff2"),
|
||||
url("Roboto-Bold/Roboto-Bold.woff") format("woff"),
|
||||
url("Roboto-Bold/Roboto-Bold.ttf") format("truetype");
|
||||
src: local("Roboto Bold"), local("Roboto-Bold"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src:
|
||||
local("Roboto Light"),
|
||||
local("Roboto-Light"),
|
||||
url("Roboto-Light/Roboto-Light.woff2") format("woff2"),
|
||||
url("Roboto-Light/Roboto-Light.woff") format("woff"),
|
||||
url("Roboto-Light/Roboto-Light.ttf") format("truetype");
|
||||
src: local("Roboto Light"), local("Roboto-Light"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff") format("woff");
|
||||
}
|
||||
|
17
index.html
17
index.html
@@ -1,13 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Magic Mirror</title>
|
||||
<title>MagicMirror²</title>
|
||||
<meta name="google" content="notranslate" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
|
||||
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
|
||||
<link rel="stylesheet" type="text/css" href="css/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="fonts/roboto.css">
|
||||
<!-- custom.css is loaded by the loader.js to make sure it's loaded after the module css files. -->
|
||||
|
||||
<script type="text/javascript">
|
||||
var version = "#VERSION#";
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="region fullscreen below"><div class="container"></div></div>
|
||||
@@ -27,9 +37,10 @@
|
||||
<div class="region bottom right"><div class="container"></div></div>
|
||||
</div>
|
||||
<div class="region fullscreen above"><div class="container"></div></div>
|
||||
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
|
||||
<script type="text/javascript" src="socket.io/socket.io.js"></script>
|
||||
<script type="text/javascript" src="vendor/node_modules/nunjucks/browser/nunjucks.min.js"></script>
|
||||
<script type="text/javascript" src="js/defaults.js"></script>
|
||||
<script type="text/javascript" src="config/config.js"></script>
|
||||
<script type="text/javascript" src="#CONFIG_FILE#"></script>
|
||||
<script type="text/javascript" src="vendor/vendor.js"></script>
|
||||
<script type="text/javascript" src="modules/default/defaultmodules.js"></script>
|
||||
<script type="text/javascript" src="js/logger.js"></script>
|
||||
|
3
installers/mm.sh
Executable file
3
installers/mm.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
# This file is still here to keep PM2 working on older installations.
|
||||
cd ~/MagicMirror
|
||||
DISPLAY=:0 npm start
|
@@ -1,148 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This is an installer script for MagicMirror2. It works well enough
|
||||
# that it can detect if you have Node installed, run a binary script
|
||||
# and then download and run MagicMirror2.
|
||||
|
||||
echo -e "\e[0m"
|
||||
echo '$$\ $$\ $$\ $$\ $$\ $$\ $$$$$$\'
|
||||
echo '$$$\ $$$ | \__| $$$\ $$$ |\__| $$ __$$\'
|
||||
echo '$$$$\ $$$$ | $$$$$$\ $$$$$$\ $$\ $$$$$$$\ $$$$\ $$$$ |$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ \__/ $$ |'
|
||||
echo '$$\$$\$$ $$ | \____$$\ $$ __$$\ $$ |$$ _____|$$\$$\$$ $$ |$$ |$$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ $$$$$$ |'
|
||||
echo '$$ \$$$ $$ | $$$$$$$ |$$ / $$ |$$ |$$ / $$ \$$$ $$ |$$ |$$ | \__|$$ | \__|$$ / $$ |$$ | \__|$$ ____/'
|
||||
echo '$$ |\$ /$$ |$$ __$$ |$$ | $$ |$$ |$$ | $$ |\$ /$$ |$$ |$$ | $$ | $$ | $$ |$$ | $$ |'
|
||||
echo '$$ | \_/ $$ |\$$$$$$$ |\$$$$$$$ |$$ |\$$$$$$$\ $$ | \_/ $$ |$$ |$$ | $$ | \$$$$$$ |$$ | $$$$$$$$\'
|
||||
echo '\__| \__| \_______| \____$$ |\__| \_______|\__| \__|\__|\__| \__| \______/ \__| \________|'
|
||||
echo ' $$\ $$ |'
|
||||
echo ' \$$$$$$ |'
|
||||
echo ' \______/'
|
||||
echo -e "\e[0m"
|
||||
|
||||
# Define the tested version of node.
|
||||
NODE_TESTED="v5.1.0"
|
||||
|
||||
#Determine which Pi is running.
|
||||
ARM=$(uname -m)
|
||||
|
||||
#Check the Raspberry Pi version.
|
||||
if [ "$ARM" != "armv7l" ]; then
|
||||
echo -e "\e[91mSorry, your Raspberry Pi is not supported."
|
||||
echo -e "\e[91mPlease run MagicMirror on a Raspberry Pi 2 or 3."
|
||||
exit;
|
||||
fi
|
||||
|
||||
#define helper methods.
|
||||
function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; }
|
||||
function command_exists () { type "$1" &> /dev/null ;}
|
||||
|
||||
# Installing helper tools
|
||||
echo -e "\e[96mInstalling helper tools ...\e[90m"
|
||||
sudo apt-get install curl wget git build-essential unzip || exit
|
||||
|
||||
# Check if we need to install or upgrade node.
|
||||
echo -e "\e[96mCheck current Node installation ...\e[0m"
|
||||
NODE_INSTALL=false
|
||||
if command_exists node; then
|
||||
echo -e "\e[0mNode currently installed. Checking version number.";
|
||||
NODE_CURRENT=$(node -v)
|
||||
echo -e "\e[0mMinimum Node version: \e[1m$NODE_TESTED\e[0m"
|
||||
echo -e "\e[0mInstalled Node version: \e[1m$NODE_CURRENT\e[0m"
|
||||
if version_gt $NODE_TESTED $NODE_CURRENT; then
|
||||
echo -e "\e[96mNode should be upgraded.\e[0m"
|
||||
NODE_INSTALL=true
|
||||
|
||||
#Check if a node process is currenlty running.
|
||||
#If so abort installation.
|
||||
if pgrep "node" > /dev/null; then
|
||||
echo -e "\e[91mA Node process is currently running. Can't upgrade."
|
||||
echo "Please quit all Node processes and restart the installer."
|
||||
exit;
|
||||
fi
|
||||
|
||||
else
|
||||
echo -e "\e[92mNo Node upgrade nessecery.\e[0m"
|
||||
fi
|
||||
|
||||
else
|
||||
echo -e "\e[93mNode is not installed.\e[0m";
|
||||
NODE_INSTALL=true
|
||||
fi
|
||||
|
||||
# Install or upgare node if nessecery.
|
||||
if $NODE_INSTALL; then
|
||||
|
||||
echo -e "\e[96mStart Node download ...\e[0m"
|
||||
|
||||
#Fetch the latest version of Node.js.
|
||||
#TODO: Is there a native way to fetch the latest node version?
|
||||
echo -e "\e[39mRetrieving latest node version."
|
||||
NODE_LATEST=$(curl -l http://api.jordidepoortere.com/nodejs-latest/ 2> /dev/null)
|
||||
|
||||
if [ "$NODE_LATEST" == "" ]; then
|
||||
echo -e "\e[91mCould not retreive latest node version."
|
||||
echo -e "\e[91mPlease try again or open an issue on GitHub."
|
||||
exit
|
||||
fi
|
||||
|
||||
echo -e "Latest node version: \e[1m$NODE_LATEST\e[0m"
|
||||
|
||||
#Construct the download URL.
|
||||
DOWNLOAD_URL="https://nodejs.org/dist/latest/node-$NODE_LATEST-linux-$ARM.tar.gz"
|
||||
|
||||
#Create Download Directory
|
||||
rm -Rf ~/.MagicMirrorNodeInstaller || exit
|
||||
mkdir ~/.MagicMirrorNodeInstaller || exit
|
||||
cd ~/.MagicMirrorNodeInstaller || exit
|
||||
|
||||
#Download Installer
|
||||
echo -e "\e[39mDownloading node ... \e[90m"
|
||||
if wget $DOWNLOAD_URL --no-verbose --show-progress; then
|
||||
echo -e "\e[39mDownload complete."
|
||||
else
|
||||
echo -e "\e[91mCould not download node."
|
||||
exit;
|
||||
fi
|
||||
|
||||
#Unpack and copy.
|
||||
echo -e "\e[96mStart Node installation ...\e[90m"
|
||||
tar xvf node-$NODE_LATEST-linux-$ARM.tar.gz || exit
|
||||
cd node* || exit
|
||||
sudo cp -R * /usr/local || exit
|
||||
|
||||
#Cleanup
|
||||
rm -Rf ~/.MagicMirrorNodeInstaller || exit
|
||||
fi
|
||||
|
||||
#Install magic mirror
|
||||
cd ~
|
||||
if [ -d "$HOME/MagicMirror" ] ; then
|
||||
echo -e "\e[93mIt seems like MagicMirror is allready installed."
|
||||
echo -e "To prevent overwriting, the installer will be aborted."
|
||||
echo -e "Please rename the \e[1m~/MagicMirror\e[0m\e[93m folder and try again.\e[0m"
|
||||
echo ""
|
||||
echo -e "If you want to upgrade your installation run \e[1m\e[97mgit pull\e[0m from the ~/MagicMirror directory."
|
||||
echo ""
|
||||
exit;
|
||||
fi
|
||||
|
||||
echo -e "\e[96mCloning MagicMirror ...\e[90m"
|
||||
if git clone https://github.com/MichMich/MagicMirror.git; then
|
||||
echo -e "\e[92mCloning MagicMirror Done!\e[0m"
|
||||
else
|
||||
echo -e "\e[91mUnable to clone MagicMirror."
|
||||
exit;
|
||||
fi
|
||||
|
||||
cd ~/MagicMirror || exit
|
||||
echo -e "\e[96mInstalling dependencies ...\e[90m"
|
||||
if npm install; then
|
||||
echo -e "\e[92mDependencies installation Done!\e[0m"
|
||||
else
|
||||
echo -e "\e[91mUnable to install dependencies!"
|
||||
exit;
|
||||
fi
|
||||
|
||||
echo " "
|
||||
echo -e "\e[92mWe're ready! Run \e[1m\e[97mDISPLAY=:0 npm start\e[0m\e[92m from the ~/MagicMirror directory to start your MagicMirror."
|
||||
echo " "
|
||||
echo " "
|
226
js/app.js
226
js/app.js
@@ -1,27 +1,48 @@
|
||||
/* Magic Mirror
|
||||
* The Core App (Server)
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var fs = require("fs");
|
||||
var Server = require(__dirname + "/server.js");
|
||||
var defaultModules = require(__dirname + "/../modules/default/defaultmodules.js");
|
||||
var path = require("path");
|
||||
var Log = require(__dirname + "/logger.js");
|
||||
var Server = require(__dirname + "/server.js");
|
||||
var Utils = require(__dirname + "/utils.js");
|
||||
var defaultModules = require(__dirname + "/../modules/default/defaultmodules.js");
|
||||
|
||||
// Alias modules mentioned in package.js under _moduleAliases.
|
||||
require("module-alias/register");
|
||||
|
||||
// Get version number.
|
||||
global.version = JSON.parse(fs.readFileSync("package.json", "utf8")).version;
|
||||
Log.log("Starting MagicMirror: v" + global.version);
|
||||
|
||||
// global absolute root path
|
||||
global.root_path = path.resolve(__dirname + "/../");
|
||||
|
||||
if (process.env.MM_CONFIG_FILE) {
|
||||
global.configuration_file = process.env.MM_CONFIG_FILE;
|
||||
}
|
||||
|
||||
// FIXME: Hotfix Pull Request
|
||||
// https://github.com/MichMich/MagicMirror/pull/673
|
||||
if (process.env.MM_PORT) {
|
||||
global.mmPort = process.env.MM_PORT;
|
||||
}
|
||||
|
||||
// The next part is here to prevent a major exception when there
|
||||
// is no internet connection. This could probable be solved better.
|
||||
process.on("uncaughtException", function (err) {
|
||||
console.log("Whoops! There was an uncaught exception...");
|
||||
console.error(err);
|
||||
console.log("MagicMirror will not quit, but it might be a good idea to check why this happened. Maybe no internet connection?");
|
||||
console.log("If you think this really is an issue, please open an issue on GitHub: https://github.com/MichMich/MagicMirror/issues");
|
||||
Log.error("Whoops! There was an uncaught exception...");
|
||||
Log.error(err);
|
||||
Log.error("MagicMirror will not quit, but it might be a good idea to check why this happened. Maybe no internet connection?");
|
||||
Log.error("If you think this really is an issue, please open an issue on GitHub: https://github.com/MichMich/MagicMirror/issues");
|
||||
});
|
||||
|
||||
/* App - The core app.
|
||||
*/
|
||||
var App = function() {
|
||||
var App = function () {
|
||||
var nodeHelpers = [];
|
||||
|
||||
/* loadConfig(callback)
|
||||
@@ -30,35 +51,63 @@ var App = function() {
|
||||
*
|
||||
* argument callback function - The callback function.
|
||||
*/
|
||||
|
||||
var loadConfig = function(callback) {
|
||||
console.log("Loading config ...");
|
||||
var loadConfig = function (callback) {
|
||||
Log.log("Loading config ...");
|
||||
var defaults = require(__dirname + "/defaults.js");
|
||||
var configFilename = path.resolve(__dirname + "/../config/config.js");
|
||||
|
||||
// For this check proposed to TestSuite
|
||||
// https://forum.magicmirror.builders/topic/1456/test-suite-for-magicmirror/8
|
||||
var configFilename = path.resolve(global.root_path + "/config/config.js");
|
||||
if (typeof global.configuration_file !== "undefined") {
|
||||
configFilename = path.resolve(global.configuration_file);
|
||||
}
|
||||
|
||||
try {
|
||||
fs.accessSync(configFilename, fs.F_OK);
|
||||
var c = require(configFilename);
|
||||
checkDeprecatedOptions(c);
|
||||
var config = Object.assign(defaults, c);
|
||||
callback(config);
|
||||
} catch (e) {
|
||||
console.error("WARNING! Could not find config. Please create one.");
|
||||
if (e.code === "ENOENT") {
|
||||
Log.error(Utils.colors.error("WARNING! Could not find config file. Please create one. Starting with default configuration."));
|
||||
} else if (e instanceof ReferenceError || e instanceof SyntaxError) {
|
||||
Log.error(Utils.colors.error("WARNING! Could not validate config file. Starting with default configuration. Please correct syntax errors at or above this line: " + e.stack));
|
||||
} else {
|
||||
Log.error(Utils.colors.error("WARNING! Could not load config file. Starting with default configuration. Error found: " + e));
|
||||
}
|
||||
callback(defaults);
|
||||
}
|
||||
};
|
||||
|
||||
var checkDeprecatedOptions = function (userConfig) {
|
||||
var deprecated = require(global.root_path + "/js/deprecated.js");
|
||||
var deprecatedOptions = deprecated.configs;
|
||||
|
||||
var usedDeprecated = [];
|
||||
|
||||
deprecatedOptions.forEach(function (option) {
|
||||
if (userConfig.hasOwnProperty(option)) {
|
||||
usedDeprecated.push(option);
|
||||
}
|
||||
});
|
||||
if (usedDeprecated.length > 0) {
|
||||
Log.warn(Utils.colors.warn("WARNING! Your config is using deprecated options: " + usedDeprecated.join(", ") + ". Check README and CHANGELOG for more up-to-date ways of getting the same functionality."));
|
||||
}
|
||||
};
|
||||
|
||||
/* loadModule(module)
|
||||
* Loads a specific module.
|
||||
*
|
||||
* argument module string - The name of the module (including subpath).
|
||||
*/
|
||||
var loadModule = function(module) {
|
||||
|
||||
var loadModule = function (module, callback) {
|
||||
var elements = module.split("/");
|
||||
var moduleName = elements[elements.length - 1];
|
||||
var moduleFolder = __dirname + "/../modules/" + module;
|
||||
var moduleFolder = __dirname + "/../modules/" + module;
|
||||
|
||||
if (defaultModules.indexOf(moduleName) !== -1) {
|
||||
moduleFolder = __dirname + "/../modules/default/" + module;
|
||||
moduleFolder = __dirname + "/../modules/default/" + module;
|
||||
}
|
||||
|
||||
var helperPath = moduleFolder + "/node_helper.js";
|
||||
@@ -68,15 +117,30 @@ var App = function() {
|
||||
fs.accessSync(helperPath, fs.R_OK);
|
||||
} catch (e) {
|
||||
loadModule = false;
|
||||
console.log("No helper found for module: " + moduleName + ".");
|
||||
Log.log("No helper found for module: " + moduleName + ".");
|
||||
}
|
||||
|
||||
if (loadModule) {
|
||||
var Module = require(helperPath);
|
||||
var m = new Module();
|
||||
|
||||
if (m.requiresVersion) {
|
||||
Log.log("Check MagicMirror version for node helper '" + moduleName + "' - Minimum version: " + m.requiresVersion + " - Current version: " + global.version);
|
||||
if (cmpVersions(global.version, m.requiresVersion) >= 0) {
|
||||
Log.log("Version is ok!");
|
||||
} else {
|
||||
Log.log("Version is incorrect. Skip module: '" + moduleName + "'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m.setName(moduleName);
|
||||
m.setPath(path.resolve(moduleFolder));
|
||||
nodeHelpers.push(m);
|
||||
|
||||
m.loaded(callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -85,58 +149,128 @@ var App = function() {
|
||||
*
|
||||
* argument module string - The name of the module (including subpath).
|
||||
*/
|
||||
var loadModules = function(modules) {
|
||||
console.log("Loading module helpers ...");
|
||||
var loadModules = function (modules, callback) {
|
||||
Log.log("Loading module helpers ...");
|
||||
|
||||
for (var m in modules) {
|
||||
loadModule(modules[m]);
|
||||
}
|
||||
var loadNextModule = function () {
|
||||
if (modules.length > 0) {
|
||||
var nextModule = modules[0];
|
||||
loadModule(nextModule, function () {
|
||||
modules = modules.slice(1);
|
||||
loadNextModule();
|
||||
});
|
||||
} else {
|
||||
// All modules are loaded
|
||||
Log.log("All module helpers loaded.");
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
console.log("All module helpers loaded.");
|
||||
loadNextModule();
|
||||
};
|
||||
|
||||
/* cmpVersions(a,b)
|
||||
* Compare two semantic version numbers and return the difference.
|
||||
*
|
||||
* argument a string - Version number a.
|
||||
* argument a string - Version number b.
|
||||
*/
|
||||
function cmpVersions(a, b) {
|
||||
var i, diff;
|
||||
var regExStrip0 = /(\.0+)+$/;
|
||||
var segmentsA = a.replace(regExStrip0, "").split(".");
|
||||
var segmentsB = b.replace(regExStrip0, "").split(".");
|
||||
var l = Math.min(segmentsA.length, segmentsB.length);
|
||||
|
||||
for (i = 0; i < l; i++) {
|
||||
diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
|
||||
if (diff) {
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
return segmentsA.length - segmentsB.length;
|
||||
}
|
||||
|
||||
/* start(callback)
|
||||
* This methods starts the core app.
|
||||
* It loads the config, then it loads all modules.
|
||||
* When it"s done it executs the callback with the config as argument.
|
||||
* When it's done it executes the callback with the config as argument.
|
||||
*
|
||||
* argument callback function - The callback function.
|
||||
*/
|
||||
this.start = function(callback) {
|
||||
|
||||
loadConfig(function(c) {
|
||||
this.start = function (callback) {
|
||||
loadConfig(function (c) {
|
||||
config = c;
|
||||
|
||||
var modules = [];
|
||||
|
||||
for (var m in config.modules) {
|
||||
var module = config.modules[m];
|
||||
if (modules.indexOf(module.module) === -1) {
|
||||
if (modules.indexOf(module.module) === -1 && !module.disabled) {
|
||||
modules.push(module.module);
|
||||
}
|
||||
}
|
||||
|
||||
loadModules(modules);
|
||||
loadModules(modules, function () {
|
||||
var server = new Server(config, function (app, io) {
|
||||
Log.log("Server started ...");
|
||||
|
||||
var server = new Server(config, function(app, io) {
|
||||
console.log("Server started ...");
|
||||
for (var h in nodeHelpers) {
|
||||
var nodeHelper = nodeHelpers[h];
|
||||
nodeHelper.setExpressApp(app);
|
||||
nodeHelper.setSocketIO(io);
|
||||
nodeHelper.start();
|
||||
}
|
||||
|
||||
for (var h in nodeHelpers) {
|
||||
var nodeHelper = nodeHelpers[h];
|
||||
nodeHelper.setExpressApp(app);
|
||||
nodeHelper.setSocketIO(io);
|
||||
nodeHelper.start();
|
||||
}
|
||||
|
||||
console.log("Sockets connected & modules started ...");
|
||||
|
||||
if (typeof callback === "function") {
|
||||
callback(config);
|
||||
}
|
||||
Log.log("Sockets connected & modules started ...");
|
||||
|
||||
if (typeof callback === "function") {
|
||||
callback(config);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/* stop()
|
||||
* This methods stops the core app.
|
||||
* This calls each node_helper's STOP() function, if it exists.
|
||||
* Added to fix #1056
|
||||
*/
|
||||
this.stop = function () {
|
||||
for (var h in nodeHelpers) {
|
||||
var nodeHelper = nodeHelpers[h];
|
||||
if (typeof nodeHelper.stop === "function") {
|
||||
nodeHelper.stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Listen for SIGINT signal and call stop() function.
|
||||
*
|
||||
* Added to fix #1056
|
||||
* Note: this is only used if running `server-only`. Otherwise
|
||||
* this.stop() is called by app.on("before-quit"... in `electron.js`
|
||||
*/
|
||||
process.on("SIGINT", () => {
|
||||
Log.log("[SIGINT] Received. Shutting down server...");
|
||||
setTimeout(() => {
|
||||
process.exit(0);
|
||||
}, 3000); // Force quit after 3 seconds
|
||||
this.stop();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
/* We also need to listen to SIGTERM signals so we stop everything when we are asked to stop by the OS.
|
||||
*/
|
||||
process.on("SIGTERM", () => {
|
||||
Log.log("[SIGTERM] Received. Shutting down server...");
|
||||
setTimeout(() => {
|
||||
process.exit(0);
|
||||
}, 3000); // Force quit after 3 seconds
|
||||
this.stop();
|
||||
process.exit(0);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = new App();
|
||||
module.exports = new App();
|
||||
|
71
js/check_config.js
Normal file
71
js/check_config.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/* Magic Mirror
|
||||
*
|
||||
* Check the configuration file for errors
|
||||
*
|
||||
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
|
||||
* MIT Licensed.
|
||||
*/
|
||||
const Linter = require("eslint").Linter;
|
||||
const linter = new Linter();
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
const rootPath = path.resolve(__dirname + "/../");
|
||||
const config = require(rootPath + "/.eslintrc.json");
|
||||
const Log = require(rootPath + "/js/logger.js");
|
||||
const Utils = require(rootPath + "/js/utils.js");
|
||||
|
||||
/* getConfigFile()
|
||||
* Return string with path of configuration file
|
||||
* Check if set by environment variable MM_CONFIG_FILE
|
||||
*/
|
||||
function getConfigFile() {
|
||||
// FIXME: This function should be in core. Do you want refactor me ;) ?, be good!
|
||||
let configFileName = path.resolve(rootPath + "/config/config.js");
|
||||
if (process.env.MM_CONFIG_FILE) {
|
||||
configFileName = path.resolve(process.env.MM_CONFIG_FILE);
|
||||
}
|
||||
return configFileName;
|
||||
}
|
||||
|
||||
function checkConfigFile() {
|
||||
const configFileName = getConfigFile();
|
||||
|
||||
// Check if file is present
|
||||
if (fs.existsSync(configFileName) === false) {
|
||||
Log.error(Utils.colors.error("File not found: "), configFileName);
|
||||
throw new Error("No config file present!");
|
||||
}
|
||||
|
||||
// check permission
|
||||
try {
|
||||
fs.accessSync(configFileName, fs.F_OK);
|
||||
} catch (e) {
|
||||
Log.log(Utils.colors.error(e));
|
||||
throw new Error("No permission to access config file!");
|
||||
}
|
||||
|
||||
// Validate syntax of the configuration file.
|
||||
Log.info(Utils.colors.info("Checking file... "), configFileName);
|
||||
|
||||
// I'm not sure if all ever is utf-8
|
||||
fs.readFile(configFileName, "utf-8", function (err, data) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
const messages = linter.verify(data, config);
|
||||
if (messages.length === 0) {
|
||||
Log.log("Your configuration file doesn't contain syntax errors :)");
|
||||
return true;
|
||||
} else {
|
||||
// In case the there errors show messages and return
|
||||
messages.forEach((error) => {
|
||||
Log.log("Line", error.line, "col", error.column, error.message);
|
||||
});
|
||||
throw new Error("Wrong syntax in config file!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkConfigFile();
|
81
js/class.js
81
js/class.js
@@ -1,18 +1,25 @@
|
||||
/* global Class, xyz */
|
||||
|
||||
/* Simple JavaScript Inheritance
|
||||
* By John Resig http://ejohn.org/
|
||||
* By John Resig https://johnresig.com/
|
||||
*
|
||||
* Inspired by base2 and Prototype
|
||||
*
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
// Inspired by base2 and Prototype
|
||||
(function() {
|
||||
(function () {
|
||||
var initializing = false;
|
||||
var fnTest = /xyz/.test(function() {xyz;}) ? /\b_super\b/ : /.*/;
|
||||
var fnTest = /xyz/.test(function () {
|
||||
xyz;
|
||||
})
|
||||
? /\b_super\b/
|
||||
: /.*/;
|
||||
|
||||
// The base Class implementation (does nothing)
|
||||
this.Class = function() {};
|
||||
this.Class = function () {};
|
||||
|
||||
// Create a new Class that inherits from this class
|
||||
Class.extend = function(prop) {
|
||||
Class.extend = function (prop) {
|
||||
var _super = this.prototype;
|
||||
|
||||
// Instantiate a base class (but only create the instance,
|
||||
@@ -21,28 +28,33 @@
|
||||
var prototype = new this();
|
||||
initializing = false;
|
||||
|
||||
// Make a copy of all prototype properties, to prevent reference issues.
|
||||
for (var p in prototype) {
|
||||
prototype[p] = cloneObject(prototype[p]);
|
||||
}
|
||||
|
||||
// Copy the properties over onto the new prototype
|
||||
for (var name in prop) {
|
||||
// Check if we're overwriting an existing function
|
||||
prototype[name] = typeof prop[name] == "function" &&
|
||||
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
|
||||
(function(name, fn) {
|
||||
return function() {
|
||||
var tmp = this._super;
|
||||
prototype[name] =
|
||||
typeof prop[name] === "function" && typeof _super[name] === "function" && fnTest.test(prop[name])
|
||||
? (function (name, fn) {
|
||||
return function () {
|
||||
var tmp = this._super;
|
||||
|
||||
// Add a new ._super() method that is the same method
|
||||
// but on the super-class
|
||||
this._super = _super[name];
|
||||
// Add a new ._super() method that is the same method
|
||||
// but on the super-class
|
||||
this._super = _super[name];
|
||||
|
||||
// The method only need to be bound temporarily, so we
|
||||
// remove it when we're done executing
|
||||
var ret = fn.apply(this, arguments);
|
||||
this._super = tmp;
|
||||
// The method only need to be bound temporarily, so we
|
||||
// remove it when we're done executing
|
||||
var ret = fn.apply(this, arguments);
|
||||
this._super = tmp;
|
||||
|
||||
return ret;
|
||||
};
|
||||
})(name, prop[name]) :
|
||||
prop[name];
|
||||
return ret;
|
||||
};
|
||||
})(name, prop[name])
|
||||
: prop[name];
|
||||
}
|
||||
|
||||
// The dummy class constructor
|
||||
@@ -66,5 +78,26 @@
|
||||
};
|
||||
})();
|
||||
|
||||
//Define the clone method for later use.
|
||||
//Helper Method
|
||||
function cloneObject(obj) {
|
||||
if (obj === null || typeof obj !== "object") {
|
||||
return obj;
|
||||
}
|
||||
|
||||
var temp = obj.constructor(); // give temp the original obj's constructor
|
||||
for (var key in obj) {
|
||||
temp[key] = cloneObject(obj[key]);
|
||||
|
||||
if (key === "lockStrings") {
|
||||
Log.log(key);
|
||||
}
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {module.exports = Class;}
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = Class;
|
||||
}
|
||||
|
@@ -1,20 +1,35 @@
|
||||
/* exported defaults */
|
||||
/* global mmPort */
|
||||
|
||||
/* Magic Mirror
|
||||
* Config Defauls
|
||||
* Config Defaults
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var address = "localhost";
|
||||
var port = 8080;
|
||||
if (typeof mmPort !== "undefined") {
|
||||
port = mmPort;
|
||||
}
|
||||
var defaults = {
|
||||
port: 8080,
|
||||
address: address,
|
||||
port: port,
|
||||
basePath: "/",
|
||||
kioskmode: false,
|
||||
electronOptions: {},
|
||||
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
|
||||
|
||||
language: "en",
|
||||
timeFormat: 24,
|
||||
units: "metric",
|
||||
|
||||
zoom: 1,
|
||||
customCss: "css/custom.css",
|
||||
|
||||
modules: [
|
||||
{
|
||||
module: "updatenotification",
|
||||
position: "top_center"
|
||||
},
|
||||
{
|
||||
module: "helloworld",
|
||||
position: "upper_third",
|
||||
@@ -53,14 +68,16 @@ var defaults = {
|
||||
config: {
|
||||
text: "www.michaelteeuw.nl"
|
||||
}
|
||||
},
|
||||
}
|
||||
],
|
||||
|
||||
paths: {
|
||||
modules: "modules",
|
||||
vendor: "vendor"
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {module.exports = defaults;}
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = defaults;
|
||||
}
|
||||
|
16
js/deprecated.js
Normal file
16
js/deprecated.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/* Magic Mirror Deprecated Config Options List
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*
|
||||
* Olex S. original idea this deprecated option
|
||||
*/
|
||||
|
||||
var deprecated = {
|
||||
configs: ["kioskmode"]
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = deprecated;
|
||||
}
|
115
js/electron.js
115
js/electron.js
@@ -1,11 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
const Server = require(__dirname + "/server.js");
|
||||
const electron = require("electron");
|
||||
const core = require(__dirname + "/app.js");
|
||||
const core = require("./app.js");
|
||||
const Log = require("./logger.js");
|
||||
|
||||
// Config
|
||||
var config = {};
|
||||
var config = process.env.config ? JSON.parse(process.env.config) : {};
|
||||
// Module to control application life.
|
||||
const app = electron.app;
|
||||
// Module to create native browser window.
|
||||
@@ -16,42 +16,87 @@ const BrowserWindow = electron.BrowserWindow;
|
||||
let mainWindow;
|
||||
|
||||
function createWindow() {
|
||||
app.commandLine.appendSwitch("autoplay-policy", "no-user-gesture-required");
|
||||
var electronOptionsDefaults = {
|
||||
width: 800,
|
||||
height: 600,
|
||||
x: 0,
|
||||
y: 0,
|
||||
darkTheme: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
zoomFactor: config.zoom
|
||||
},
|
||||
backgroundColor: "#000000"
|
||||
};
|
||||
|
||||
// DEPRECATED: "kioskmode" backwards compatibility, to be removed
|
||||
// settings these options directly instead provides cleaner interface
|
||||
if (config.kioskmode) {
|
||||
electronOptionsDefaults.kiosk = true;
|
||||
} else {
|
||||
electronOptionsDefaults.fullscreen = true;
|
||||
electronOptionsDefaults.autoHideMenuBar = true;
|
||||
}
|
||||
|
||||
var electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions);
|
||||
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({width: 800, height: 600, fullscreen: true, autoHideMenuBar: true, darkTheme: true, webPreferences: {nodeIntegration: false}});
|
||||
mainWindow = new BrowserWindow(electronOptions);
|
||||
|
||||
// and load the index.html of the app.
|
||||
//mainWindow.loadURL('file://' + __dirname + '../../index.html');
|
||||
mainWindow.loadURL("http://localhost:" + config.port);
|
||||
// If config.address is not defined or is an empty string (listening on all interfaces), connect to localhost
|
||||
|
||||
// Open the DevTools.
|
||||
//mainWindow.webContents.openDevTools();
|
||||
var prefix;
|
||||
if (config["tls"] !== null && config["tls"]) {
|
||||
prefix = "https://";
|
||||
} else {
|
||||
prefix = "http://";
|
||||
}
|
||||
|
||||
// Emitted when the window is closed.
|
||||
mainWindow.on("closed", function() {
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
var address = (config.address === void 0) | (config.address === "") ? (config.address = "localhost") : config.address;
|
||||
mainWindow.loadURL(`${prefix}${address}:${config.port}`);
|
||||
|
||||
// Open the DevTools if run with "npm start dev"
|
||||
if (process.argv.includes("dev")) {
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
// Set responders for window events.
|
||||
mainWindow.on("closed", function () {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
if (config.kioskmode) {
|
||||
mainWindow.on("blur", function () {
|
||||
mainWindow.focus();
|
||||
});
|
||||
|
||||
mainWindow.on("leave-full-screen", function () {
|
||||
mainWindow.setFullScreen(true);
|
||||
});
|
||||
|
||||
mainWindow.on("resize", function () {
|
||||
setTimeout(function () {
|
||||
mainWindow.reload();
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
app.on("ready", function() {
|
||||
console.log("Launching application.");
|
||||
app.on("ready", function () {
|
||||
Log.log("Launching application.");
|
||||
createWindow();
|
||||
});
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on("window-all-closed", function() {
|
||||
// On OS X it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
app.on("window-all-closed", function () {
|
||||
createWindow();
|
||||
});
|
||||
|
||||
app.on("activate", function() {
|
||||
app.on("activate", function () {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (mainWindow === null) {
|
||||
@@ -59,8 +104,26 @@ app.on("activate", function() {
|
||||
}
|
||||
});
|
||||
|
||||
// Start the core application.
|
||||
// This starts all node helpers and starts the webserver.
|
||||
core.start(function(c) {
|
||||
config = c;
|
||||
/* This method will be called when SIGINT is received and will call
|
||||
* each node_helper's stop function if it exists. Added to fix #1056
|
||||
*
|
||||
* Note: this is only used if running Electron. Otherwise
|
||||
* core.stop() is called by process.on("SIGINT"... in `app.js`
|
||||
*/
|
||||
app.on("before-quit", (event) => {
|
||||
Log.log("Shutting down server...");
|
||||
event.preventDefault();
|
||||
setTimeout(() => {
|
||||
process.exit(0);
|
||||
}, 3000); // Force-quit after 3 seconds.
|
||||
core.stop();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Start the core application if server is run on localhost
|
||||
// This starts all node helpers and starts the webserver.
|
||||
if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) > -1) {
|
||||
core.start(function (c) {
|
||||
config = c;
|
||||
});
|
||||
}
|
||||
|
143
js/loader.js
143
js/loader.js
@@ -1,14 +1,13 @@
|
||||
/* global config, vendor, MM, Log, Module */
|
||||
/* global defaultModules, vendor */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module and File loaders.
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var Loader = (function() {
|
||||
|
||||
/* Create helper valiables */
|
||||
var Loader = (function () {
|
||||
/* Create helper variables */
|
||||
|
||||
var loadedModuleFiles = [];
|
||||
var loadedFiles = [];
|
||||
@@ -19,27 +18,25 @@ var Loader = (function() {
|
||||
/* loadModules()
|
||||
* Loops thru all modules and requests load for every module.
|
||||
*/
|
||||
var loadModules = function() {
|
||||
|
||||
var loadModules = function () {
|
||||
var moduleData = getModuleData();
|
||||
|
||||
var loadNextModule = function() {
|
||||
var loadNextModule = function () {
|
||||
if (moduleData.length > 0) {
|
||||
var nextModule = moduleData[0];
|
||||
loadModule(nextModule, function() {
|
||||
loadModule(nextModule, function () {
|
||||
moduleData = moduleData.slice(1);
|
||||
loadNextModule();
|
||||
});
|
||||
} else {
|
||||
// All modules loaded. Load custom.css
|
||||
// This is done after all the moduels so we can
|
||||
// overwrite all the defined styls.
|
||||
// This is done after all the modules so we can
|
||||
// overwrite all the defined styles.
|
||||
|
||||
loadFile("css/custom.css", function() {
|
||||
loadFile(config.customCss, function () {
|
||||
// custom.css loaded. Start all modules.
|
||||
startModules();
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -49,13 +46,13 @@ var Loader = (function() {
|
||||
/* startModules()
|
||||
* Loops thru all modules and requests start for every module.
|
||||
*/
|
||||
var startModules = function() {
|
||||
var startModules = function () {
|
||||
for (var m in moduleObjects) {
|
||||
var module = moduleObjects[m];
|
||||
module.start();
|
||||
}
|
||||
|
||||
// Notifiy core of loded modules.
|
||||
// Notify core of loaded modules.
|
||||
MM.modulesStarted(moduleObjects);
|
||||
};
|
||||
|
||||
@@ -64,7 +61,7 @@ var Loader = (function() {
|
||||
*
|
||||
* return array - module data as configured in config
|
||||
*/
|
||||
var getAllModules = function() {
|
||||
var getAllModules = function () {
|
||||
return config.modules;
|
||||
};
|
||||
|
||||
@@ -73,7 +70,7 @@ var Loader = (function() {
|
||||
*
|
||||
* return array - Module information.
|
||||
*/
|
||||
var getModuleData = function() {
|
||||
var getModuleData = function () {
|
||||
var modules = getAllModules();
|
||||
var moduleFiles = [];
|
||||
|
||||
@@ -83,24 +80,27 @@ var Loader = (function() {
|
||||
|
||||
var elements = module.split("/");
|
||||
var moduleName = elements[elements.length - 1];
|
||||
var moduleFolder = config.paths.modules + "/" + module;
|
||||
var moduleFolder = config.paths.modules + "/" + module;
|
||||
|
||||
if (defaultModules.indexOf(moduleName) !== -1) {
|
||||
moduleFolder = config.paths.modules + "/default/" + module;
|
||||
moduleFolder = config.paths.modules + "/default/" + module;
|
||||
}
|
||||
|
||||
if (moduleData.disabled === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
moduleFiles.push({
|
||||
index: m,
|
||||
identifier: "module_" + m + "_" + module,
|
||||
name: moduleName,
|
||||
path: moduleFolder + "/" ,
|
||||
path: moduleFolder + "/",
|
||||
file: moduleName + ".js",
|
||||
position: moduleData.position,
|
||||
header: moduleData.header,
|
||||
config: moduleData.config,
|
||||
classes: (typeof moduleData.classes !== "undefined") ? moduleData.classes + " " + module : module
|
||||
classes: typeof moduleData.classes !== "undefined" ? moduleData.classes + " " + module : module
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return moduleFiles;
|
||||
@@ -112,25 +112,28 @@ var Loader = (function() {
|
||||
* argument callback function - Function called when done.
|
||||
* argument module object - Information about the module we want to load.
|
||||
*/
|
||||
var loadModule = function(module, callback) {
|
||||
var loadModule = function (module, callback) {
|
||||
var url = module.path + "/" + module.file;
|
||||
|
||||
var afterLoad = function() {
|
||||
var afterLoad = function () {
|
||||
var moduleObject = Module.create(module.name);
|
||||
bootstrapModule(module, moduleObject, function() {
|
||||
if (moduleObject) {
|
||||
bootstrapModule(module, moduleObject, function () {
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (loadedModuleFiles.indexOf(url) !== -1) {
|
||||
afterLoad();
|
||||
} else {
|
||||
loadFile(url, function() {
|
||||
loadFile(url, function () {
|
||||
loadedModuleFiles.push(url);
|
||||
afterLoad();
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* bootstrapModule(module, mObj)
|
||||
@@ -140,23 +143,22 @@ var Loader = (function() {
|
||||
* argument mObj object - Modules instance.
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
var bootstrapModule = function(module, mObj, callback) {
|
||||
var bootstrapModule = function (module, mObj, callback) {
|
||||
Log.info("Bootstrapping module: " + module.name);
|
||||
|
||||
mObj.setData(module);
|
||||
|
||||
mObj.loadScripts(function() {
|
||||
mObj.loadScripts(function () {
|
||||
Log.log("Scripts loaded for: " + module.name);
|
||||
mObj.loadStyles(function() {
|
||||
mObj.loadStyles(function () {
|
||||
Log.log("Styles loaded for: " + module.name);
|
||||
mObj.loadTranslations(function() {
|
||||
mObj.loadTranslations(function () {
|
||||
Log.log("Translations loaded for: " + module.name);
|
||||
moduleObjects.push(mObj);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/* loadFile(fileName)
|
||||
@@ -165,43 +167,58 @@ var Loader = (function() {
|
||||
* argument fileName string - Path of the file we want to load.
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
var loadFile = function(fileName, callback) {
|
||||
|
||||
var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
|
||||
var loadFile = function (fileName, callback) {
|
||||
var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
|
||||
|
||||
switch (extension.toLowerCase()) {
|
||||
case "js":
|
||||
Log.log("Load script: " + fileName);
|
||||
var script = document.createElement("script");
|
||||
script.type = "text/javascript";
|
||||
script.src = fileName;
|
||||
script.onload = function() {
|
||||
if (typeof callback === "function") {callback();}
|
||||
};
|
||||
document.getElementsByTagName("body")[0].appendChild(script);
|
||||
break;
|
||||
case "css":
|
||||
Log.log("Load stylesheet: " + fileName);
|
||||
var stylesheet = document.createElement("link");
|
||||
stylesheet.rel = "stylesheet";
|
||||
stylesheet.type = "text/css";
|
||||
stylesheet.href = fileName;
|
||||
stylesheet.onload = function() {
|
||||
if (typeof callback === "function") {callback();}
|
||||
};
|
||||
document.getElementsByTagName("head")[0].appendChild(stylesheet);
|
||||
break;
|
||||
}
|
||||
case "js":
|
||||
Log.log("Load script: " + fileName);
|
||||
var script = document.createElement("script");
|
||||
script.type = "text/javascript";
|
||||
script.src = fileName;
|
||||
script.onload = function () {
|
||||
if (typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
script.onerror = function () {
|
||||
Log.error("Error on loading script:", fileName);
|
||||
if (typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementsByTagName("body")[0].appendChild(script);
|
||||
break;
|
||||
case "css":
|
||||
Log.log("Load stylesheet: " + fileName);
|
||||
var stylesheet = document.createElement("link");
|
||||
stylesheet.rel = "stylesheet";
|
||||
stylesheet.type = "text/css";
|
||||
stylesheet.href = fileName;
|
||||
stylesheet.onload = function () {
|
||||
if (typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
stylesheet.onerror = function () {
|
||||
Log.error("Error on loading stylesheet:", fileName);
|
||||
if (typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
document.getElementsByTagName("head")[0].appendChild(stylesheet);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/* Public Methods */
|
||||
return {
|
||||
|
||||
/* loadModules()
|
||||
* Load all modules as defined in the config.
|
||||
*/
|
||||
loadModules: function() {
|
||||
loadModules: function () {
|
||||
loadModules();
|
||||
},
|
||||
|
||||
@@ -213,8 +230,7 @@ var Loader = (function() {
|
||||
* argument module Module Object - the module that calls the loadFile function.
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
loadFile: function(fileName, module, callback) {
|
||||
|
||||
loadFile: function (fileName, module, callback) {
|
||||
if (loadedFiles.indexOf(fileName.toLowerCase()) !== -1) {
|
||||
Log.log("File already loaded: " + fileName);
|
||||
callback();
|
||||
@@ -243,5 +259,4 @@ var Loader = (function() {
|
||||
loadFile(module.file(fileName), callback);
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
79
js/logger.js
79
js/logger.js
@@ -1,47 +1,44 @@
|
||||
/* global console */
|
||||
/* exported Log */
|
||||
|
||||
/* Magic Mirror
|
||||
* Logger
|
||||
* Log
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* This logger is very simple, but needs to be extended.
|
||||
* This system can eventually be used to push the log messages to an external target.
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
(function (root, factory) {
|
||||
if (typeof exports === "object") {
|
||||
// add timestamps in front of log messages
|
||||
require("console-stamp")(console, "yyyy-mm-dd HH:MM:ss.l");
|
||||
|
||||
// This logger is very simple, but needs to be extended.
|
||||
// This system can eventually be used to push the log messages to an external target.
|
||||
|
||||
var Log = (function() {
|
||||
return {
|
||||
info: function(message) {
|
||||
console.info(message);
|
||||
},
|
||||
log: function(message) {
|
||||
console.log(message);
|
||||
},
|
||||
error: function(message) {
|
||||
console.error(message);
|
||||
},
|
||||
warn: function(message) {
|
||||
console.warn(message);
|
||||
},
|
||||
group: function(message) {
|
||||
console.group(message);
|
||||
},
|
||||
groupCollapsed: function(message) {
|
||||
console.groupCollapsed(message);
|
||||
},
|
||||
groupEnd: function() {
|
||||
console.groupEnd();
|
||||
},
|
||||
time: function(message) {
|
||||
console.time(message);
|
||||
},
|
||||
timeEnd: function(message) {
|
||||
console.timeEnd(message);
|
||||
},
|
||||
timeStamp: function(message) {
|
||||
console.timeStamp(message);
|
||||
}
|
||||
// Node, CommonJS-like
|
||||
module.exports = factory(root.config);
|
||||
} else {
|
||||
// Browser globals (root is window)
|
||||
root.Log = factory(root.config);
|
||||
}
|
||||
})(this, function (config) {
|
||||
let logLevel = {
|
||||
info: Function.prototype.bind.call(console.info, console),
|
||||
log: Function.prototype.bind.call(console.log, console),
|
||||
error: Function.prototype.bind.call(console.error, console),
|
||||
warn: Function.prototype.bind.call(console.warn, console),
|
||||
group: Function.prototype.bind.call(console.group, console),
|
||||
groupCollapsed: Function.prototype.bind.call(console.groupCollapsed, console),
|
||||
groupEnd: Function.prototype.bind.call(console.groupEnd, console),
|
||||
time: Function.prototype.bind.call(console.time, console),
|
||||
timeEnd: Function.prototype.bind.call(console.timeEnd, console),
|
||||
timeStamp: Function.prototype.bind.call(console.timeStamp, console)
|
||||
};
|
||||
})();
|
||||
|
||||
if (config && config.logLevel) {
|
||||
Object.keys(logLevel).forEach(function (key, index) {
|
||||
if (!config.logLevel.includes(key.toLocaleUpperCase())) {
|
||||
logLevel[key] = function () {};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return logLevel;
|
||||
});
|
||||
|
435
js/main.js
435
js/main.js
@@ -1,14 +1,12 @@
|
||||
/* global Log, Loader, Module, config, defaults */
|
||||
/* global Loader, defaults, Translator */
|
||||
|
||||
/* Magic Mirror
|
||||
* Main System
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var MM = (function() {
|
||||
|
||||
var MM = (function () {
|
||||
var modules = [];
|
||||
|
||||
/* Private Methods */
|
||||
@@ -17,40 +15,54 @@ var MM = (function() {
|
||||
* Create dom objects for all modules that
|
||||
* are configured for a specific position.
|
||||
*/
|
||||
var createDomObjects = function() {
|
||||
for (var m in modules) {
|
||||
var module = modules[m];
|
||||
var createDomObjects = function () {
|
||||
var domCreationPromises = [];
|
||||
|
||||
if (typeof module.data.position === "string") {
|
||||
|
||||
var wrapper = selectWrapper(module.data.position);
|
||||
|
||||
var dom = document.createElement("div");
|
||||
dom.id = module.identifier;
|
||||
dom.className = module.name;
|
||||
|
||||
if (typeof module.data.classes === "string") {
|
||||
dom.className = "module " + dom.className + " " + module.data.classes;
|
||||
}
|
||||
|
||||
dom.opacity = 0;
|
||||
wrapper.appendChild(dom);
|
||||
|
||||
if (typeof module.data.header !== "undefined" && module.data.header !== "") {
|
||||
var moduleHeader = document.createElement("header");
|
||||
moduleHeader.innerHTML = module.data.header;
|
||||
dom.appendChild(moduleHeader);
|
||||
}
|
||||
|
||||
var moduleContent = document.createElement("div");
|
||||
moduleContent.className = "module-content";
|
||||
dom.appendChild(moduleContent);
|
||||
|
||||
updateDom(module, 0);
|
||||
modules.forEach(function (module) {
|
||||
if (typeof module.data.position !== "string") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sendNotification("DOM_OBJECTS_CREATED");
|
||||
var wrapper = selectWrapper(module.data.position);
|
||||
|
||||
var dom = document.createElement("div");
|
||||
dom.id = module.identifier;
|
||||
dom.className = module.name;
|
||||
|
||||
if (typeof module.data.classes === "string") {
|
||||
dom.className = "module " + dom.className + " " + module.data.classes;
|
||||
}
|
||||
|
||||
dom.opacity = 0;
|
||||
wrapper.appendChild(dom);
|
||||
|
||||
var moduleHeader = document.createElement("header");
|
||||
moduleHeader.innerHTML = module.getHeader();
|
||||
moduleHeader.className = "module-header";
|
||||
dom.appendChild(moduleHeader);
|
||||
|
||||
if (typeof module.getHeader() === "undefined" || module.getHeader() !== "") {
|
||||
moduleHeader.style = "display: none;";
|
||||
}
|
||||
|
||||
var moduleContent = document.createElement("div");
|
||||
moduleContent.className = "module-content";
|
||||
dom.appendChild(moduleContent);
|
||||
|
||||
var domCreationPromise = updateDom(module, 0);
|
||||
domCreationPromises.push(domCreationPromise);
|
||||
domCreationPromise
|
||||
.then(function () {
|
||||
sendNotification("MODULE_DOM_CREATED", null, null, module);
|
||||
})
|
||||
.catch(Log.error);
|
||||
});
|
||||
|
||||
updateWrapperStates();
|
||||
|
||||
Promise.all(domCreationPromises).then(function () {
|
||||
sendNotification("DOM_OBJECTS_CREATED");
|
||||
});
|
||||
};
|
||||
|
||||
/* selectWrapper(position)
|
||||
@@ -58,11 +70,11 @@ var MM = (function() {
|
||||
*
|
||||
* argument position string - The name of the position.
|
||||
*/
|
||||
var selectWrapper = function(position) {
|
||||
var classes = position.replace("_"," ");
|
||||
var selectWrapper = function (position) {
|
||||
var classes = position.replace("_", " ");
|
||||
var parentWrapper = document.getElementsByClassName(classes);
|
||||
if (parentWrapper.length > 0) {
|
||||
var wrapper = parentWrapper[0].getElementsByClassName("container");
|
||||
var wrapper = parentWrapper[0].getElementsByClassName("container");
|
||||
if (wrapper.length > 0) {
|
||||
return wrapper[0];
|
||||
}
|
||||
@@ -72,14 +84,15 @@ var MM = (function() {
|
||||
/* sendNotification(notification, payload, sender)
|
||||
* Send a notification to all modules.
|
||||
*
|
||||
* argument notification string - The identifier of the noitication.
|
||||
* argument notification string - The identifier of the notification.
|
||||
* argument payload mixed - The payload of the notification.
|
||||
* argument sender Module - The module that sent the notification.
|
||||
* argument sendTo Module - The module to send the notification to. (optional)
|
||||
*/
|
||||
var sendNotification = function(notification, payload, sender) {
|
||||
var sendNotification = function (notification, payload, sender, sendTo) {
|
||||
for (var m in modules) {
|
||||
var module = modules[m];
|
||||
if (module !== sender) {
|
||||
if (module !== sender && (!sendTo || module === sendTo)) {
|
||||
module.notificationReceived(notification, payload, sender);
|
||||
}
|
||||
}
|
||||
@@ -90,62 +103,120 @@ var MM = (function() {
|
||||
*
|
||||
* argument module Module - The module that needs an update.
|
||||
* argument speed Number - The number of microseconds for the animation. (optional)
|
||||
*
|
||||
* return Promise - Resolved when the dom is fully updated.
|
||||
*/
|
||||
var updateDom = function(module, speed) {
|
||||
var newContent = module.getDom();
|
||||
var updateDom = function (module, speed) {
|
||||
return new Promise(function (resolve) {
|
||||
var newContentPromise = module.getDom();
|
||||
var newHeader = module.getHeader();
|
||||
|
||||
if (!module.hidden) {
|
||||
if (!(newContentPromise instanceof Promise)) {
|
||||
// convert to a promise if not already one to avoid if/else's everywhere
|
||||
newContentPromise = Promise.resolve(newContentPromise);
|
||||
}
|
||||
|
||||
if (!moduleNeedsUpdate(module, newContent)) {
|
||||
newContentPromise
|
||||
.then(function (newContent) {
|
||||
var updatePromise = updateDomWithContent(module, speed, newHeader, newContent);
|
||||
|
||||
updatePromise.then(resolve).catch(Log.error);
|
||||
})
|
||||
.catch(Log.error);
|
||||
});
|
||||
};
|
||||
|
||||
/* updateDomWithContent(module, speed, newHeader, newContent)
|
||||
* Update the dom with the specified content
|
||||
*
|
||||
* argument module Module - The module that needs an update.
|
||||
* argument speed Number - The number of microseconds for the animation. (optional)
|
||||
* argument newHeader String - The new header that is generated.
|
||||
* argument newContent Domobject - The new content that is generated.
|
||||
*
|
||||
* return Promise - Resolved when the module dom has been updated.
|
||||
*/
|
||||
var updateDomWithContent = function (module, speed, newHeader, newContent) {
|
||||
return new Promise(function (resolve) {
|
||||
if (module.hidden || !speed) {
|
||||
updateModuleContent(module, newHeader, newContent);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!moduleNeedsUpdate(module, newHeader, newContent)) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!speed) {
|
||||
updateModuleContent(module, newContent);
|
||||
updateModuleContent(module, newHeader, newContent);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
hideModule(module, speed / 2, function() {
|
||||
updateModuleContent(module, newContent);
|
||||
hideModule(module, speed / 2, function () {
|
||||
updateModuleContent(module, newHeader, newContent);
|
||||
if (!module.hidden) {
|
||||
showModule(module, speed / 2);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
updateModuleContent(module, newContent);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* moduleNeedsUpdate(module, newContent)
|
||||
* Check if the content has changed.
|
||||
*
|
||||
* argument module Module - The module to check.
|
||||
* argument newHeader String - The new header that is generated.
|
||||
* argument newContent Domobject - The new content that is generated.
|
||||
*
|
||||
* return bool - Does the module need an update?
|
||||
*/
|
||||
var moduleNeedsUpdate = function(module, newContent) {
|
||||
var moduleNeedsUpdate = function (module, newHeader, newContent) {
|
||||
var moduleWrapper = document.getElementById(module.identifier);
|
||||
var contentWrapper = moduleWrapper.getElementsByClassName("module-content")[0];
|
||||
if (moduleWrapper === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var tempWrapper = document.createElement("div");
|
||||
tempWrapper.appendChild(newContent);
|
||||
var contentWrapper = moduleWrapper.getElementsByClassName("module-content");
|
||||
var headerWrapper = moduleWrapper.getElementsByClassName("module-header");
|
||||
|
||||
return tempWrapper.innerHTML !== contentWrapper.innerHTML;
|
||||
var headerNeedsUpdate = false;
|
||||
var contentNeedsUpdate = false;
|
||||
|
||||
if (headerWrapper.length > 0) {
|
||||
headerNeedsUpdate = newHeader !== headerWrapper[0].innerHTML;
|
||||
}
|
||||
|
||||
var tempContentWrapper = document.createElement("div");
|
||||
tempContentWrapper.appendChild(newContent);
|
||||
contentNeedsUpdate = tempContentWrapper.innerHTML !== contentWrapper[0].innerHTML;
|
||||
|
||||
return headerNeedsUpdate || contentNeedsUpdate;
|
||||
};
|
||||
|
||||
/* moduleNeedsUpdate(module, newContent)
|
||||
* Update the content of a module on screen.
|
||||
*
|
||||
* argument module Module - The module to check.
|
||||
* argument newHeader String - The new header that is generated.
|
||||
* argument newContent Domobject - The new content that is generated.
|
||||
*/
|
||||
var updateModuleContent = function(module, content) {
|
||||
var updateModuleContent = function (module, newHeader, newContent) {
|
||||
var moduleWrapper = document.getElementById(module.identifier);
|
||||
var contentWrapper = moduleWrapper.getElementsByClassName("module-content")[0];
|
||||
if (moduleWrapper === null) {
|
||||
return;
|
||||
}
|
||||
var headerWrapper = moduleWrapper.getElementsByClassName("module-header");
|
||||
var contentWrapper = moduleWrapper.getElementsByClassName("module-content");
|
||||
|
||||
contentWrapper.innerHTML = null;
|
||||
contentWrapper.appendChild(content);
|
||||
contentWrapper[0].innerHTML = "";
|
||||
contentWrapper[0].appendChild(newContent);
|
||||
|
||||
headerWrapper[0].innerHTML = newHeader;
|
||||
headerWrapper[0].style = headerWrapper.length > 0 && newHeader ? undefined : "display: none;";
|
||||
};
|
||||
|
||||
/* hideModule(module, speed, callback)
|
||||
@@ -155,22 +226,41 @@ var MM = (function() {
|
||||
* argument speed Number - The speed of the hide animation.
|
||||
* argument callback function - Called when the animation is done.
|
||||
*/
|
||||
var hideModule = function(module, speed, callback) {
|
||||
var hideModule = function (module, speed, callback, options) {
|
||||
options = options || {};
|
||||
|
||||
// set lockString if set in options.
|
||||
if (options.lockString) {
|
||||
// Log.log("Has lockstring: " + options.lockString);
|
||||
if (module.lockStrings.indexOf(options.lockString) === -1) {
|
||||
module.lockStrings.push(options.lockString);
|
||||
}
|
||||
}
|
||||
|
||||
var moduleWrapper = document.getElementById(module.identifier);
|
||||
if (moduleWrapper !== null) {
|
||||
moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
|
||||
moduleWrapper.style.opacity = 0;
|
||||
|
||||
clearTimeout(module.showHideTimer);
|
||||
module.showHideTimer = setTimeout(function() {
|
||||
module.showHideTimer = setTimeout(function () {
|
||||
// To not take up any space, we just make the position absolute.
|
||||
// since it"s fade out anyway, we can see it lay above or
|
||||
// since it's fade out anyway, we can see it lay above or
|
||||
// below other modules. This works way better than adjusting
|
||||
// the .display property.
|
||||
moduleWrapper.style.position = "absolute";
|
||||
moduleWrapper.style.position = "fixed";
|
||||
|
||||
if (typeof callback === "function") { callback(); }
|
||||
updateWrapperStates();
|
||||
|
||||
if (typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
}, speed);
|
||||
} else {
|
||||
// invoke callback even if no content, issue 1308
|
||||
if (typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -181,33 +271,101 @@ var MM = (function() {
|
||||
* argument speed Number - The speed of the show animation.
|
||||
* argument callback function - Called when the animation is done.
|
||||
*/
|
||||
var showModule = function(module, speed, callback) {
|
||||
var showModule = function (module, speed, callback, options) {
|
||||
options = options || {};
|
||||
|
||||
// remove lockString if set in options.
|
||||
if (options.lockString) {
|
||||
var index = module.lockStrings.indexOf(options.lockString);
|
||||
if (index !== -1) {
|
||||
module.lockStrings.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there are no more lockstrings set, or the force option is set.
|
||||
// Otherwise cancel show action.
|
||||
if (module.lockStrings.length !== 0 && options.force !== true) {
|
||||
Log.log("Will not show " + module.name + ". LockStrings active: " + module.lockStrings.join(","));
|
||||
return;
|
||||
}
|
||||
|
||||
module.hidden = false;
|
||||
|
||||
// If forced show, clean current lockstrings.
|
||||
if (module.lockStrings.length !== 0 && options.force === true) {
|
||||
Log.log("Force show of module: " + module.name);
|
||||
module.lockStrings = [];
|
||||
}
|
||||
|
||||
var moduleWrapper = document.getElementById(module.identifier);
|
||||
if (moduleWrapper !== null) {
|
||||
moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
|
||||
// Restore the postition. See hideModule() for more info.
|
||||
// Restore the position. See hideModule() for more info.
|
||||
moduleWrapper.style.position = "static";
|
||||
|
||||
updateWrapperStates();
|
||||
|
||||
// Waiting for DOM-changes done in updateWrapperStates before we can start the animation.
|
||||
var dummy = moduleWrapper.parentElement.parentElement.offsetHeight;
|
||||
moduleWrapper.style.opacity = 1;
|
||||
|
||||
clearTimeout(module.showHideTimer);
|
||||
module.showHideTimer = setTimeout(function() {
|
||||
if (typeof callback === "function") { callback(); }
|
||||
module.showHideTimer = setTimeout(function () {
|
||||
if (typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
}, speed);
|
||||
|
||||
} else {
|
||||
// invoke callback
|
||||
if (typeof callback === "function") {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* updateWrapperStates()
|
||||
* Checks for all positions if it has visible content.
|
||||
* If not, if will hide the position to prevent unwanted margins.
|
||||
* This method should be called by the show and hide methods.
|
||||
*
|
||||
* Example:
|
||||
* If the top_bar only contains the update notification. And no update is available,
|
||||
* the update notification is hidden. The top bar still occupies space making for
|
||||
* an ugly top margin. By using this function, the top bar will be hidden if the
|
||||
* update notification is not visible.
|
||||
*/
|
||||
var updateWrapperStates = function () {
|
||||
var positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
|
||||
|
||||
positions.forEach(function (position) {
|
||||
var wrapper = selectWrapper(position);
|
||||
var moduleWrappers = wrapper.getElementsByClassName("module");
|
||||
|
||||
var showWrapper = false;
|
||||
Array.prototype.forEach.call(moduleWrappers, function (moduleWrapper) {
|
||||
if (moduleWrapper.style.position === "" || moduleWrapper.style.position === "static") {
|
||||
showWrapper = true;
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.style.display = showWrapper ? "block" : "none";
|
||||
});
|
||||
};
|
||||
|
||||
/* loadConfig()
|
||||
* Loads the core config and combines it with de system defaults.
|
||||
*/
|
||||
var loadConfig = function() {
|
||||
var loadConfig = function () {
|
||||
// FIXME: Think about how to pass config around without breaking tests
|
||||
/* eslint-disable */
|
||||
if (typeof config === "undefined") {
|
||||
config = defaults;
|
||||
Log.error("Config file is missing! Please create a config file.");
|
||||
return;
|
||||
}
|
||||
|
||||
config = Object.assign(defaults, config);
|
||||
config = Object.assign({}, defaults, config);
|
||||
/* eslint-enable */
|
||||
};
|
||||
|
||||
/* setSelectionMethodsForModules()
|
||||
@@ -215,69 +373,55 @@ var MM = (function() {
|
||||
*
|
||||
* argument modules array - Array of modules.
|
||||
*/
|
||||
var setSelectionMethodsForModules = function(modules) {
|
||||
|
||||
var setSelectionMethodsForModules = function (modules) {
|
||||
/* withClass(className)
|
||||
* filters a collection of modules based on classname(s).
|
||||
* calls modulesByClass to filter modules with the specified classes.
|
||||
*
|
||||
* argument className string/array - one or multiple classnames. (array or space devided)
|
||||
* argument className string/array - one or multiple classnames. (array or space divided)
|
||||
*
|
||||
* return array - Filtered collection of modules.
|
||||
*/
|
||||
var withClass = function(className) {
|
||||
var newModules = [];
|
||||
|
||||
var searchClasses = className;
|
||||
if (typeof className === "string") {
|
||||
searchClasses = className.split(" ");
|
||||
}
|
||||
|
||||
for (var m in modules) {
|
||||
var module = modules[m];
|
||||
var classes = module.data.classes.toLowerCase().split(" ");
|
||||
|
||||
for (var c in searchClasses) {
|
||||
var searchClass = searchClasses[c];
|
||||
if (classes.indexOf(searchClass.toLowerCase()) !== -1) {
|
||||
newModules.push(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSelectionMethodsForModules(newModules);
|
||||
return newModules;
|
||||
var withClass = function (className) {
|
||||
return modulesByClass(className, true);
|
||||
};
|
||||
|
||||
/* exceptWithClass(className)
|
||||
* filters a collection of modules based on classname(s). (NOT)
|
||||
* calls modulesByClass to filter modules without the specified classes.
|
||||
*
|
||||
* argument className string/array - one or multiple classnames. (array or space devided)
|
||||
* argument className string/array - one or multiple classnames. (array or space divided)
|
||||
*
|
||||
* return array - Filtered collection of modules.
|
||||
*/
|
||||
var exceptWithClass = function(className) {
|
||||
var newModules = [];
|
||||
var exceptWithClass = function (className) {
|
||||
return modulesByClass(className, false);
|
||||
};
|
||||
|
||||
/* modulesByClass(className, include)
|
||||
* filters a collection of modules based on classname(s).
|
||||
*
|
||||
* argument className string/array - one or multiple classnames. (array or space divided)
|
||||
* argument include boolean - if the filter should include or exclude the modules with the specific classes.
|
||||
*
|
||||
* return array - Filtered collection of modules.
|
||||
*/
|
||||
var modulesByClass = function (className, include) {
|
||||
var searchClasses = className;
|
||||
if (typeof className === "string") {
|
||||
searchClasses = className.split(" ");
|
||||
}
|
||||
|
||||
for (var m in modules) {
|
||||
var module = modules[m];
|
||||
var newModules = modules.filter(function (module) {
|
||||
var classes = module.data.classes.toLowerCase().split(" ");
|
||||
var foundClass = false;
|
||||
|
||||
for (var c in searchClasses) {
|
||||
var searchClass = searchClasses[c];
|
||||
if (classes.indexOf(searchClass.toLowerCase()) !== -1) {
|
||||
foundClass = true;
|
||||
break;
|
||||
return include;
|
||||
}
|
||||
}
|
||||
if (!foundClass) {
|
||||
newModules.push(module);
|
||||
}
|
||||
}
|
||||
|
||||
return !include;
|
||||
});
|
||||
|
||||
setSelectionMethodsForModules(newModules);
|
||||
return newModules;
|
||||
@@ -290,15 +434,10 @@ var MM = (function() {
|
||||
*
|
||||
* return array - Filtered collection of modules.
|
||||
*/
|
||||
var exceptModule = function(module) {
|
||||
var newModules = [];
|
||||
|
||||
for (var m in modules) {
|
||||
var mod = modules[m];
|
||||
if (mod.identifier !== module.identifier) {
|
||||
newModules.push(mod);
|
||||
}
|
||||
}
|
||||
var exceptModule = function (module) {
|
||||
var newModules = modules.filter(function (mod) {
|
||||
return mod.identifier !== module.identifier;
|
||||
});
|
||||
|
||||
setSelectionMethodsForModules(newModules);
|
||||
return newModules;
|
||||
@@ -309,17 +448,24 @@ var MM = (function() {
|
||||
*
|
||||
* argument callback function - The function to execute with the module as an argument.
|
||||
*/
|
||||
var enumerate = function(callback) {
|
||||
for (var m in modules) {
|
||||
var module = modules[m];
|
||||
var enumerate = function (callback) {
|
||||
modules.map(function (module) {
|
||||
callback(module);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (typeof modules.withClass === "undefined") { Object.defineProperty(modules, "withClass", {value: withClass, enumerable: false}); }
|
||||
if (typeof modules.exceptWithClass === "undefined") { Object.defineProperty(modules, "exceptWithClass", {value: exceptWithClass, enumerable: false}); }
|
||||
if (typeof modules.exceptModule === "undefined") { Object.defineProperty(modules, "exceptModule", {value: exceptModule, enumerable: false}); }
|
||||
if (typeof modules.enumerate === "undefined") { Object.defineProperty(modules, "enumerate", {value: enumerate, enumerable: false}); }
|
||||
if (typeof modules.withClass === "undefined") {
|
||||
Object.defineProperty(modules, "withClass", { value: withClass, enumerable: false });
|
||||
}
|
||||
if (typeof modules.exceptWithClass === "undefined") {
|
||||
Object.defineProperty(modules, "exceptWithClass", { value: exceptWithClass, enumerable: false });
|
||||
}
|
||||
if (typeof modules.exceptModule === "undefined") {
|
||||
Object.defineProperty(modules, "exceptModule", { value: exceptModule, enumerable: false });
|
||||
}
|
||||
if (typeof modules.enumerate === "undefined") {
|
||||
Object.defineProperty(modules, "enumerate", { value: enumerate, enumerable: false });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -328,7 +474,7 @@ var MM = (function() {
|
||||
/* init()
|
||||
* Main init method.
|
||||
*/
|
||||
init: function() {
|
||||
init: function () {
|
||||
Log.info("Initializing MagicMirror.");
|
||||
loadConfig();
|
||||
Translator.loadCoreTranslations(config.language);
|
||||
@@ -340,7 +486,7 @@ var MM = (function() {
|
||||
*
|
||||
* argument moduleObjects array<Module> - All module instances.
|
||||
*/
|
||||
modulesStarted: function(moduleObjects) {
|
||||
modulesStarted: function (moduleObjects) {
|
||||
modules = [];
|
||||
for (var m in moduleObjects) {
|
||||
var module = moduleObjects[m];
|
||||
@@ -356,11 +502,11 @@ var MM = (function() {
|
||||
/* sendNotification(notification, payload, sender)
|
||||
* Send a notification to all modules.
|
||||
*
|
||||
* argument notification string - The identifier of the noitication.
|
||||
* argument notification string - The identifier of the notification.
|
||||
* argument payload mixed - The payload of the notification.
|
||||
* argument sender Module - The module that sent the notification.
|
||||
*/
|
||||
sendNotification: function(notification, payload, sender) {
|
||||
sendNotification: function (notification, payload, sender) {
|
||||
if (arguments.length < 3) {
|
||||
Log.error("sendNotification: Missing arguments.");
|
||||
return;
|
||||
@@ -386,7 +532,7 @@ var MM = (function() {
|
||||
* argument module Module - The module that needs an update.
|
||||
* argument speed Number - The number of microseconds for the animation. (optional)
|
||||
*/
|
||||
updateDom: function(module, speed) {
|
||||
updateDom: function (module, speed) {
|
||||
if (!(module instanceof Module)) {
|
||||
Log.error("updateDom: Sender should be a module.");
|
||||
return;
|
||||
@@ -401,7 +547,7 @@ var MM = (function() {
|
||||
*
|
||||
* return array - A collection of all modules currently active.
|
||||
*/
|
||||
getModules: function() {
|
||||
getModules: function () {
|
||||
setSelectionMethodsForModules(modules);
|
||||
return modules;
|
||||
},
|
||||
@@ -412,10 +558,11 @@ var MM = (function() {
|
||||
* argument module Module - The module hide.
|
||||
* argument speed Number - The speed of the hide animation.
|
||||
* argument callback function - Called when the animation is done.
|
||||
* argument options object - Optional settings for the hide method.
|
||||
*/
|
||||
hideModule: function(module, speed, callback) {
|
||||
hideModule: function (module, speed, callback, options) {
|
||||
module.hidden = true;
|
||||
hideModule(module, speed, callback);
|
||||
hideModule(module, speed, callback, options);
|
||||
},
|
||||
|
||||
/* showModule(module, speed, callback)
|
||||
@@ -424,19 +571,19 @@ var MM = (function() {
|
||||
* argument module Module - The module show.
|
||||
* argument speed Number - The speed of the show animation.
|
||||
* argument callback function - Called when the animation is done.
|
||||
* argument options object - Optional settings for the hide method.
|
||||
*/
|
||||
showModule: function(module, speed, callback) {
|
||||
module.hidden = false;
|
||||
showModule(module, speed, callback);
|
||||
showModule: function (module, speed, callback, options) {
|
||||
// do not change module.hidden yet, only if we really show it later
|
||||
showModule(module, speed, callback, options);
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
// Add polyfill for Object.assign.
|
||||
if (typeof Object.assign != "function") {
|
||||
(function() {
|
||||
Object.assign = function(target) {
|
||||
if (typeof Object.assign !== "function") {
|
||||
(function () {
|
||||
Object.assign = function (target) {
|
||||
"use strict";
|
||||
if (target === undefined || target === null) {
|
||||
throw new TypeError("Cannot convert undefined or null to object");
|
||||
|
354
js/module.js
354
js/module.js
@@ -1,36 +1,45 @@
|
||||
/* global Log, Class, Loader, Class , MM */
|
||||
/* exported Module */
|
||||
/* global Class, cloneObject, Loader, MMSocket, nunjucks, Translator */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module Blueprint.
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var Module = Class.extend({
|
||||
|
||||
/*********************************************************
|
||||
* All methods (and properties) below can be subclassed. *
|
||||
*********************************************************/
|
||||
|
||||
// Set the minimum MagicMirror module version for this module.
|
||||
requiresVersion: "2.0.0",
|
||||
|
||||
// Module config defaults.
|
||||
defaults: {},
|
||||
|
||||
// Timer reference used for showHide animation callbacks.
|
||||
showHideTimer: null,
|
||||
|
||||
// Array to store lockStrings. These strings are used to lock
|
||||
// visibility when hiding and showing module.
|
||||
lockStrings: [],
|
||||
|
||||
// Storage of the nunjuck Environment,
|
||||
// This should not be referenced directly.
|
||||
// Use the nunjucksEnvironment() to get it.
|
||||
_nunjucksEnvironment: null,
|
||||
|
||||
/* init()
|
||||
* Is called when the module is instantiated.
|
||||
*/
|
||||
init: function() {
|
||||
init: function () {
|
||||
//Log.log(this.defaults);
|
||||
},
|
||||
|
||||
/* start()
|
||||
* Is called when the module is started.
|
||||
*/
|
||||
start: function() {
|
||||
start: function () {
|
||||
Log.info("Starting module: " + this.name);
|
||||
},
|
||||
|
||||
@@ -39,7 +48,7 @@ var Module = Class.extend({
|
||||
*
|
||||
* return Array<String> - An array with filenames.
|
||||
*/
|
||||
getScripts: function() {
|
||||
getScripts: function () {
|
||||
return [];
|
||||
},
|
||||
|
||||
@@ -48,7 +57,7 @@ var Module = Class.extend({
|
||||
*
|
||||
* return Array<String> - An array with filenames.
|
||||
*/
|
||||
getStyles: function() {
|
||||
getStyles: function () {
|
||||
return [];
|
||||
},
|
||||
|
||||
@@ -57,70 +66,139 @@ var Module = Class.extend({
|
||||
*
|
||||
* return Map<String, String> - A map with langKeys and filenames.
|
||||
*/
|
||||
getTranslations: function() {
|
||||
getTranslations: function () {
|
||||
return false;
|
||||
},
|
||||
|
||||
/* getDom()
|
||||
* This method generates the dom which needs to be displayed. This method is called by the Magic Mirror core.
|
||||
* This method needs to be subclassed if the module wants to display info on the mirror.
|
||||
* This method can to be subclassed if the module wants to display info on the mirror.
|
||||
* Alternatively, the getTemplate method could be subclassed.
|
||||
*
|
||||
* return domobject - The dom to display.
|
||||
* return DomObject | Promise - The dom or a promise with the dom to display.
|
||||
*/
|
||||
getDom: function() {
|
||||
var nameWrapper = document.createElement("div");
|
||||
var name = document.createTextNode(this.name);
|
||||
nameWrapper.appendChild(name);
|
||||
getDom: function () {
|
||||
var self = this;
|
||||
return new Promise(function (resolve) {
|
||||
var div = document.createElement("div");
|
||||
var template = self.getTemplate();
|
||||
var templateData = self.getTemplateData();
|
||||
|
||||
var identifierWrapper = document.createElement("div");
|
||||
var identifier = document.createTextNode(this.identifier);
|
||||
identifierWrapper.appendChild(identifier);
|
||||
identifierWrapper.className = "small dimmed";
|
||||
// Check to see if we need to render a template string or a file.
|
||||
if (/^.*((\.html)|(\.njk))$/.test(template)) {
|
||||
// the template is a filename
|
||||
self.nunjucksEnvironment().render(template, templateData, function (err, res) {
|
||||
if (err) {
|
||||
Log.error(err);
|
||||
}
|
||||
|
||||
var div = document.createElement("div");
|
||||
div.appendChild(nameWrapper);
|
||||
div.appendChild(identifierWrapper);
|
||||
div.innerHTML = res;
|
||||
|
||||
return div;
|
||||
resolve(div);
|
||||
});
|
||||
} else {
|
||||
// the template is a template string.
|
||||
div.innerHTML = self.nunjucksEnvironment().renderString(template, templateData);
|
||||
|
||||
resolve(div);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/* getHeader()
|
||||
* This method generates the header string which needs to be displayed if a user has a header configured for this module.
|
||||
* This method is called by the Magic Mirror core, but only if the user has configured a default header for the module.
|
||||
* This method needs to be subclassed if the module wants to display modified headers on the mirror.
|
||||
*
|
||||
* return string - The header to display above the header.
|
||||
*/
|
||||
getHeader: function () {
|
||||
return this.data.header;
|
||||
},
|
||||
|
||||
/* getTemplate()
|
||||
* This method returns the template for the module which is used by the default getDom implementation.
|
||||
* This method needs to be subclassed if the module wants to use a template.
|
||||
* It can either return a template sting, or a template filename.
|
||||
* If the string ends with '.html' it's considered a file from within the module's folder.
|
||||
*
|
||||
* return string - The template string of filename.
|
||||
*/
|
||||
getTemplate: function () {
|
||||
return '<div class="normal">' + this.name + '</div><div class="small dimmed">' + this.identifier + "</div>";
|
||||
},
|
||||
|
||||
/* getTemplateData()
|
||||
* This method returns the data to be used in the template.
|
||||
* This method needs to be subclassed if the module wants to use a custom data.
|
||||
*
|
||||
* return Object
|
||||
*/
|
||||
getTemplateData: function () {
|
||||
return {};
|
||||
},
|
||||
|
||||
/* notificationReceived(notification, payload, sender)
|
||||
* This method is called when a notification arrives.
|
||||
* This method is called by the Magic Mirror core.
|
||||
*
|
||||
* argument notification string - The identifier of the noitication.
|
||||
* argument notification string - The identifier of the notification.
|
||||
* argument payload mixed - The payload of the notification.
|
||||
* argument sender Module - The module that sent the notification.
|
||||
*/
|
||||
notificationReceived: function(notification, payload, sender) {
|
||||
notificationReceived: function (notification, payload, sender) {
|
||||
if (sender) {
|
||||
Log.log(this.name + " received a module notification: " + notification + " from sender: " + sender.name);
|
||||
// Log.log(this.name + " received a module notification: " + notification + " from sender: " + sender.name);
|
||||
} else {
|
||||
Log.log(this.name + " received a system notification: " + notification);
|
||||
// Log.log(this.name + " received a system notification: " + notification);
|
||||
}
|
||||
},
|
||||
|
||||
/** nunjucksEnvironment()
|
||||
* Returns the nunjucks environment for the current module.
|
||||
* The environment is checked in the _nunjucksEnvironment instance variable.
|
||||
|
||||
* @returns Nunjucks Environment
|
||||
*/
|
||||
nunjucksEnvironment: function () {
|
||||
if (this._nunjucksEnvironment !== null) {
|
||||
return this._nunjucksEnvironment;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
this._nunjucksEnvironment = new nunjucks.Environment(new nunjucks.WebLoader(this.file(""), { async: true }), {
|
||||
trimBlocks: true,
|
||||
lstripBlocks: true
|
||||
});
|
||||
this._nunjucksEnvironment.addFilter("translate", function (str) {
|
||||
return self.translate(str);
|
||||
});
|
||||
|
||||
return this._nunjucksEnvironment;
|
||||
},
|
||||
|
||||
/* socketNotificationReceived(notification, payload)
|
||||
* This method is called when a socket notification arrives.
|
||||
*
|
||||
* argument notification string - The identifier of the noitication.
|
||||
* argument notification string - The identifier of the notification.
|
||||
* argument payload mixed - The payload of the notification.
|
||||
*/
|
||||
socketNotificationReceived: function(notification, payload) {
|
||||
socketNotificationReceived: function (notification, payload) {
|
||||
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
|
||||
},
|
||||
|
||||
/* suspend()
|
||||
* This method is called when a module is hidden.
|
||||
*/
|
||||
suspend: function() {
|
||||
Log.log(this.name + " is suspend.");
|
||||
suspend: function () {
|
||||
Log.log(this.name + " is suspended.");
|
||||
},
|
||||
|
||||
/* resume()
|
||||
* This method is called when a module is shown.
|
||||
*/
|
||||
resume: function() {
|
||||
resume: function () {
|
||||
Log.log(this.name + " is resumed.");
|
||||
},
|
||||
|
||||
@@ -131,9 +209,9 @@ var Module = Class.extend({
|
||||
/* setData(data)
|
||||
* Set the module data.
|
||||
*
|
||||
* argument data obejct - Module data.
|
||||
* argument data object - Module data.
|
||||
*/
|
||||
setData: function(data) {
|
||||
setData: function (data) {
|
||||
this.data = data;
|
||||
this.name = data.name;
|
||||
this.identifier = data.identifier;
|
||||
@@ -145,23 +223,23 @@ var Module = Class.extend({
|
||||
/* setConfig(config)
|
||||
* Set the module config and combine it with the module defaults.
|
||||
*
|
||||
* argument config obejct - Module config.
|
||||
* argument config object - Module config.
|
||||
*/
|
||||
setConfig: function(config) {
|
||||
this.config = Object.assign(this.defaults, config);
|
||||
setConfig: function (config) {
|
||||
this.config = Object.assign({}, this.defaults, config);
|
||||
},
|
||||
|
||||
/* socket()
|
||||
* Returns a socket object. If it doesn"t exsist, it"s created.
|
||||
* Returns a socket object. If it doesn't exist, it"s created.
|
||||
* It also registers the notification callback.
|
||||
*/
|
||||
socket: function() {
|
||||
socket: function () {
|
||||
if (typeof this._socket === "undefined") {
|
||||
this._socket = this._socket = new MMSocket(this.name);
|
||||
this._socket = new MMSocket(this.name);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this._socket.setNotificationCallback(function(notification, payload) {
|
||||
this._socket.setNotificationCallback(function (notification, payload) {
|
||||
self.socketNotificationReceived(notification, payload);
|
||||
});
|
||||
|
||||
@@ -175,8 +253,8 @@ var Module = Class.extend({
|
||||
*
|
||||
* return string - File path.
|
||||
*/
|
||||
file: function(file) {
|
||||
return this.data.path + "/" + file;
|
||||
file: function (file) {
|
||||
return (this.data.path + "/" + file).replace("//", "/");
|
||||
},
|
||||
|
||||
/* loadStyles()
|
||||
@@ -184,23 +262,8 @@ var Module = Class.extend({
|
||||
*
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
loadStyles: function(callback) {
|
||||
var self = this;
|
||||
var styles = this.getStyles();
|
||||
|
||||
var loadNextStyle = function() {
|
||||
if (styles.length > 0) {
|
||||
var nextStyle = styles[0];
|
||||
Loader.loadFile(nextStyle, self, function() {
|
||||
styles = styles.slice(1);
|
||||
loadNextStyle();
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
loadNextStyle();
|
||||
loadStyles: function (callback) {
|
||||
this.loadDependencies("getStyles", callback);
|
||||
},
|
||||
|
||||
/* loadScripts()
|
||||
@@ -208,23 +271,33 @@ var Module = Class.extend({
|
||||
*
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
loadScripts: function(callback) {
|
||||
var self = this;
|
||||
var scripts = this.getScripts();
|
||||
loadScripts: function (callback) {
|
||||
this.loadDependencies("getScripts", callback);
|
||||
},
|
||||
|
||||
var loadNextScript = function() {
|
||||
if (scripts.length > 0) {
|
||||
var nextScript = scripts[0];
|
||||
Loader.loadFile(nextScript, self, function() {
|
||||
scripts = scripts.slice(1);
|
||||
loadNextScript();
|
||||
/* loadDependencies(funcName, callback)
|
||||
* Helper method to load all dependencies.
|
||||
*
|
||||
* argument funcName string - Function name to call to get scripts or styles.
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
loadDependencies: function (funcName, callback) {
|
||||
var self = this;
|
||||
var dependencies = this[funcName]();
|
||||
|
||||
var loadNextDependency = function () {
|
||||
if (dependencies.length > 0) {
|
||||
var nextDependency = dependencies[0];
|
||||
Loader.loadFile(nextDependency, self, function () {
|
||||
dependencies = dependencies.slice(1);
|
||||
loadNextDependency();
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
loadNextScript();
|
||||
loadNextDependency();
|
||||
},
|
||||
|
||||
/* loadScripts()
|
||||
@@ -232,14 +305,16 @@ var Module = Class.extend({
|
||||
*
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
loadTranslations: function(callback) {
|
||||
loadTranslations: function (callback) {
|
||||
var self = this;
|
||||
var translations = this.getTranslations();
|
||||
var lang = config.language.toLowerCase();
|
||||
|
||||
// The variable `first` will contain the first
|
||||
// defined translation after the following line.
|
||||
for (var first in translations) {break;}
|
||||
for (var first in translations) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (translations) {
|
||||
var translationFile = translations[lang] || undefined;
|
||||
@@ -247,8 +322,8 @@ var Module = Class.extend({
|
||||
|
||||
// If a translation file is set, load it and then also load the fallback translation file.
|
||||
// Otherwise only load the fallback translation file.
|
||||
if (translationFile !== undefined) {
|
||||
Translator.load(self, translationFile, false, function() {
|
||||
if (translationFile !== undefined && translationFile !== translationsFallbackFile) {
|
||||
Translator.load(self, translationFile, false, function () {
|
||||
Translator.load(self, translationsFallbackFile, true, callback);
|
||||
});
|
||||
} else {
|
||||
@@ -259,14 +334,18 @@ var Module = Class.extend({
|
||||
}
|
||||
},
|
||||
|
||||
/* translate(key, defaultValue)
|
||||
* Request the translation for a given key.
|
||||
*
|
||||
* argument key string - The key of the string to translage
|
||||
* argument defaultValue string - The default value if no translation was found. (Optional)
|
||||
*/
|
||||
translate: function(key, defaultValue) {
|
||||
return Translator.translate(this, key) || defaultValue || "";
|
||||
/* translate(key, defaultValueOrVariables, defaultValue)
|
||||
* Request the translation for a given key with optional variables and default value.
|
||||
*
|
||||
* argument key string - The key of the string to translate
|
||||
* argument defaultValueOrVariables string/object - The default value or variables for translating. (Optional)
|
||||
* argument defaultValue string - The default value with variables. (Optional)
|
||||
*/
|
||||
translate: function (key, defaultValueOrVariables, defaultValue) {
|
||||
if (typeof defaultValueOrVariables === "object") {
|
||||
return Translator.translate(this, key, defaultValueOrVariables) || defaultValue || "";
|
||||
}
|
||||
return Translator.translate(this, key) || defaultValueOrVariables || "";
|
||||
},
|
||||
|
||||
/* updateDom(speed)
|
||||
@@ -274,27 +353,27 @@ var Module = Class.extend({
|
||||
*
|
||||
* argument speed Number - The speed of the animation. (Optional)
|
||||
*/
|
||||
updateDom: function(speed) {
|
||||
updateDom: function (speed) {
|
||||
MM.updateDom(this, speed);
|
||||
},
|
||||
|
||||
/* sendNotification(notification, payload)
|
||||
* Send a notification to all modules.
|
||||
*
|
||||
* argument notification string - The identifier of the noitication.
|
||||
* argument notification string - The identifier of the notification.
|
||||
* argument payload mixed - The payload of the notification.
|
||||
*/
|
||||
sendNotification: function(notification, payload) {
|
||||
sendNotification: function (notification, payload) {
|
||||
MM.sendNotification(notification, payload, this);
|
||||
},
|
||||
|
||||
/* sendSocketNotification(notification, payload)
|
||||
* Send a socket notification to the node helper.
|
||||
*
|
||||
* argument notification string - The identifier of the noitication.
|
||||
* argument notification string - The identifier of the notification.
|
||||
* argument payload mixed - The payload of the notification.
|
||||
*/
|
||||
sendSocketNotification: function(notification, payload) {
|
||||
sendSocketNotification: function (notification, payload) {
|
||||
this.socket().sendNotification(notification, payload);
|
||||
},
|
||||
|
||||
@@ -303,15 +382,27 @@ var Module = Class.extend({
|
||||
*
|
||||
* argument speed Number - The speed of the hide animation.
|
||||
* argument callback function - Called when the animation is done.
|
||||
* argument options object - Optional settings for the hide method.
|
||||
*/
|
||||
hide: function(speed, callback) {
|
||||
callback = callback || function() {};
|
||||
hide: function (speed, callback, options) {
|
||||
if (typeof callback === "object") {
|
||||
options = callback;
|
||||
callback = function () {};
|
||||
}
|
||||
|
||||
callback = callback || function () {};
|
||||
options = options || {};
|
||||
|
||||
var self = this;
|
||||
MM.hideModule(self, speed, function() {
|
||||
self.suspend();
|
||||
callback();
|
||||
});
|
||||
MM.hideModule(
|
||||
self,
|
||||
speed,
|
||||
function () {
|
||||
self.suspend();
|
||||
callback();
|
||||
},
|
||||
options
|
||||
);
|
||||
},
|
||||
|
||||
/* showModule(module, speed, callback)
|
||||
@@ -319,29 +410,36 @@ var Module = Class.extend({
|
||||
*
|
||||
* argument speed Number - The speed of the show animation.
|
||||
* argument callback function - Called when the animation is done.
|
||||
* argument options object - Optional settings for the hide method.
|
||||
*/
|
||||
show: function(speed, callback) {
|
||||
this.resume();
|
||||
MM.showModule(this, speed, callback);
|
||||
show: function (speed, callback, options) {
|
||||
if (typeof callback === "object") {
|
||||
options = callback;
|
||||
callback = function () {};
|
||||
}
|
||||
|
||||
callback = callback || function () {};
|
||||
options = options || {};
|
||||
|
||||
var self = this;
|
||||
MM.showModule(
|
||||
this,
|
||||
speed,
|
||||
function () {
|
||||
self.resume();
|
||||
callback;
|
||||
},
|
||||
options
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Module.definitions = {};
|
||||
|
||||
Module.create = function(name) {
|
||||
|
||||
//Define the clone method for later use.
|
||||
function cloneObject(obj) {
|
||||
if (obj === null || typeof obj !== "object") {
|
||||
return obj;
|
||||
}
|
||||
|
||||
var temp = obj.constructor(); // give temp the original obj"s constructor
|
||||
for (var key in obj) {
|
||||
temp[key] = cloneObject(obj[key]);
|
||||
}
|
||||
|
||||
return temp;
|
||||
Module.create = function (name) {
|
||||
// Make sure module definition is available.
|
||||
if (!Module.definitions[name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var moduleDefinition = Module.definitions[name];
|
||||
@@ -351,10 +449,40 @@ Module.create = function(name) {
|
||||
var ModuleClass = Module.extend(clonedDefinition);
|
||||
|
||||
return new ModuleClass();
|
||||
|
||||
};
|
||||
|
||||
Module.register = function(name, moduleDefinition) {
|
||||
/* cmpVersions(a,b)
|
||||
* Compare two semantic version numbers and return the difference.
|
||||
*
|
||||
* argument a string - Version number a.
|
||||
* argument a string - Version number b.
|
||||
*/
|
||||
function cmpVersions(a, b) {
|
||||
var i, diff;
|
||||
var regExStrip0 = /(\.0+)+$/;
|
||||
var segmentsA = a.replace(regExStrip0, "").split(".");
|
||||
var segmentsB = b.replace(regExStrip0, "").split(".");
|
||||
var l = Math.min(segmentsA.length, segmentsB.length);
|
||||
|
||||
for (i = 0; i < l; i++) {
|
||||
diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
|
||||
if (diff) {
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
return segmentsA.length - segmentsB.length;
|
||||
}
|
||||
|
||||
Module.register = function (name, moduleDefinition) {
|
||||
if (moduleDefinition.requiresVersion) {
|
||||
Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + window.version);
|
||||
if (cmpVersions(window.version, moduleDefinition.requiresVersion) >= 0) {
|
||||
Log.log("Version is ok!");
|
||||
} else {
|
||||
Log.log("Version is incorrect. Skip module: '" + name + "'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
Log.log("Module registered: " + name);
|
||||
Module.definitions[name] = moduleDefinition;
|
||||
};
|
||||
|
126
js/node_helper.js
Normal file
126
js/node_helper.js
Normal file
@@ -0,0 +1,126 @@
|
||||
/* Magic Mirror
|
||||
* Node Helper Superclass
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
const Class = require("./class.js");
|
||||
const Log = require("./logger.js");
|
||||
const express = require("express");
|
||||
|
||||
var NodeHelper = Class.extend({
|
||||
init: function () {
|
||||
Log.log("Initializing new module helper ...");
|
||||
},
|
||||
|
||||
loaded: function (callback) {
|
||||
Log.log("Module helper loaded: " + this.name);
|
||||
callback();
|
||||
},
|
||||
|
||||
start: function () {
|
||||
Log.log("Starting module helper: " + this.name);
|
||||
},
|
||||
|
||||
/* stop()
|
||||
* Called when the MagicMirror server receives a `SIGINT`
|
||||
* Close any open connections, stop any sub-processes and
|
||||
* gracefully exit the module.
|
||||
*
|
||||
*/
|
||||
stop: function () {
|
||||
Log.log("Stopping module helper: " + this.name);
|
||||
},
|
||||
|
||||
/* socketNotificationReceived(notification, payload)
|
||||
* This method is called when a socket notification arrives.
|
||||
*
|
||||
* argument notification string - The identifier of the notification.
|
||||
* argument payload mixed - The payload of the notification.
|
||||
*/
|
||||
socketNotificationReceived: function (notification, payload) {
|
||||
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
|
||||
},
|
||||
|
||||
/* setName(name)
|
||||
* Set the module name.
|
||||
*
|
||||
* argument name string - Module name.
|
||||
*/
|
||||
setName: function (name) {
|
||||
this.name = name;
|
||||
},
|
||||
|
||||
/* setPath(path)
|
||||
* Set the module path.
|
||||
*
|
||||
* argument path string - Module path.
|
||||
*/
|
||||
setPath: function (path) {
|
||||
this.path = path;
|
||||
},
|
||||
|
||||
/* sendSocketNotification(notification, payload)
|
||||
* Send a socket notification to the node helper.
|
||||
*
|
||||
* argument notification string - The identifier of the notification.
|
||||
* argument payload mixed - The payload of the notification.
|
||||
*/
|
||||
sendSocketNotification: function (notification, payload) {
|
||||
this.io.of(this.name).emit(notification, payload);
|
||||
},
|
||||
|
||||
/* setExpressApp(app)
|
||||
* Sets the express app object for this module.
|
||||
* This allows you to host files from the created webserver.
|
||||
*
|
||||
* argument app Express app - The Express app object.
|
||||
*/
|
||||
setExpressApp: function (app) {
|
||||
this.expressApp = app;
|
||||
|
||||
var publicPath = this.path + "/public";
|
||||
app.use("/" + this.name, express.static(publicPath));
|
||||
},
|
||||
|
||||
/* setSocketIO(io)
|
||||
* Sets the socket io object for this module.
|
||||
* Binds message receiver.
|
||||
*
|
||||
* argument io Socket.io - The Socket io object.
|
||||
*/
|
||||
setSocketIO: function (io) {
|
||||
var self = this;
|
||||
self.io = io;
|
||||
|
||||
Log.log("Connecting socket for: " + this.name);
|
||||
var namespace = this.name;
|
||||
io.of(namespace).on("connection", function (socket) {
|
||||
// add a catch all event.
|
||||
var onevent = socket.onevent;
|
||||
socket.onevent = function (packet) {
|
||||
var args = packet.data || [];
|
||||
onevent.call(this, packet); // original call
|
||||
packet.data = ["*"].concat(args);
|
||||
onevent.call(this, packet); // additional call to catch-all
|
||||
};
|
||||
|
||||
// register catch all.
|
||||
socket.on("*", function (notification, payload) {
|
||||
if (notification !== "*") {
|
||||
//Log.log('received message in namespace: ' + namespace);
|
||||
self.socketNotificationReceived(notification, payload);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
NodeHelper.create = function (moduleDefinition) {
|
||||
return NodeHelper.extend(moduleDefinition);
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = NodeHelper;
|
||||
}
|
83
js/server.js
83
js/server.js
@@ -1,30 +1,83 @@
|
||||
/* Magic Mirror
|
||||
* Server
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var express = require("express");
|
||||
var app = require("express")();
|
||||
var server = require("http").Server(app);
|
||||
var io = require("socket.io")(server);
|
||||
var path = require("path");
|
||||
var ipfilter = require("express-ipfilter").IpFilter;
|
||||
var fs = require("fs");
|
||||
var helmet = require("helmet");
|
||||
|
||||
var Server = function(config, callback) {
|
||||
console.log("Starting server op port " + config.port + " ... ");
|
||||
var Log = require("./logger.js");
|
||||
var Utils = require("./utils.js");
|
||||
|
||||
var Server = function (config, callback) {
|
||||
var port = config.port;
|
||||
if (process.env.MM_PORT) {
|
||||
port = process.env.MM_PORT;
|
||||
}
|
||||
|
||||
var server = null;
|
||||
if (config.useHttps) {
|
||||
var options = {
|
||||
key: fs.readFileSync(config.httpsPrivateKey),
|
||||
cert: fs.readFileSync(config.httpsCertificate)
|
||||
};
|
||||
server = require("https").Server(options, app);
|
||||
} else {
|
||||
server = require("http").Server(app);
|
||||
}
|
||||
var io = require("socket.io")(server);
|
||||
|
||||
Log.log("Starting server on port " + port + " ... ");
|
||||
|
||||
server.listen(port, config.address ? config.address : "localhost");
|
||||
|
||||
if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) {
|
||||
Log.info(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs"));
|
||||
}
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
var result = ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) {
|
||||
if (err === undefined) {
|
||||
return next();
|
||||
}
|
||||
Log.log(err.message);
|
||||
res.status(403).send("This device is not allowed to access your mirror. <br> Please check your config.js or config.js.sample to change this.");
|
||||
});
|
||||
});
|
||||
app.use(helmet());
|
||||
|
||||
server.listen(config.port);
|
||||
app.use("/js", express.static(__dirname));
|
||||
app.use("/config", express.static(path.resolve(__dirname + "/../config")));
|
||||
app.use("/css", express.static(path.resolve(__dirname + "/../css")));
|
||||
app.use("/fonts", express.static(path.resolve(__dirname + "/../fonts")));
|
||||
app.use("/modules", express.static(path.resolve(__dirname + "/../modules")));
|
||||
app.use("/vendor", express.static(path.resolve(__dirname + "/../vendor")));
|
||||
app.use("/translations", express.static(path.resolve(__dirname + "/../translations")));
|
||||
var directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations", "/tests/configs"];
|
||||
var directory;
|
||||
for (var i in directories) {
|
||||
directory = directories[i];
|
||||
app.use(directory, express.static(path.resolve(global.root_path + directory)));
|
||||
}
|
||||
|
||||
app.get("/", function(req, res) {
|
||||
res.sendFile(path.resolve(__dirname + "/../index.html"));
|
||||
app.get("/version", function (req, res) {
|
||||
res.send(global.version);
|
||||
});
|
||||
|
||||
app.get("/config", function (req, res) {
|
||||
res.send(config);
|
||||
});
|
||||
|
||||
app.get("/", function (req, res) {
|
||||
var html = fs.readFileSync(path.resolve(global.root_path + "/index.html"), { encoding: "utf8" });
|
||||
html = html.replace("#VERSION#", global.version);
|
||||
|
||||
var configFile = "config/config.js";
|
||||
if (typeof global.configuration_file !== "undefined") {
|
||||
configFile = global.configuration_file;
|
||||
}
|
||||
html = html.replace("#CONFIG_FILE#", configFile);
|
||||
|
||||
res.send(html);
|
||||
});
|
||||
|
||||
if (typeof callback === "function") {
|
||||
|
35
js/socket.js
35
js/socket.js
@@ -1,35 +0,0 @@
|
||||
/* exported Log */
|
||||
|
||||
/* Magic Mirror
|
||||
* Socket Connection
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var MMSocket = function(moduleName) {
|
||||
|
||||
var self = this;
|
||||
|
||||
if (typeof moduleName !== "string") {
|
||||
throw new Error("Please set the module name for the MMSocket.");
|
||||
}
|
||||
|
||||
self.moduleName = moduleName;
|
||||
|
||||
self.socket = io("http://localhost:8080");
|
||||
self.socket.on("notification", function(data) {
|
||||
MM.sendNotification(data.notification, data.payload, Socket);
|
||||
});
|
||||
|
||||
return {
|
||||
sendMessage: function(notification, payload, sender) {
|
||||
Log.log("Send socket message: " + notification);
|
||||
self.socket.emit("notification", {
|
||||
notification: notification,
|
||||
sender: sender,
|
||||
payload: payload
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
@@ -1,4 +1,12 @@
|
||||
var MMSocket = function(moduleName) {
|
||||
/* global io */
|
||||
|
||||
/* Magic Mirror
|
||||
* TODO add description
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
var MMSocket = function (moduleName) {
|
||||
var self = this;
|
||||
|
||||
if (typeof moduleName !== "string") {
|
||||
@@ -8,31 +16,36 @@ var MMSocket = function(moduleName) {
|
||||
self.moduleName = moduleName;
|
||||
|
||||
// Private Methods
|
||||
self.socket = io("/" + self.moduleName);
|
||||
var notificationCallback = function() {};
|
||||
var base = "/";
|
||||
if (typeof config !== "undefined" && typeof config.basePath !== "undefined") {
|
||||
base = config.basePath;
|
||||
}
|
||||
self.socket = io("/" + self.moduleName, {
|
||||
path: base + "socket.io"
|
||||
});
|
||||
var notificationCallback = function () {};
|
||||
|
||||
var onevent = self.socket.onevent;
|
||||
self.socket.onevent = function(packet) {
|
||||
self.socket.onevent = function (packet) {
|
||||
var args = packet.data || [];
|
||||
onevent.call(this, packet); // original call
|
||||
onevent.call(this, packet); // original call
|
||||
packet.data = ["*"].concat(args);
|
||||
onevent.call(this, packet); // additional call to catch-all
|
||||
onevent.call(this, packet); // additional call to catch-all
|
||||
};
|
||||
|
||||
// register catch all.
|
||||
self.socket.on("*", function(notification, payload) {
|
||||
self.socket.on("*", function (notification, payload) {
|
||||
if (notification !== "*") {
|
||||
//console.log('Received notification: ' + notification +', payload: ' + payload);
|
||||
notificationCallback(notification, payload);
|
||||
}
|
||||
});
|
||||
|
||||
// Public Methods
|
||||
this.setNotificationCallback = function(callback) {
|
||||
this.setNotificationCallback = function (callback) {
|
||||
notificationCallback = callback;
|
||||
};
|
||||
|
||||
this.sendNotification = function(notification, payload) {
|
||||
this.sendNotification = function (notification, payload) {
|
||||
if (typeof payload === "undefined") {
|
||||
payload = {};
|
||||
}
|
||||
|
@@ -1,12 +1,12 @@
|
||||
/* exported Translator */
|
||||
/* global translations */
|
||||
|
||||
/* Magic Mirror
|
||||
* Translator (l10n)
|
||||
*
|
||||
* By Christopher Fenner http://github.com/CFenner
|
||||
* By Christopher Fenner https://github.com/CFenner
|
||||
* MIT Licensed.
|
||||
*/
|
||||
var Translator = (function() {
|
||||
|
||||
var Translator = (function () {
|
||||
/* loadJSON(file, callback)
|
||||
* Load a JSON file via XHR.
|
||||
*
|
||||
@@ -18,7 +18,7 @@ var Translator = (function() {
|
||||
xhr.overrideMimeType("application/json");
|
||||
xhr.open("GET", file, true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4 && xhr.status == "200") {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
callback(JSON.parse(stripComments(xhr.responseText)));
|
||||
}
|
||||
};
|
||||
@@ -61,7 +61,7 @@ var Translator = (function() {
|
||||
currentChar = str[i];
|
||||
nextChar = str[i + 1];
|
||||
|
||||
if (!insideComment && currentChar === "\"") {
|
||||
if (!insideComment && currentChar === '"') {
|
||||
var escaped = str[i - 1] === "\\" && str[i - 2] !== "\\";
|
||||
if (!escaped) {
|
||||
insideString = !insideString;
|
||||
@@ -111,44 +111,64 @@ var Translator = (function() {
|
||||
translations: {},
|
||||
translationsFallback: {},
|
||||
|
||||
/* translate(module, key)
|
||||
/* translate(module, key, variables)
|
||||
* Load a translation for a given key for a given module.
|
||||
*
|
||||
* argument module Module - The module to load the translation for.
|
||||
* argument key string - The key of the text to translate.
|
||||
* argument variables - The variables to use within the translation template (optional)
|
||||
*/
|
||||
translate: function(module, key) {
|
||||
translate: function (module, key, variables) {
|
||||
variables = variables || {}; //Empty object by default
|
||||
|
||||
if(this.translations[module.name] && key in this.translations[module.name]) {
|
||||
// Combines template and variables like:
|
||||
// template: "Please wait for {timeToWait} before continuing with {work}."
|
||||
// variables: {timeToWait: "2 hours", work: "painting"}
|
||||
// to: "Please wait for 2 hours before continuing with painting."
|
||||
function createStringFromTemplate(template, variables) {
|
||||
if (Object.prototype.toString.call(template) !== "[object String]") {
|
||||
return template;
|
||||
}
|
||||
if (variables.fallback && !template.match(new RegExp("{.+}"))) {
|
||||
template = variables.fallback;
|
||||
}
|
||||
return template.replace(new RegExp("{([^}]+)}", "g"), function (_unused, varName) {
|
||||
return variables[varName] || "{" + varName + "}";
|
||||
});
|
||||
}
|
||||
|
||||
if (this.translations[module.name] && key in this.translations[module.name]) {
|
||||
// Log.log("Got translation for " + key + " from module translation: ");
|
||||
return this.translations[module.name][key];
|
||||
return createStringFromTemplate(this.translations[module.name][key], variables);
|
||||
}
|
||||
|
||||
if (key in this.coreTranslations) {
|
||||
// Log.log("Got translation for " + key + " from core translation.");
|
||||
return this.coreTranslations[key];
|
||||
return createStringFromTemplate(this.coreTranslations[key], variables);
|
||||
}
|
||||
|
||||
if (this.translationsFallback[module.name] && key in this.translationsFallback[module.name]) {
|
||||
// Log.log("Got translation for " + key + " from module translation fallback.");
|
||||
return this.translationsFallback[module.name][key];
|
||||
return createStringFromTemplate(this.translationsFallback[module.name][key], variables);
|
||||
}
|
||||
|
||||
if (key in this.coreTranslationsFallback) {
|
||||
// Log.log("Got translation for " + key + " from core translation fallback.");
|
||||
return this.coreTranslationsFallback[key];
|
||||
return createStringFromTemplate(this.coreTranslationsFallback[key], variables);
|
||||
}
|
||||
|
||||
return key;
|
||||
},
|
||||
/* load(module, file, callback)
|
||||
|
||||
/* load(module, file, isFallback, callback)
|
||||
* Load a translation file (json) and remember the data.
|
||||
*
|
||||
* argument module Module - The module to load the translation file for.
|
||||
* argument file string - Path of the file we want to load.
|
||||
* argument isFallback boolean - Flag to indicate fallback translations.
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
load: function(module, file, isFallback, callback) {
|
||||
load: function (module, file, isFallback, callback) {
|
||||
if (!isFallback) {
|
||||
Log.log(module.name + " - Load translation: " + file);
|
||||
} else {
|
||||
@@ -156,8 +176,8 @@ var Translator = (function() {
|
||||
}
|
||||
|
||||
var self = this;
|
||||
if(!this.translationsFallback[module.name]) {
|
||||
loadJSON(module.file(file), function(json) {
|
||||
if (!this.translationsFallback[module.name]) {
|
||||
loadJSON(module.file(file), function (json) {
|
||||
if (!isFallback) {
|
||||
self.translations[module.name] = json;
|
||||
} else {
|
||||
@@ -175,12 +195,12 @@ var Translator = (function() {
|
||||
*
|
||||
* argument lang String - The language identifier of the core language.
|
||||
*/
|
||||
loadCoreTranslations: function(lang) {
|
||||
loadCoreTranslations: function (lang) {
|
||||
var self = this;
|
||||
|
||||
if (lang in translations) {
|
||||
Log.log("Loading core translation file: " + translations[lang]);
|
||||
loadJSON(translations[lang], function(translations) {
|
||||
loadJSON(translations[lang], function (translations) {
|
||||
self.coreTranslations = translations;
|
||||
});
|
||||
} else {
|
||||
@@ -194,17 +214,21 @@ var Translator = (function() {
|
||||
* Load the core translations fallback.
|
||||
* The first language defined in translations.js will be used.
|
||||
*/
|
||||
loadCoreTranslationsFallback: function() {
|
||||
loadCoreTranslationsFallback: function () {
|
||||
var self = this;
|
||||
|
||||
// The variable `first` will contain the first
|
||||
// defined translation after the following line.
|
||||
for (var first in translations) {break;}
|
||||
for (var first in translations) {
|
||||
break;
|
||||
}
|
||||
|
||||
Log.log("Loading core translation fallback file: " + translations[first]);
|
||||
loadJSON(translations[first], function(translations) {
|
||||
self.coreTranslationsFallback = translations;
|
||||
});
|
||||
},
|
||||
if (first) {
|
||||
Log.log("Loading core translation fallback file: " + translations[first]);
|
||||
loadJSON(translations[first], function (translations) {
|
||||
self.coreTranslationsFallback = translations;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
19
js/utils.js
Normal file
19
js/utils.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/* Magic Mirror
|
||||
* Utils
|
||||
*
|
||||
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
|
||||
* MIT Licensed.
|
||||
*/
|
||||
var colors = require("colors/safe");
|
||||
|
||||
var Utils = {
|
||||
colors: {
|
||||
warn: colors.yellow,
|
||||
error: colors.red,
|
||||
info: colors.blue
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = Utils;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user