mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-08-18 11:46:48 +00:00
Compare commits
998 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
60b9a5b9da | ||
|
eaaa62a7f3 | ||
|
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 | ||
|
f4d5996a88 | ||
|
c75662e720 | ||
|
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 | ||
|
6e051d73c1 | ||
|
e381e1a313 | ||
|
2d03ff63cf | ||
|
309de631ed | ||
|
bad7316b80 | ||
|
4757c36233 | ||
|
9ca6180207 | ||
|
30179ad977 | ||
|
b799609749 | ||
|
efb6994ae7 | ||
|
880a977dd6 | ||
|
6eceffb403 | ||
|
795e33881c | ||
|
d310c3857f | ||
|
8c53908cc4 | ||
|
589b54984a | ||
|
3f30ed5251 | ||
|
3f9181905a | ||
|
29f3a81666 | ||
|
5efc43260e | ||
|
e01794a07f | ||
|
5fde095a6f | ||
|
3237af2d85 | ||
|
953b666ebd | ||
|
417bb4bf37 | ||
|
a55213d88a | ||
|
ec84d190f4 | ||
|
1b0bad72de | ||
|
931055708d | ||
|
15fd570b49 | ||
|
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 | ||
|
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 | ||
|
980b8a2924 |
@@ -1,6 +1,6 @@
|
||||
vendor/
|
||||
vendor/*
|
||||
!/vendor/vendor.js
|
||||
!/modules/default/**
|
||||
!/modules/node_helper
|
||||
!/modules/node_helper/**
|
||||
!/modules/default/defaultmodules.js
|
||||
!/modules/default/defaultmodules.js
|
||||
|
@@ -5,12 +5,18 @@
|
||||
"max-len": ["error", 250],
|
||||
"curly": "error",
|
||||
"camelcase": ["error", {"properties": "never"}],
|
||||
"no-trailing-spaces": ["error"],
|
||||
"no-trailing-spaces": ["error", {"ignoreComments": false }],
|
||||
"no-irregular-whitespace": ["error"]
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
}
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"globalReturn": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,6 +1,13 @@
|
||||
> Please send your pull requests the develop branch.
|
||||
> Don't forget to add the change to CHANGELOG.md.
|
||||
|
||||
**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!
|
||||
|
||||
|
||||
* 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.
|
||||
|
16
.gitignore
vendored
16
.gitignore
vendored
@@ -16,6 +16,9 @@ jspm_modules
|
||||
.npm
|
||||
.node_repl_history
|
||||
|
||||
# Visual Studio Code ignoramuses.
|
||||
.vscode/
|
||||
|
||||
# Various Windows ignoramuses.
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
@@ -53,14 +56,25 @@ Temporary Items
|
||||
|
||||
# Various Magic Mirror ignoramuses and anti-ignoramuses.
|
||||
|
||||
# Don't ignore the node_helper nore module.
|
||||
# Don't ignore the node_helper core 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
|
||||
|
17
.travis.yml
17
.travis.yml
@@ -1,7 +1,18 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
- "5.1"
|
||||
- "7"
|
||||
before_script:
|
||||
- yarn danger ci
|
||||
- npm install grunt-cli -g
|
||||
script: grunt
|
||||
- "export DISPLAY=:99.0"
|
||||
- "sh -e /etc/init.d/xvfb start"
|
||||
- sleep 5
|
||||
script:
|
||||
- grunt
|
||||
- npm run test:unit
|
||||
- npm run test:e2e
|
||||
after_script:
|
||||
- npm list
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
230
CHANGELOG.md
230
CHANGELOG.md
@@ -2,6 +2,234 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [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 to scroll 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
|
||||
- Calender 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 newfeed 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 allignment 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 fucntions 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 irrespecive 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 translaton 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 enviroment.
|
||||
- Enable ability to set configuration file by the enviroment 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 enviroment.
|
||||
- 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 compability, 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 enviroment 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 is 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 enviroment 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`
|
||||
@@ -118,7 +346,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
### Fixed
|
||||
- Added reference to Italian Translation.
|
||||
- Added the missing NE translation to all languages. [#334](https://github.com/MichMich/MagicMirror/issues/344)
|
||||
- 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
|
||||
|
48
Gruntfile.js
48
Gruntfile.js
@@ -6,9 +6,20 @@ module.exports = function(grunt) {
|
||||
options: {
|
||||
configFile: ".eslintrc.json"
|
||||
},
|
||||
target: ["js/*.js", "modules/default/*.js", "modules/default/*/*.js",
|
||||
"serveronly/*.js", "*.js", "!modules/default/alert/notificationFx.js",
|
||||
"!modules/default/alert/modernizr.custom.js", "!modules/default/alert/classie.js"
|
||||
target: [
|
||||
"js/*.js",
|
||||
"modules/default/*.js",
|
||||
"modules/default/*/*.js",
|
||||
"serveronly/*.js",
|
||||
"*.js",
|
||||
"tests/**/*.js",
|
||||
"!modules/default/alert/notificationFx.js",
|
||||
"!modules/default/alert/modernizr.custom.js",
|
||||
"!modules/default/alert/classie.js",
|
||||
"config/*",
|
||||
"translations/translations.js",
|
||||
"vendor/vendor.js",
|
||||
"modules/node_modules/node_helper/index.js"
|
||||
]
|
||||
},
|
||||
stylelint: {
|
||||
@@ -16,12 +27,26 @@ module.exports = function(grunt) {
|
||||
options: {
|
||||
configFile: ".stylelintrc"
|
||||
},
|
||||
src: ["css/main.css", "modules/default/calendar/calendar.css", "modules/default/clock/clock_styles.css", "modules/default/currentweather/currentweather.css", "modules/default/weatherforcast/weatherforcast.css"]
|
||||
src: [
|
||||
"css/main.css",
|
||||
"modules/default/calendar/calendar.css",
|
||||
"modules/default/clock/clock_styles.css",
|
||||
"modules/default/currentweather/currentweather.css",
|
||||
"modules/default/weatherforcast/weatherforcast.css"
|
||||
]
|
||||
}
|
||||
},
|
||||
jsonlint: {
|
||||
main: {
|
||||
src: ["package.json", ".eslintrc.json", ".stylelint"],
|
||||
src: [
|
||||
"package.json",
|
||||
".eslintrc.json",
|
||||
".stylelintrc",
|
||||
"translations/*.json",
|
||||
"modules/default/*/translations/*.json",
|
||||
"installers/pm2_MagicMirror.json",
|
||||
"vendor/package.js"
|
||||
],
|
||||
options: {
|
||||
reporter: "jshint"
|
||||
}
|
||||
@@ -53,11 +78,20 @@ module.exports = function(grunt) {
|
||||
"MD038": false
|
||||
}
|
||||
},
|
||||
src: ["README.md", "CHANGELOG.md", "LICENSE.md", "modules/README.md", "modules/default/**/*.md", "!modules/default/calendar/vendor/ical.js/readme.md"]
|
||||
src: [
|
||||
"README.md",
|
||||
"CHANGELOG.md",
|
||||
"LICENSE.md",
|
||||
"modules/README.md",
|
||||
"modules/default/**/*.md",
|
||||
"!modules/default/calendar/vendor/ical.js/readme.md"
|
||||
]
|
||||
}
|
||||
},
|
||||
yamllint: {
|
||||
all: [".travis.yml"]
|
||||
all: [
|
||||
".travis.yml"
|
||||
]
|
||||
}
|
||||
});
|
||||
grunt.loadNpmTasks("grunt-eslint");
|
||||
|
@@ -1,7 +1,7 @@
|
||||
The MIT License (MIT)
|
||||
=====================
|
||||
|
||||
Copyright © 2016 Michael Teeuw
|
||||
Copyright © 2016-2017 Michael Teeuw
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
|
159
README.md
159
README.md
@@ -7,6 +7,7 @@
|
||||
<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://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>
|
||||
<a href="http://slack.magicmirror.builders"><img src="http://slack.magicmirror.builders:3000/badge.svg" alt="Slack Status"></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).
|
||||
@@ -15,76 +16,130 @@ MagicMirror² focuses on a modular plugin system and uses [Electron](http://elec
|
||||
|
||||
## Table Of Contents
|
||||
|
||||
- [Usage](#usage)
|
||||
- [Installation](#installation)
|
||||
- [Raspberry Pi](#raspberrypi)
|
||||
- [General](#general)
|
||||
- [Server Only](#server-only)
|
||||
- [Client Only](#client-only)
|
||||
- [Docker](#docker)
|
||||
- [Configuration](#configuration)
|
||||
- [Modules](#modules)
|
||||
- [Updating](#updating)
|
||||
- [Known Issues](#known-issues)
|
||||
- [community](#community)
|
||||
- [Community](#community)
|
||||
- [Contributing Guidelines](#contributing-guidelines)
|
||||
- [Manifesto](#manifesto)
|
||||
|
||||
## Usage
|
||||
## Installation
|
||||
|
||||
### 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.
|
||||
### Raspberry Pi
|
||||
|
||||
### Automatic Installer (Raspberry Pi Only!)
|
||||
#### Automatic Installation (Raspberry Pi only!)
|
||||
|
||||
*Electron*, the app wrapper around MagicMirror², only supports the Raspberry Pi 2/3. The Raspberry Pi 0/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. (Yes, people have managed to run MM² also on a Pi0, so if you insist, search in the forums.)
|
||||
|
||||
Note that you will need to install the lastest full version of Raspbian, **don't use the Lite version**.
|
||||
|
||||
Execute the following command on your Raspberry Pi to install MagicMirror²:
|
||||
````
|
||||
curl -sL https://raw.githubusercontent.com/MichMich/MagicMirror/master/installers/raspberry.sh | bash
|
||||
````
|
||||
|
||||
### Manual Installation
|
||||
```bash
|
||||
bash -c "$(curl -sL https://raw.githubusercontent.com/MichMich/MagicMirror/master/installers/raspberry.sh)"
|
||||
```
|
||||
|
||||
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`
|
||||
#### Manual Installation
|
||||
|
||||
**Important:** `npm start` does **not** work via SSH, use `DISPLAY=:0 nohup npm start &` instead. This starts the mirror on the remote display.
|
||||
1. Download and install the latest *Node.js* version.
|
||||
2. Clone the repository and check out the master branch: `git clone https://github.com/MichMich/MagicMirror`
|
||||
3. Enter the repository: `cd MagicMirror/`
|
||||
4. Install and run the app with: `npm install && npm start` \
|
||||
For **Server Only** use: `npm install && node serveronly` .
|
||||
|
||||
|
||||
**:warning: Important!**
|
||||
|
||||
- **The installation step for `npm install` will take a very long time**, often with little or no terminal response! \
|
||||
For the RPi3 this is **~10** minutes and for the Rpi2 **~25** minutes. \
|
||||
Do not interrupt or you risk getting a :broken_heart: by Raspberry Jam.
|
||||
|
||||
|
||||
Also note that:
|
||||
|
||||
- `npm start` does **not** work via SSH. But you can use `DISPLAY=:0 nohup npm start &` instead. \
|
||||
This starts the mirror on the remote display.
|
||||
- If you want to debug on Raspberry Pi you can use `npm start dev` which will start MM with *Dev Tools* enabled.
|
||||
- To access toolbar menu when in mirror mode, hit `ALT` key.
|
||||
- To toggle the (web) `Developer Tools` from mirror mode, use `CTRL-SHIFT-I` or `ALT` and select `View`.
|
||||
|
||||
**Note:** if you want to debug on Raspberry Pi you can use `npm start dev` which will start the MagicMirror app with Dev Tools enabled.
|
||||
|
||||
### 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.
|
||||
In some cases, you want to start the application without an actual app window. In this case, you can start MagicMirror² in server only mode by manually running `node serveronly` or using Docker. This will start the server, after which you can open the application in your browser of choice. Detailed description below.
|
||||
|
||||
### Raspberry Configuration & Auto Start.
|
||||
**Important:** Make sure that you whitelist the interface/ip (`ipWhitelist`) in the server config where you want the client to connect to, otherwise it will not be allowed to connect to the server. You also need to set the local host `address` field to `0.0.0.0` in order for the RPi to listen on all interfaces and not only `localhost` (default).
|
||||
|
||||
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 your 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:
|
||||
|
||||
```bash
|
||||
git pull && npm install
|
||||
```javascript
|
||||
var config = {
|
||||
address: "0.0.0.0", // default is "localhost"
|
||||
port: 8080, // default
|
||||
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:172.17.0.1"], // default -- need to add your IP here
|
||||
...
|
||||
};
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### Client Only
|
||||
|
||||
This is when you already have a server running remotely and want your RPi to connect as a standalone client to this instance, to show the MM from the server. Then from your RPi, you run it with: `node clientonly --address 192.168.1.5 --port 8080`. (Specify the ip address and port number of the server)
|
||||
|
||||
|
||||
### Docker
|
||||
|
||||
MagicMirror² in server only mode can be deployed using [Docker](https://docker.com). After a successful [Docker installation](https://docs.docker.com/engine/installation/) you just need to execute the following command in the shell:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--publish 80:8080 \
|
||||
--restart always \
|
||||
--volume ~/magic_mirror/config:/opt/magic_mirror/config \
|
||||
--volume ~/magic_mirror/modules:/opt/magic_mirror/modules \
|
||||
--name magic_mirror \
|
||||
bastilimbach/docker-magicmirror
|
||||
```
|
||||
To get more information about the available Dockerfile versions and configurations head over to the respective [GitHub repository](https://github.com/bastilimbach/docker-MagicMirror).
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
1. Duplicate `config/config.js.sample` to `config/config.js`.
|
||||
2. Modify your required settings.
|
||||
### Raspberry Specific
|
||||
|
||||
The following wiki links are helpful for the initial 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)
|
||||
|
||||
|
||||
### General
|
||||
|
||||
1. Copy `config/config.js.sample` to `config/config.js`. \
|
||||
**Note:** If you used the installer script. This step is already done for you.
|
||||
|
||||
2. Modify your required settings. \
|
||||
Note: You'll can check your configuration running `npm run config:check`.
|
||||
|
||||
|
||||
The following properties can be configured:
|
||||
|
||||
| **Option** | **Description** |
|
||||
| --- | --- |
|
||||
| `port` | The port on which the MagicMirror² server will run on. The default value is `8080`. |
|
||||
| `address` | The ip address the accept connections. The default open bind `::` is IPv6 is available or `0.0.0.0` IPv4 run on. Example config: `192.168.10.100`. |
|
||||
| `ipWhitelist` | The list of IPs from which you are allowed to access the MagicMirror². The default value is `["127.0.0.1", "::ffff:127.0.0.1", "::1"]`. It is possible to specify IPs with subnet masks (`["127.0.0.1", "127.0.0.1/24"]`) or define ip ranges (`["127.0.0.1", ["192.168.0.1", "192.168.0.100"]]`).|
|
||||
| `address` | The *interface* ip address on which to accept connections. The default is `localhost`, which would prevent exposing the built-in webserver to machines on the local network. To expose it to other machines, use: `0.0.0.0`. |
|
||||
| `ipWhitelist` | The list of IPs from which you are allowed to access the MagicMirror². The default value is `["127.0.0.1", "::ffff:127.0.0.1", "::1"]`, which is from `localhost` only. Add your IP when needed. You can also specify IP ranges with subnet masks (`["127.0.0.1", "127.0.0.1/24"]`) or directly with (`["127.0.0.1", ["192.168.0.1", "192.168.0.100"]]`). Set `[]` to allow all IP addresses. For more information see: [follow post ipWhitelist HowTo](https://forum.magicmirror.builders/topic/1326/ipwhitelist-howto) |
|
||||
| `zoom` | This allows to scale the mirror contents with a given zoom factor. The default value is `1.0`|
|
||||
| `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.** |
|
||||
| `electronOptions` | An optional array of Electron (browser) options. This allows configuration of e.g. the browser screen size and position (defaults `.width = 800` & `.height = 600`). Kiosk mode can be enabled by setting `.kiosk = true`, `.autoHideMenuBar = false`, `.fullscreen = false`. More options can be found [here](https://github.com/electron/electron/blob/master/docs/api/browser-window.md). |
|
||||
|
||||
| `electronOptions` | An optional array of Electron (browser) options. This allows configuration of e.g. the browser screen size and position (example: `electronOptions: { fullscreen: false, width: 800, height: 600 }`). Kiosk mode can be enabled by setting `kiosk = true`, `autoHideMenuBar = false` and `fullscreen = false`. More options can be found [here](https://github.com/electron/electron/blob/master/docs/api/browser-window.md). |
|
||||
| `customCss` | The path of the `custom.css` stylesheet. The default is `css/custom.css`. |
|
||||
|
||||
Module configuration:
|
||||
|
||||
@@ -110,12 +165,20 @@ The following modules are installed by default.
|
||||
- [**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)!
|
||||
For more available modules, check out out the wiki page [MagicMirror² 3rd Party Modules](https://github.com/MichMich/MagicMirror/wiki/3rd-party-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).
|
||||
## Updating
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
git pull && npm install
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## Community
|
||||
|
||||
@@ -134,6 +197,22 @@ Please keep the following in mind:
|
||||
|
||||
Thanks for your help in making MagicMirror² better!
|
||||
|
||||
## Manifesto
|
||||
|
||||
A real Manifesto is still to be written. Till then, Michael's response on [one of the repository issues](https://github.com/MichMich/MagicMirror/issues/1174) gives a great summary:
|
||||
|
||||
> "... I started this project as an ultimate starter project for Raspberry Pi enthusiasts. As a matter of fact, for most of the contributors, the MagicMirror project is the first open source project they ever contributed to. This is one of the reasons why the MagicMirror project is featured in several RasPi magazines.
|
||||
>
|
||||
>The project has a lot of opportunities for improvement. We could use a powerful framework like Vue to ramp up the development speed. We could use SASS for better/easier css implementations. We could make it an NPM installable package. And as you say, we could bundle it up. The big downside of of of these changes is that it over complicates things: a user no longer will be able to open just one file and make a small modification and see how it works out.
|
||||
>
|
||||
>Of course, a bundled version can be complimentary to the regular un-bundled version. And I'm sure a lot of (new) users will opt for the bundled version. But this means those users won't be motivated to take a peek under the hood. They will just remain 'users'. They won't become contributors, and worse: they won't be motivated to take their first steps in software development.
|
||||
>
|
||||
>And to be honest: motivating curious users to step out of their comfort zone and take those first steps is what drives me in this project. Therefor my ultimate goal is this project is to keep it as accessible as possible."
|
||||
>
|
||||
> ~ Michael Teeuw
|
||||
|
||||
|
||||
|
||||
<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>
|
||||
|
104
clientonly/index.js
Normal file
104
clientonly/index.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/* jshint esversion: 6 */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Use seperate 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()]);
|
||||
})
|
||||
}
|
||||
|
||||
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 incomming 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'");
|
||||
}
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
getServerAddress();
|
||||
|
||||
(config.address && config.port) || fail();
|
||||
|
||||
// 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(`http://${config.address}:${config.port}/config/`)
|
||||
.then(function (config) {
|
||||
// Pass along the server config via an environment variable
|
||||
var env = Object.create(process.env);
|
||||
var options = { env: env };
|
||||
config.address = config.address;
|
||||
config.port = config.port;
|
||||
env.config = JSON.stringify(config);
|
||||
|
||||
// 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();
|
||||
}
|
||||
}());
|
@@ -2,67 +2,80 @@
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*
|
||||
* For more information how you can configurate 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, is "localhost"
|
||||
port: 8080,
|
||||
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
|
||||
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',
|
||||
language: "en",
|
||||
timeFormat: 24,
|
||||
units: 'metric',
|
||||
units: "metric",
|
||||
|
||||
modules: [
|
||||
{
|
||||
module: 'alert',
|
||||
module: "alert",
|
||||
},
|
||||
{
|
||||
module: "updatenotification",
|
||||
position: "top_bar"
|
||||
},
|
||||
{
|
||||
module: 'clock',
|
||||
position: 'top_left'
|
||||
module: "clock",
|
||||
position: "top_left"
|
||||
},
|
||||
{
|
||||
module: 'calendar',
|
||||
header: 'US Holidays',
|
||||
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-o ",
|
||||
url: "webcal://www.calendarlabs.com/templates/ical/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',
|
||||
locationID: '', //ID from http://www.openweathermap.org
|
||||
appid: 'YOUR_OPENWEATHER_API_KEY'
|
||||
location: "New York",
|
||||
locationID: "", //ID from http://www.openweathermap.org/help/city_list.txt
|
||||
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',
|
||||
locationID: '5128581', //ID from http://www.openweathermap.org
|
||||
appid: 'YOUR_OPENWEATHER_API_KEY'
|
||||
location: "New York",
|
||||
locationID: "5128581", //ID from http://www.openweathermap.org/help/city_list.txt
|
||||
appid: "YOUR_OPENWEATHER_API_KEY"
|
||||
}
|
||||
},
|
||||
{
|
||||
module: 'newsfeed',
|
||||
position: 'bottom_bar',
|
||||
module: "newsfeed",
|
||||
position: "bottom_bar",
|
||||
config: {
|
||||
feeds: [
|
||||
{
|
||||
@@ -79,4 +92,4 @@ var config = {
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== 'undefined') {module.exports = config;}
|
||||
if (typeof module !== "undefined") {module.exports = config;}
|
||||
|
27
css/main.css
27
css/main.css
@@ -1,6 +1,7 @@
|
||||
html {
|
||||
cursor: none;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
@@ -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;
|
||||
@@ -121,6 +122,12 @@ sup {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.no-wrap {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Region Definitions.
|
||||
*/
|
||||
@@ -135,10 +142,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 {
|
||||
@@ -149,6 +162,10 @@ sup {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.region.bottom .container {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.region.top .container:empty {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -173,10 +190,6 @@ sup {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.region.bottom .container {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.region.bottom .container:empty {
|
||||
margin-top: 0;
|
||||
}
|
||||
@@ -219,10 +232,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.8.0",
|
||||
"resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.8.0.tgz",
|
||||
"integrity": "sha512-ZYzRkETgBrdEGzL5JSKimvjI2CX7ioyZCkX2BpcfyjqI+079W0wHAyj5W4rIZMcDSOHgLZtgz1IdDi/vU77KEQ=="
|
||||
}
|
||||
}
|
||||
}
|
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.8.0"
|
||||
}
|
||||
}
|
@@ -5,9 +5,9 @@
|
||||
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");
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff") format("woff"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@@ -17,9 +17,9 @@
|
||||
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");
|
||||
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"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@@ -29,9 +29,9 @@
|
||||
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");
|
||||
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"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@@ -41,9 +41,9 @@
|
||||
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");
|
||||
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"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@@ -53,9 +53,9 @@
|
||||
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");
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff") format("woff"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@@ -65,9 +65,9 @@
|
||||
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");
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff") format("woff"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@@ -77,9 +77,9 @@
|
||||
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");
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff") format("woff"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@@ -89,7 +89,7 @@
|
||||
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");
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff") format("woff"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.ttf") format("truetype");
|
||||
}
|
||||
|
7
fonts/yarn.lock
Normal file
7
fonts/yarn.lock
Normal file
@@ -0,0 +1,7 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
roboto-fontface@^0.8.0:
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/roboto-fontface/-/roboto-fontface-0.8.0.tgz#031a83c8f79932801a57d83bf743f37250163499"
|
11
index.html
11
index.html
@@ -1,9 +1,15 @@
|
||||
<!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">
|
||||
@@ -32,8 +38,9 @@
|
||||
</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="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>
|
||||
|
2
installers/mm.sh
Executable file
2
installers/mm.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
cd ~/MagicMirror
|
||||
DISPLAY=:0 npm start
|
7
installers/pm2_MagicMirror.json
Normal file
7
installers/pm2_MagicMirror.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"apps" : [{
|
||||
"name" : "MagicMirror",
|
||||
"script" : "/home/pi/MagicMirror/installers/mm.sh",
|
||||
"watch" : ["/home/pi/MagicMirror/config/config.js"]
|
||||
}]
|
||||
}
|
2
installers/postinstall/postinstall.sh
Normal file
2
installers/postinstall/postinstall.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
echo "\033[32mMagicMirror installation successful!"
|
||||
exit 0
|
@@ -21,10 +21,10 @@ echo -e "\e[0m"
|
||||
# Define the tested version of Node.js.
|
||||
NODE_TESTED="v5.1.0"
|
||||
|
||||
#Determine which Pi is running.
|
||||
# Determine which Pi is running.
|
||||
ARM=$(uname -m)
|
||||
|
||||
#Check the Raspberry Pi version.
|
||||
# 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."
|
||||
@@ -32,13 +32,17 @@ if [ "$ARM" != "armv7l" ]; then
|
||||
exit;
|
||||
fi
|
||||
|
||||
#define helper methods.
|
||||
# Define helper methods.
|
||||
function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; }
|
||||
function command_exists () { type "$1" &> /dev/null ;}
|
||||
|
||||
# Update before first apt-get
|
||||
echo -e "\e[96mUpdating packages ...\e[90m"
|
||||
sudo apt-get update || echo -e "\e[91mUpdate failed, carrying on installation ...\e[90m"
|
||||
|
||||
# Installing helper tools
|
||||
echo -e "\e[96mInstalling helper tools ...\e[90m"
|
||||
sudo apt-get install curl wget git build-essential unzip || exit
|
||||
sudo apt-get --assume-yes install curl wget git build-essential unzip || exit
|
||||
|
||||
# Check if we need to install or upgrade Node.js.
|
||||
echo -e "\e[96mCheck current Node installation ...\e[0m"
|
||||
@@ -52,8 +56,8 @@ if command_exists node; 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.
|
||||
# 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."
|
||||
@@ -61,7 +65,7 @@ if command_exists node; then
|
||||
fi
|
||||
|
||||
else
|
||||
echo -e "\e[92mNo Node.js upgrade nessecery.\e[0m"
|
||||
echo -e "\e[92mNo Node.js upgrade necessary.\e[0m"
|
||||
fi
|
||||
|
||||
else
|
||||
@@ -74,17 +78,17 @@ if $NODE_INSTALL; then
|
||||
|
||||
echo -e "\e[96mInstalling Node.js ...\e[90m"
|
||||
|
||||
#Fetch the latest version of Node.js from the selected branch
|
||||
#The NODE_STABLE_BRANCH variable will need to be manually adjusted when a new branch is released. (e.g. 7.x)
|
||||
#Only tested (stable) versions are recommended as newer versions could break MagicMirror.
|
||||
# Fetch the latest version of Node.js from the selected branch
|
||||
# The NODE_STABLE_BRANCH variable will need to be manually adjusted when a new branch is released. (e.g. 7.x)
|
||||
# Only tested (stable) versions are recommended as newer versions could break MagicMirror.
|
||||
|
||||
NODE_STABLE_BRANCH="6.x"
|
||||
NODE_STABLE_BRANCH="9.x"
|
||||
curl -sL https://deb.nodesource.com/setup_$NODE_STABLE_BRANCH | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
echo -e "\e[92mNode.js installation Done!\e[0m"
|
||||
fi
|
||||
|
||||
#Install magic mirror
|
||||
# Install MagicMirror
|
||||
cd ~
|
||||
if [ -d "$HOME/MagicMirror" ] ; then
|
||||
echo -e "\e[93mIt seems like MagicMirror is already installed."
|
||||
@@ -97,7 +101,7 @@ if [ -d "$HOME/MagicMirror" ] ; then
|
||||
fi
|
||||
|
||||
echo -e "\e[96mCloning MagicMirror ...\e[90m"
|
||||
if git clone https://github.com/MichMich/MagicMirror.git; then
|
||||
if git clone --depth=1 https://github.com/MichMich/MagicMirror.git; then
|
||||
echo -e "\e[92mCloning MagicMirror Done!\e[0m"
|
||||
else
|
||||
echo -e "\e[91mUnable to clone MagicMirror."
|
||||
@@ -113,6 +117,9 @@ else
|
||||
exit;
|
||||
fi
|
||||
|
||||
# Use sample config for start MagicMirror
|
||||
cp config/config.js.sample config/config.js
|
||||
|
||||
# Check if plymouth is installed (default with PIXEL desktop environment), then install custom splashscreen.
|
||||
echo -e "\e[96mCheck plymouth installation ...\e[0m"
|
||||
if command_exists plymouth; then
|
||||
@@ -141,6 +148,15 @@ else
|
||||
echo -e "\e[93mplymouth is not installed.\e[0m";
|
||||
fi
|
||||
|
||||
# Use pm2 control like a service MagicMirror
|
||||
read -p "Do you want use pm2 for auto starting of your MagicMirror (y/N)?" choice
|
||||
if [[ $choice =~ ^[Yy]$ ]]; then
|
||||
sudo npm install -g pm2
|
||||
sudo su -c "env PATH=$PATH:/usr/bin pm2 startup linux -u pi --hp /home/pi"
|
||||
pm2 start ~/MagicMirror/installers/pm2_MagicMirror.json
|
||||
pm2 save
|
||||
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.\e[0m"
|
||||
echo " "
|
||||
|
130
js/app.js
130
js/app.js
@@ -7,6 +7,7 @@
|
||||
|
||||
var fs = require("fs");
|
||||
var Server = require(__dirname + "/server.js");
|
||||
var Utils = require(__dirname + "/utils.js");
|
||||
var defaultModules = require(__dirname + "/../modules/default/defaultmodules.js");
|
||||
var path = require("path");
|
||||
|
||||
@@ -17,6 +18,16 @@ console.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) {
|
||||
@@ -41,32 +52,58 @@ var App = function() {
|
||||
var loadConfig = function(callback) {
|
||||
console.log("Loading config ...");
|
||||
var defaults = require(__dirname + "/defaults.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) {
|
||||
if (e.code == "ENOENT") {
|
||||
console.error("WARNING! Could not find config file. Please create one. Starting with default configuration.");
|
||||
callback(defaults);
|
||||
console.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) {
|
||||
console.error("WARNING! Could not validate config file. Please correct syntax errors. Starting with default configuration.");
|
||||
callback(defaults);
|
||||
console.error(Utils.colors.error("WARNING! Could not validate config file. Please correct syntax errors. Starting with default configuration."));
|
||||
} else {
|
||||
console.error("WARNING! Could not load config file. Starting with default configuration. Error found: " + e);
|
||||
callback(defaults);
|
||||
console.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) {
|
||||
console.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];
|
||||
@@ -103,6 +140,10 @@ var App = function() {
|
||||
m.setName(moduleName);
|
||||
m.setPath(path.resolve(moduleFolder));
|
||||
nodeHelpers.push(m);
|
||||
|
||||
m.loaded(callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -111,14 +152,24 @@ var App = function() {
|
||||
*
|
||||
* argument module string - The name of the module (including subpath).
|
||||
*/
|
||||
var loadModules = function(modules) {
|
||||
var loadModules = function(modules, callback) {
|
||||
console.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
|
||||
console.log("All module helpers loaded.");
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
console.log("All module helpers loaded.");
|
||||
loadNextModule();
|
||||
};
|
||||
|
||||
/* cmpVersions(a,b)
|
||||
@@ -164,27 +215,54 @@ var App = function() {
|
||||
}
|
||||
}
|
||||
|
||||
loadModules(modules);
|
||||
loadModules(modules, function() {
|
||||
var server = new Server(config, function(app, io) {
|
||||
console.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 ...");
|
||||
|
||||
console.log("Sockets connected & modules started ...");
|
||||
|
||||
if (typeof callback === "function") {
|
||||
callback(config);
|
||||
}
|
||||
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", () => {
|
||||
console.log("[SIGINT] Received. Shutting down server...");
|
||||
setTimeout(() => { process.exit(0); }, 3000); // Force quit after 3 seconds
|
||||
this.stop();
|
||||
process.exit(0);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = new App();
|
||||
|
38
js/class.js
38
js/class.js
@@ -4,15 +4,15 @@
|
||||
*/
|
||||
|
||||
// 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,
|
||||
@@ -30,23 +30,23 @@
|
||||
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;
|
||||
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
|
||||
@@ -90,4 +90,6 @@ function cloneObject(obj) {
|
||||
}
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {module.exports = Class;}
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = Class;
|
||||
}
|
||||
|
@@ -7,8 +7,14 @@
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var port = 8080;
|
||||
var address = "localhost";
|
||||
if (typeof(mmPort) !== "undefined") {
|
||||
port = mmPort;
|
||||
}
|
||||
var defaults = {
|
||||
port: 8080,
|
||||
address: address,
|
||||
port: port,
|
||||
kioskmode: false,
|
||||
electronOptions: {},
|
||||
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
|
||||
@@ -17,6 +23,7 @@ var defaults = {
|
||||
timeFormat: 24,
|
||||
units: "metric",
|
||||
zoom: 1,
|
||||
customCss: "css/custom.css",
|
||||
|
||||
modules: [
|
||||
{
|
||||
|
14
js/deprecated.js
Normal file
14
js/deprecated.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/* Magic Mirror Deprecated Config Options List
|
||||
*
|
||||
* By Michael Teeuw http://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;}
|
@@ -2,12 +2,11 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const Server = require(__dirname + "/server.js");
|
||||
const electron = require("electron");
|
||||
const core = require(__dirname + "/app.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.
|
||||
@@ -18,7 +17,6 @@ const BrowserWindow = electron.BrowserWindow;
|
||||
let mainWindow;
|
||||
|
||||
function createWindow() {
|
||||
|
||||
var electronOptionsDefaults = {
|
||||
width: 800,
|
||||
height: 600,
|
||||
@@ -28,8 +26,9 @@ function createWindow() {
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
zoomFactor: config.zoom
|
||||
}
|
||||
}
|
||||
},
|
||||
backgroundColor: "#000000"
|
||||
};
|
||||
|
||||
// DEPRECATED: "kioskmode" backwards compatibility, to be removed
|
||||
// settings these options directly instead provides cleaner interface
|
||||
@@ -46,11 +45,12 @@ function createWindow() {
|
||||
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
|
||||
var address = (config.address === void 0) | (config.address === "") ? (config.address = "localhost") : config.address;
|
||||
mainWindow.loadURL(`http://${address}:${config.port}`);
|
||||
|
||||
// Open the DevTools if run with "npm start dev"
|
||||
if(process.argv[2] == "dev") {
|
||||
if (process.argv.includes("dev")) {
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
@@ -96,8 +96,24 @@ 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) => {
|
||||
console.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;
|
||||
});
|
||||
}
|
||||
|
16
js/loader.js
16
js/loader.js
@@ -32,10 +32,10 @@ var Loader = (function() {
|
||||
});
|
||||
} 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();
|
||||
});
|
||||
@@ -186,6 +186,11 @@ var Loader = (function() {
|
||||
script.onload = function() {
|
||||
if (typeof callback === "function") {callback();}
|
||||
};
|
||||
script.onerror = function() {
|
||||
console.error("Error on loading script:", fileName);
|
||||
if (typeof callback === "function") {callback();}
|
||||
};
|
||||
|
||||
document.getElementsByTagName("body")[0].appendChild(script);
|
||||
break;
|
||||
case "css":
|
||||
@@ -197,6 +202,11 @@ var Loader = (function() {
|
||||
stylesheet.onload = function() {
|
||||
if (typeof callback === "function") {callback();}
|
||||
};
|
||||
stylesheet.onerror = function() {
|
||||
console.error("Error on loading stylesheet:", fileName);
|
||||
if (typeof callback === "function") {callback();}
|
||||
};
|
||||
|
||||
document.getElementsByTagName("head")[0].appendChild(stylesheet);
|
||||
break;
|
||||
}
|
||||
|
179
js/main.js
179
js/main.js
@@ -1,5 +1,5 @@
|
||||
/* global Log, Loader, Module, config, defaults */
|
||||
/* jshint -W020 */
|
||||
/* jshint -W020, esversion: 6 */
|
||||
|
||||
/* Magic Mirror
|
||||
* Main System
|
||||
@@ -19,42 +19,49 @@ var MM = (function() {
|
||||
* are configured for a specific position.
|
||||
*/
|
||||
var createDomObjects = function() {
|
||||
for (var m in modules) {
|
||||
var module = modules[m];
|
||||
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;
|
||||
moduleHeader.className = "module-header";
|
||||
dom.appendChild(moduleHeader);
|
||||
}
|
||||
|
||||
var moduleContent = document.createElement("div");
|
||||
moduleContent.className = "module-content";
|
||||
dom.appendChild(moduleContent);
|
||||
|
||||
updateDom(module, 0);
|
||||
modules.forEach(module => {
|
||||
if (typeof module.data.position !== "string") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
moduleHeader.className = "module-header";
|
||||
dom.appendChild(moduleHeader);
|
||||
}
|
||||
|
||||
var moduleContent = document.createElement("div");
|
||||
moduleContent.className = "module-content";
|
||||
dom.appendChild(moduleContent);
|
||||
|
||||
var domCreationPromise = updateDom(module, 0);
|
||||
domCreationPromises.push(domCreationPromise);
|
||||
domCreationPromise.then(() => {
|
||||
sendNotification("MODULE_DOM_CREATED", null, null, module);
|
||||
}).catch(Log.error);
|
||||
});
|
||||
|
||||
updateWrapperStates();
|
||||
|
||||
sendNotification("DOM_OBJECTS_CREATED");
|
||||
Promise.all(domCreationPromises).then(() => {
|
||||
sendNotification("DOM_OBJECTS_CREATED");
|
||||
});
|
||||
};
|
||||
|
||||
/* selectWrapper(position)
|
||||
@@ -66,7 +73,7 @@ var MM = (function() {
|
||||
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];
|
||||
}
|
||||
@@ -79,11 +86,12 @@ var MM = (function() {
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -94,19 +102,53 @@ 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 newHeader = module.getHeader();
|
||||
return new Promise((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);
|
||||
}
|
||||
|
||||
newContentPromise.then((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((resolve) => {
|
||||
if (module.hidden || !speed) {
|
||||
updateModuleContent(module, newHeader, newContent);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!moduleNeedsUpdate(module, newHeader, newContent)) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!speed) {
|
||||
updateModuleContent(module, newHeader, newContent);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,16 +157,16 @@ var MM = (function() {
|
||||
if (!module.hidden) {
|
||||
showModule(module, speed / 2);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
updateModuleContent(module, newHeader, 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?
|
||||
@@ -152,6 +194,7 @@ var MM = (function() {
|
||||
* 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, newHeader, newContent) {
|
||||
@@ -165,8 +208,6 @@ var MM = (function() {
|
||||
if( headerWrapper.length > 0 && newHeader) {
|
||||
headerWrapper[0].innerHTML = newHeader;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
/* hideModule(module, speed, callback)
|
||||
@@ -219,7 +260,7 @@ var MM = (function() {
|
||||
|
||||
// remove lockString if set in options.
|
||||
if (options.lockString) {
|
||||
var index = module.lockStrings.indexOf(options.lockString)
|
||||
var index = module.lockStrings.indexOf(options.lockString);
|
||||
if ( index !== -1) {
|
||||
module.lockStrings.splice(index, 1);
|
||||
}
|
||||
@@ -232,6 +273,8 @@ var MM = (function() {
|
||||
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);
|
||||
@@ -243,10 +286,13 @@ var MM = (function() {
|
||||
moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
|
||||
// Restore the postition. See hideModule() for more info.
|
||||
moduleWrapper.style.position = "static";
|
||||
moduleWrapper.style.opacity = 1;
|
||||
|
||||
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(); }
|
||||
@@ -306,43 +352,36 @@ var MM = (function() {
|
||||
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 divided)
|
||||
*
|
||||
* return array - Filtered collection of modules.
|
||||
*/
|
||||
var withClass = function(className) {
|
||||
var searchClasses = className;
|
||||
if (typeof className === "string") {
|
||||
searchClasses = className.split(" ");
|
||||
}
|
||||
|
||||
var newModules = modules.filter(function(module) {
|
||||
var classes = module.data.classes.toLowerCase().split(" ");
|
||||
|
||||
for (var c in searchClasses) {
|
||||
var searchClass = searchClasses[c];
|
||||
if (classes.indexOf(searchClass.toLowerCase()) !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
setSelectionMethodsForModules(newModules);
|
||||
return newModules;
|
||||
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 divided)
|
||||
*
|
||||
* return array - Filtered collection of modules.
|
||||
*/
|
||||
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(" ");
|
||||
@@ -354,11 +393,11 @@ var MM = (function() {
|
||||
for (var c in searchClasses) {
|
||||
var searchClass = searchClasses[c];
|
||||
if (classes.indexOf(searchClass.toLowerCase()) !== -1) {
|
||||
return false;
|
||||
return include;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return !include;
|
||||
});
|
||||
|
||||
setSelectionMethodsForModules(newModules);
|
||||
@@ -504,7 +543,7 @@ var MM = (function() {
|
||||
* argument options object - Optional settings for the hide method.
|
||||
*/
|
||||
showModule: function(module, speed, callback, options) {
|
||||
module.hidden = false;
|
||||
// do not change module.hidden yet, only if we really show it later
|
||||
showModule(module, speed, callback, options);
|
||||
}
|
||||
};
|
||||
|
151
js/module.js
151
js/module.js
@@ -27,6 +27,11 @@ var Module = Class.extend({
|
||||
// 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.
|
||||
*/
|
||||
@@ -70,25 +75,36 @@ var Module = Class.extend({
|
||||
|
||||
/* 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 getTemplete 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);
|
||||
return new Promise((resolve) => {
|
||||
var div = document.createElement("div");
|
||||
var template = this.getTemplate();
|
||||
var templateData = this.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
|
||||
this.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 = this.nunjucksEnvironment().renderString(template, templateData);
|
||||
|
||||
resolve(div);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/* getHeader()
|
||||
@@ -102,6 +118,28 @@ var Module = Class.extend({
|
||||
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 tempate.
|
||||
* 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.
|
||||
@@ -118,6 +156,30 @@ var Module = Class.extend({
|
||||
}
|
||||
},
|
||||
|
||||
/** 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.
|
||||
*
|
||||
@@ -194,7 +256,7 @@ var Module = Class.extend({
|
||||
* return string - File path.
|
||||
*/
|
||||
file: function (file) {
|
||||
return this.data.path + "/" + file;
|
||||
return (this.data.path + "/" + file).replace("//", "/");
|
||||
},
|
||||
|
||||
/* loadStyles()
|
||||
@@ -203,22 +265,7 @@ 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();
|
||||
this.loadDependencies("getStyles", callback);
|
||||
},
|
||||
|
||||
/* loadScripts()
|
||||
@@ -227,22 +274,32 @@ var Module = Class.extend({
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
loadScripts: function (callback) {
|
||||
var self = this;
|
||||
var scripts = this.getScripts();
|
||||
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()
|
||||
@@ -277,14 +334,18 @@ var Module = Class.extend({
|
||||
}
|
||||
},
|
||||
|
||||
/* translate(key, defaultValue)
|
||||
* Request the translation for a given key.
|
||||
/* 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 translage
|
||||
* argument defaultValue string - The default value if no translation was found. (Optional)
|
||||
* 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, defaultValue) {
|
||||
return Translator.translate(this, key) || defaultValue || "";
|
||||
translate: function (key, defaultValueOrVariables, defaultValue) {
|
||||
if(typeof defaultValueOrVariables === "object") {
|
||||
return Translator.translate(this, key, defaultValueOrVariables) || defaultValue || "";
|
||||
}
|
||||
return Translator.translate(this, key) || defaultValueOrVariables || "";
|
||||
},
|
||||
|
||||
/* updateDom(speed)
|
||||
|
39
js/server.js
39
js/server.js
@@ -13,14 +13,25 @@ var path = require("path");
|
||||
var ipfilter = require("express-ipfilter").IpFilter;
|
||||
var fs = require("fs");
|
||||
var helmet = require("helmet");
|
||||
var Utils = require(__dirname + "/utils.js");
|
||||
|
||||
var Server = function(config, callback) {
|
||||
console.log("Starting server op port " + config.port + " ... ");
|
||||
|
||||
server.listen(config.port, config.address ? config.address : null);
|
||||
var port = config.port;
|
||||
if (process.env.MM_PORT) {
|
||||
port = process.env.MM_PORT;
|
||||
}
|
||||
|
||||
console.log("Starting server on port " + port + " ... ");
|
||||
|
||||
server.listen(port, config.address ? config.address : null);
|
||||
|
||||
if (config.ipWhitelist instanceof Array && config.ipWhitelist.length == 0) {
|
||||
console.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: "allow", log: false})(req, res, function(err) {
|
||||
var result = ipfilter(config.ipWhitelist, {mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false})(req, res, function(err) {
|
||||
if (err === undefined) {
|
||||
return next();
|
||||
}
|
||||
@@ -31,21 +42,31 @@ var Server = function(config, callback) {
|
||||
app.use(helmet());
|
||||
|
||||
app.use("/js", express.static(__dirname));
|
||||
app.use("/config", express.static(path.resolve(global.root_path + "/config")));
|
||||
app.use("/css", express.static(path.resolve(global.root_path + "/css")));
|
||||
app.use("/fonts", express.static(path.resolve(global.root_path + "/fonts")));
|
||||
app.use("/modules", express.static(path.resolve(global.root_path + "/modules")));
|
||||
app.use("/vendor", express.static(path.resolve(global.root_path + "/vendor")));
|
||||
app.use("/translations", express.static(path.resolve(global.root_path + "/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("/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);
|
||||
|
||||
configFile = "config/config.js";
|
||||
if (typeof(global.configuration_file) !== "undefined") {
|
||||
configFile = global.configuration_file;
|
||||
}
|
||||
html = html.replace("#CONFIG_FILE#", configFile);
|
||||
|
||||
res.send(html);
|
||||
});
|
||||
|
||||
|
@@ -22,7 +22,6 @@ var MMSocket = function(moduleName) {
|
||||
// register catch all.
|
||||
self.socket.on("*", function(notification, payload) {
|
||||
if (notification !== "*") {
|
||||
//console.log('Received notification: ' + notification +', payload: ' + payload);
|
||||
notificationCallback(notification, payload);
|
||||
}
|
||||
});
|
||||
|
@@ -111,41 +111,60 @@ 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
|
||||
|
||||
// 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) {
|
||||
@@ -201,10 +220,12 @@ var Translator = (function() {
|
||||
// defined translation after the following line.
|
||||
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 @@
|
||||
/* exported Utils */
|
||||
/* 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;}
|
31
module-types.ts
Normal file
31
module-types.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
type ModuleProperties = {
|
||||
defaults?: object,
|
||||
start?(): void,
|
||||
getHeader?(): string,
|
||||
getTemplate?(): string,
|
||||
getTemplateData?(): object,
|
||||
notificationReceived?(notification: string, payload: any, sender: object): void,
|
||||
socketNotificationReceived?(notification: string, payload: any): void,
|
||||
suspend?(): void,
|
||||
resume?(): void,
|
||||
getDom?(): HTMLElement,
|
||||
getStyles?(): string[],
|
||||
[key: string]: any,
|
||||
};
|
||||
|
||||
export declare const Module: {
|
||||
register(moduleName: string, moduleProperties: ModuleProperties): void;
|
||||
};
|
||||
|
||||
export declare const Log: {
|
||||
info(message?: any, ...optionalParams: any[]): void,
|
||||
log(message?: any, ...optionalParams: any[]): void,
|
||||
error(message?: any, ...optionalParams: any[]): void,
|
||||
warn(message?: any, ...optionalParams: any[]): void,
|
||||
group(groupTitle?: string, ...optionalParams: any[]): void,
|
||||
groupCollapsed(groupTitle?: string, ...optionalParams: any[]): void,
|
||||
groupEnd(): void,
|
||||
time(timerName?: string): void,
|
||||
timeEnd(timerName?: string): void,
|
||||
timeStamp(timerName?: string): void,
|
||||
};
|
@@ -2,6 +2,42 @@
|
||||
|
||||
This document describes the way to develop your own MagicMirror² modules.
|
||||
|
||||
Table of Contents:
|
||||
|
||||
- Module structure
|
||||
- Files
|
||||
|
||||
- The Core module file: modulename.js
|
||||
- Available module instance properties
|
||||
- Subclassable module methods
|
||||
- Module instance methods
|
||||
- Visibility locking
|
||||
|
||||
- The Node Helper: node_helper.js
|
||||
- Available module instance properties
|
||||
- Subclassable module methods
|
||||
- Module instance methods
|
||||
|
||||
- MagicMirror Helper Methods
|
||||
- Module Selection
|
||||
|
||||
- MagicMirror Logger
|
||||
|
||||
---
|
||||
|
||||
|
||||
## General Advice
|
||||
|
||||
As MagicMirror has gained huge popularity, so has the number of available modules. For new users and developers alike, it is very time consuming to navigate around the various repositories in order to find out what exactly a certain modules does, how it looks and what it depends on. Unfortunately, this information is rarely available, nor easily obtained without having to install it first.
|
||||
Therefore **we highly recommend you to include the following information in your README file.**
|
||||
|
||||
- A high quality screenshot of your working module
|
||||
- A short, one sentence, clear description what it does (duh!)
|
||||
- What external API's it depend on, including web links to those
|
||||
- Wheteher the API/request require a key and the user limitations of those. (Is it free?)
|
||||
|
||||
Surely this also help you get better recognition and feedback for your work.
|
||||
|
||||
## Module structure
|
||||
|
||||
All modules are loaded in the `modules` folder. The default modules are grouped together in the `modules/default` folder. Your module should be placed in a subfolder of `modules`. Note that any file or folder your create in the `modules` folder will be ignored by git, allowing you to upgrade the MagicMirror² without the loss of your files.
|
||||
@@ -14,7 +50,7 @@ A module can be placed in one single folder. Or multiple modules can be grouped
|
||||
- **modulename/public** - Any files in this folder can be accesed via the browser on `/modulename/filename.ext`.
|
||||
- **modulename/anyfileorfolder** Any other file or folder in the module folder can be used by the core module script. For example: *modulename/css/modulename.css* would be a good path for your additional module styles.
|
||||
|
||||
## Core module file: modulename.js
|
||||
## The Core module file: modulename.js
|
||||
This is the script in which the module will be defined. This script is required in order for the module to be used. In it's most simple form, the core module file must contain:
|
||||
````javascript
|
||||
Module.register("modulename",{});
|
||||
@@ -44,30 +80,16 @@ As you can see, the `Module.register()` method takes two arguments: the name of
|
||||
### Available module instance properties
|
||||
After the module is initialized, the module instance has a few available module properties:
|
||||
|
||||
####`this.name`
|
||||
**String**
|
||||
| Instance Property | Type | Description |
|
||||
|:----------------- |:---- |:----------- |
|
||||
| `this.name` | String | The name of the module. |
|
||||
| `this.identifier` | String | This is a unique identifier for the module instance. |
|
||||
| `this.hidden` | Boolean | This represents if the module is currently hidden (faded away). |
|
||||
| `this.config` | Boolean | The configuration of the module instance as set in the user's `config.js` file. This config will also contain the module's defaults if these properties are not over-written by the user config. |
|
||||
| `this.data` | Object | The data object contain additional metadata about the module instance. (See below) |
|
||||
|
||||
The name of the module.
|
||||
|
||||
####`this.identifier`
|
||||
**String**
|
||||
|
||||
This is a unique identifier for the module instance.
|
||||
|
||||
####`this.hidden`
|
||||
**Boolean**
|
||||
|
||||
This represents if the module is currently hidden (faded away).
|
||||
|
||||
####`this.config`
|
||||
**Boolean**
|
||||
|
||||
The configuration of the module instance as set in the user's config.js file. This config will also contain the module's defaults if these properties are not over written by the user config.
|
||||
|
||||
####`this.data`
|
||||
**Object**
|
||||
|
||||
The data object contains additional metadata about the module instance:
|
||||
The `this.data` data object contain the follwoing metadata:
|
||||
- `data.classes` - The classes which are added to the module dom wrapper.
|
||||
- `data.file` - The filename of the core module file.
|
||||
- `data.path` - The path of the module folder.
|
||||
@@ -75,10 +97,10 @@ The data object contains additional metadata about the module instance:
|
||||
- `data.position` - The position in which the instance will be shown.
|
||||
|
||||
|
||||
####`defaults: {}`
|
||||
#### `defaults: {}`
|
||||
Any properties defined in the defaults object, will be merged with the module config as defined in the user's config.js file. This is the best place to set your modules's configuration defaults. Any of the module configuration properties can be accessed using `this.config.propertyName`, but more about that later.
|
||||
|
||||
####'requiresVersion:'
|
||||
#### `requiresVersion:`
|
||||
|
||||
*Introduced in version: 2.1.0.*
|
||||
|
||||
@@ -93,10 +115,25 @@ requiresVersion: "2.1.0",
|
||||
|
||||
### Subclassable module methods
|
||||
|
||||
####`init()`
|
||||
#### `init()`
|
||||
This method is called when a module gets instantiated. In most cases you do not need to subclass this method.
|
||||
|
||||
####`start()`
|
||||
#### `loaded(callback)`
|
||||
|
||||
*Introduced in version: 2.1.1.*
|
||||
|
||||
This method is called when a module is loaded. Subsequent modules in the config are not yet loaded. The `callback` function MUST be called when the module is done loading. In most cases you do not need to subclass this method.
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
loaded: function(callback) {
|
||||
this.finishLoading();
|
||||
Log.log(this.name + ' is loaded!');
|
||||
callback();
|
||||
}
|
||||
````
|
||||
|
||||
#### `start()`
|
||||
This method is called when all modules are loaded an the system is ready to boot up. Keep in mind that the dom object for the module is not yet created. The start method is a perfect place to define any additional module properties:
|
||||
|
||||
**Example:**
|
||||
@@ -107,7 +144,7 @@ start: function() {
|
||||
}
|
||||
````
|
||||
|
||||
####`getScripts()`
|
||||
#### `getScripts()`
|
||||
**Should return: Array**
|
||||
|
||||
The getScripts method is called to request any additional scripts that need to be loaded. This method should therefore return an array with strings. If you want to return a full path to a file in the module folder, use the `this.file('filename.js')` method. In all cases the loader will only load a file once. It even checks if the file is available in the default vendor folder.
|
||||
@@ -127,7 +164,7 @@ getScripts: function() {
|
||||
**Note:** If a file can not be loaded, the boot up of the mirror will stall. Therefore it's advised not to use any external urls.
|
||||
|
||||
|
||||
####`getStyles()`
|
||||
#### `getStyles()`
|
||||
**Should return: Array**
|
||||
|
||||
The getStyles method is called to request any additional stylesheets that need to be loaded. This method should therefore return an array with strings. If you want to return a full path to a file in the module folder, use the `this.file('filename.css')` method. In all cases the loader will only load a file once. It even checks if the file is available in the default vendor folder.
|
||||
@@ -146,7 +183,7 @@ getStyles: function() {
|
||||
````
|
||||
**Note:** If a file can not be loaded, the boot up of the mirror will stall. Therefore it's advised not to use any external urls.
|
||||
|
||||
####`getTranslations()`
|
||||
#### `getTranslations()`
|
||||
**Should return: Dictionary**
|
||||
|
||||
The getTranslations method is called to request translation files that need to be loaded. This method should therefore return a dictionary with the files to load, identified by the country's short name.
|
||||
@@ -164,7 +201,7 @@ getTranslations: function() {
|
||||
|
||||
````
|
||||
|
||||
####`getDom()`
|
||||
#### `getDom()`
|
||||
**Should return:** Dom Object
|
||||
|
||||
Whenever the MagicMirror needs to update the information on screen (because it starts, or because your module asked a refresh using `this.updateDom()`), the system calls the getDom method. This method should therefore return a dom object.
|
||||
@@ -179,7 +216,7 @@ getDom: function() {
|
||||
|
||||
````
|
||||
|
||||
####`getHeader()`
|
||||
#### `getHeader()`
|
||||
**Should return:** String
|
||||
|
||||
Whenever the MagicMirror needs to update the information on screen (because it starts, or because your module asked a refresh using `this.updateDom()`), the system calls the getHeader method to retrieve the module's header. This method should therefor return a string. If this method is not subclassed, this function will return the user's configured header.
|
||||
@@ -196,7 +233,7 @@ getHeader: function() {
|
||||
|
||||
````
|
||||
|
||||
####`notificationReceived(notification, payload, sender)`
|
||||
#### `notificationReceived(notification, payload, sender)`
|
||||
|
||||
That MagicMirror core has the ability to send notifications to modules. Or even better: the modules have the possibility to send notifications to other modules. When this module is called, it has 3 arguments:
|
||||
|
||||
@@ -215,14 +252,15 @@ notificationReceived: function(notification, payload, sender) {
|
||||
}
|
||||
````
|
||||
|
||||
**Note:** the system sends two notifications when starting up. These notifications could come in handy!
|
||||
**Note:** the system sends three notifications when starting up. These notifications could come in handy!
|
||||
|
||||
|
||||
- `ALL_MODULES_STARTED` - All modules are started. You can now send notifications to other modules.
|
||||
- `DOM_OBJECTS_CREATED` - All dom objects are created. The system is now ready to perform visual changes.
|
||||
- `MODULE_DOM_CREATED` - This module's dom has been fully loaded. You can now access your module's dom objects.
|
||||
|
||||
|
||||
####`socketNotificationReceived: function(notification, payload)`
|
||||
#### `socketNotificationReceived: function(notification, payload)`
|
||||
When using a node_helper, the node helper can send your module notifications. When this module is called, it has 2 arguments:
|
||||
|
||||
- `notification` - String - The notification identifier.
|
||||
@@ -238,11 +276,11 @@ socketNotificationReceived: function(notification, payload) {
|
||||
},
|
||||
````
|
||||
|
||||
####`suspend()`
|
||||
#### `suspend()`
|
||||
When a module is hidden (using the `module.hide()` method), the `suspend()` method will be called. By subclassing this method you can perform tasks like halting the update timers.
|
||||
|
||||
####`resume()`
|
||||
When a module will be shown after it was previously hidden (using the `module.show()` method), the `resume()` method will be called. By subclassing this method you can perform tasks restarting the update timers.
|
||||
#### `resume()`
|
||||
When a module is requested to be shown (using the `module.show()` method), the `resume()` method will be called. By subclassing this method you can perform tasks restarting the update timers.
|
||||
|
||||
|
||||
### Module instance methods
|
||||
@@ -250,13 +288,13 @@ When a module will be shown after it was previously hidden (using the `module.sh
|
||||
Each module instance has some handy methods which can be helpful building your module.
|
||||
|
||||
|
||||
####`this.file(filename)`
|
||||
#### `this.file(filename)`
|
||||
***filename* String** - The name of the file you want to create the path for.<br>
|
||||
**Returns String**
|
||||
|
||||
If you want to create a path to a file in your module folder, use the `file()` method. It returns the path to the filename given as the attribute. Is method comes in handy when configuring the [getScripts](#getscripts) and [getStyles](#getstyles) methods.
|
||||
|
||||
####`this.updateDom(speed)`
|
||||
#### `this.updateDom(speed)`
|
||||
***speed* Number** - Optional. Animation speed in milliseconds.<br>
|
||||
|
||||
Whenever your module need to be updated, call the `updateDom(speed)` method. It requests the MagicMirror core to update its dom object. If you define the speed, the content update will be animated, but only if the content will really change.
|
||||
@@ -274,7 +312,7 @@ start: function() {
|
||||
...
|
||||
````
|
||||
|
||||
####`this.sendNotification(notification, payload)`
|
||||
#### `this.sendNotification(notification, payload)`
|
||||
***notification* String** - The notification identifier.<br>
|
||||
***payload* AnyType** - Optional. A notification payload.<br>
|
||||
|
||||
@@ -285,7 +323,7 @@ If you want to send a notification to all other modules, use the `sendNotificati
|
||||
this.sendNotification('MYMODULE_READY_FOR_ACTION', {foo:bar});
|
||||
````
|
||||
|
||||
####`this.sendSocketNotification(notification, payload)`
|
||||
#### `this.sendSocketNotification(notification, payload)`
|
||||
***notification* String** - The notification identifier.<br>
|
||||
***payload* AnyType** - Optional. A notification payload.<br>
|
||||
|
||||
@@ -296,7 +334,7 @@ If you want to send a notification to the node_helper, use the `sendSocketNotifi
|
||||
this.sendSocketNotification('SET_CONFIG', this.config);
|
||||
````
|
||||
|
||||
####`this.hide(speed, callback, options)`
|
||||
#### `this.hide(speed, callback, options)`
|
||||
***speed* Number** - Optional (Required when setting callback or options), The speed of the hide animation in milliseconds.
|
||||
***callback* Function** - Optional, The callback after the hide animation is finished.
|
||||
***options* Function** - Optional, Object with additional options for the hide action (see below). (*Introduced in version: 2.1.0.*)
|
||||
@@ -313,7 +351,7 @@ Possible configurable options:
|
||||
**Note 3:** If the dom is not yet created, the hide method won't work. Wait for the `DOM_OBJECTS_CREATED` [notification](#notificationreceivednotification-payload-sender).
|
||||
|
||||
|
||||
####`this.show(speed, callback, options)`
|
||||
#### `this.show(speed, callback, options)`
|
||||
***speed* Number** - Optional (Required when setting callback or options), The speed of the show animation in milliseconds.
|
||||
***callback* Function** - Optional, The callback after the show animation is finished.
|
||||
***options* Function** - Optional, Object with additional options for the show action (see below). (*Introduced in version: 2.1.0.*)
|
||||
@@ -329,7 +367,7 @@ Possible configurable options:
|
||||
**Note 2:** If the show animation is hijacked (an other method calls show on the same module), the callback will not be called.<br>
|
||||
**Note 3:** If the dom is not yet created, the show method won't work. Wait for the `DOM_OBJECTS_CREATED` [notification](#notificationreceivednotification-payload-sender).
|
||||
|
||||
####Visibility locking
|
||||
#### Visibility locking
|
||||
|
||||
(*Introduced in version: 2.1.0.*)
|
||||
|
||||
@@ -386,7 +424,7 @@ Use this `force` method with caution. See `show()` method for more information.
|
||||
|
||||
|
||||
|
||||
####`this.translate(identifier)`
|
||||
#### `this.translate(identifier)`
|
||||
***identifier* String** - Identifier of the string that should be translated.
|
||||
|
||||
The Magic Mirror contains a convenience wrapper for `l18n`. You can use this to automatically serve different translations for your modules based on the user's `language` configuration.
|
||||
@@ -414,6 +452,51 @@ this.translate("INFO") //Will return a translated string for the identifier INFO
|
||||
|
||||
**Note:** although comments are officially not supported in JSON files, MagicMirror allows it by stripping the comments before parsing the JSON file. Comments in translation files could help other translators.
|
||||
|
||||
##### `this.translate(identifier, variables)`
|
||||
***identifier* String** - Identifier of the string that should be translated.
|
||||
***variables* Object** - Object of variables to be used in translation.
|
||||
|
||||
This improved and backwards compatible way to handle translations behaves like the normal translation function and follows the rules described above. It's recommended to use this new format for translating everywhere. It allows translator to change the word order in the sentence to be translated.
|
||||
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
var timeUntilEnd = moment(event.endDate, "x").fromNow(true);
|
||||
this.translate("RUNNING", { "timeUntilEnd": timeUntilEnd) }); // Will return a translated string for the identifier RUNNING, replacing `{timeUntilEnd}` with the contents of the variable `timeUntilEnd` in the order that translator intended.
|
||||
````
|
||||
|
||||
**Example English .json file:**
|
||||
````javascript
|
||||
{
|
||||
"RUNNING": "Ends in {timeUntilEnd}",
|
||||
}
|
||||
````
|
||||
|
||||
**Example Finnish .json file:**
|
||||
````javascript
|
||||
{
|
||||
"RUNNING": "Päättyy {timeUntilEnd} päästä",
|
||||
}
|
||||
````
|
||||
|
||||
**Note:** The *variables* Object has an special case called `fallback`. It's used to support old translations in translation files that do not have the variables in them. If you are upgrading an old module that had translations that did not support the word order, it is recommended to have the fallback layout.
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
var timeUntilEnd = moment(event.endDate, "x").fromNow(true);
|
||||
this.translate("RUNNING", {
|
||||
"fallback": this.translate("RUNNING") + " {timeUntilEnd}"
|
||||
"timeUntilEnd": timeUntilEnd
|
||||
)}); // Will return a translated string for the identifier RUNNING, replacing `{timeUntilEnd}` with the contents of the variable `timeUntilEnd` in the order that translator intended. (has a fallback)
|
||||
````
|
||||
|
||||
**Example swedish .json file that does not have the variable in it:**
|
||||
````javascript
|
||||
{
|
||||
"RUNNING": "Slutar",
|
||||
}
|
||||
````
|
||||
In this case the `translate`-function will not find any variables in the translation, will look for `fallback` variable and use that if possible to create the translation.
|
||||
|
||||
## The Node Helper: node_helper.js
|
||||
|
||||
@@ -432,17 +515,17 @@ Of course, the above helper would not do anything useful. So with the informatio
|
||||
|
||||
### Available module instance properties
|
||||
|
||||
####`this.name`
|
||||
#### `this.name`
|
||||
**String**
|
||||
|
||||
The name of the module
|
||||
|
||||
####`this.path`
|
||||
#### `this.path`
|
||||
**String**
|
||||
|
||||
The path of the module
|
||||
|
||||
####`this.expressApp`
|
||||
#### `this.expressApp`
|
||||
**Express App Instance**
|
||||
|
||||
This is a link to the express instance. It will allow you to define extra routes.
|
||||
@@ -461,13 +544,13 @@ start: function() {
|
||||
this.expressApp.use("/" + this.name, express.static(this.path + "/public"));
|
||||
````
|
||||
|
||||
####`this.io`
|
||||
#### `this.io`
|
||||
**Socket IO Instance**
|
||||
|
||||
This is a link to the IO instance. It will allow you to do some Socket.IO magic. In most cases you won't need this, since the Node Helper has a few convenience methods to make this simple.
|
||||
|
||||
|
||||
####'requiresVersion:'
|
||||
#### `requiresVersion:`
|
||||
*Introduced in version: 2.1.0.*
|
||||
|
||||
A string that defines the minimum version of the MagicMirror framework. If it is set, the system compares the required version with the users version. If the version of the user is out of date, it won't run the module.
|
||||
@@ -481,10 +564,10 @@ requiresVersion: "2.1.0",
|
||||
|
||||
### Subclassable module methods
|
||||
|
||||
####`init()`
|
||||
#### `init()`
|
||||
This method is called when a node helper gets instantiated. In most cases you do not need to subclass this method.
|
||||
|
||||
####`start()`
|
||||
#### `start()`
|
||||
This method is called when all node helpers are loaded and the system is ready to boot up. The start method is a perfect place to define any additional module properties:
|
||||
|
||||
**Example:**
|
||||
@@ -495,7 +578,18 @@ start: function() {
|
||||
}
|
||||
````
|
||||
|
||||
####`socketNotificationReceived: function(notification, payload)`
|
||||
#### `stop()`
|
||||
This method is called when the MagicMirror server receives a `SIGINT` command and is shutting down. This method should include any commands needed to close any open connections, stop any sub-processes and gracefully exit the module.
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
stop: function() {
|
||||
console.log("Shutting down MyModule");
|
||||
this.connection.close();
|
||||
}
|
||||
````
|
||||
|
||||
#### `socketNotificationReceived: function(notification, payload)`
|
||||
With this method, your node helper can receive notifications from your modules. When this method is called, it has 2 arguments:
|
||||
|
||||
- `notification` - String - The notification identifier.
|
||||
@@ -514,7 +608,7 @@ socketNotificationReceived: function(notification, payload) {
|
||||
|
||||
Each node helper has some handy methods which can be helpful building your module.
|
||||
|
||||
####`this.sendSocketNotification(notification, payload)`
|
||||
#### `this.sendSocketNotification(notification, payload)`
|
||||
***notification* String** - The notification identifier.<br>
|
||||
***payload* AnyType** - Optional. A notification payload.<br>
|
||||
|
||||
@@ -534,7 +628,7 @@ The core Magic Mirror object: `MM` has some handy method that will help you in c
|
||||
### Module selection
|
||||
The only additional method available for your module, is the feature to retrieve references to other modules. This can be used to hide and show other modules.
|
||||
|
||||
####`MM.getModules()`
|
||||
#### `MM.getModules()`
|
||||
**Returns Array** - An array with module instances.<br>
|
||||
|
||||
To make a selection of all currently loaded module instances, run the `MM.getModules()` method. It will return an array with all currently loaded module instances. The returned array has a lot of filtering methods. See below for more info.
|
||||
@@ -542,7 +636,7 @@ To make a selection of all currently loaded module instances, run the `MM.getMod
|
||||
**Note:** This method returns an empty array if not all modules are started yet. Wait for the `ALL_MODULES_STARTED` [notification](#notificationreceivednotification-payload-sender).
|
||||
|
||||
|
||||
#####`.withClass(classnames)`
|
||||
##### `.withClass(classnames)`
|
||||
***classnames* String or Array** - The class names on which you want to filter.
|
||||
**Returns Array** - An array with module instances.<br>
|
||||
|
||||
@@ -555,7 +649,7 @@ var modules = MM.getModules().withClass('classname1 classname2');
|
||||
var modules = MM.getModules().withClass(['classname1','classname2']);
|
||||
````
|
||||
|
||||
#####`.exceptWithClass(classnames)`
|
||||
##### `.exceptWithClass(classnames)`
|
||||
***classnames* String or Array** - The class names of the modules you want to remove from the results.
|
||||
**Returns Array** - An array with module instances.<br>
|
||||
|
||||
@@ -568,7 +662,7 @@ var modules = MM.getModules().exceptWithClass('classname1 classname2');
|
||||
var modules = MM.getModules().exceptWithClass(['classname1','classname2']);
|
||||
````
|
||||
|
||||
#####`.exceptModule(module)`
|
||||
##### `.exceptModule(module)`
|
||||
***module* Module Object** - The reference to a module you want to remove from the results.
|
||||
**Returns Array** - An array with module instances.<br>
|
||||
|
||||
@@ -586,7 +680,7 @@ Of course, you can combine all of the above filters:
|
||||
var modules = MM.getModules().withClass('classname1').exceptwithClass('classname2').exceptModule(aModule);
|
||||
````
|
||||
|
||||
#####`.enumerate(callback)`
|
||||
##### `.enumerate(callback)`
|
||||
***callback* Function(module)** - The callback run on every instance.
|
||||
|
||||
If you want to perform an action on all selected modules, you can use the `enumerate` function:
|
||||
|
@@ -7,10 +7,10 @@ To use this module, add it to the modules array in the config/config.js file:
|
||||
```
|
||||
modules: [
|
||||
{
|
||||
module: 'alert',
|
||||
module: "alert",
|
||||
config: {
|
||||
// The config property is optional.
|
||||
// See 'Configuration options' for more information.
|
||||
// See 'Configuration options' for more information.
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -21,145 +21,44 @@ modules: [
|
||||
The following properties can be configured:
|
||||
|
||||
|
||||
<table width="100%">
|
||||
<!-- why, markdown... -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th width="100%">Description</th>
|
||||
</tr>
|
||||
<thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>effect</code></td>
|
||||
<td>The animation effect to use for notifications.<br>
|
||||
<br><b>Possible values:</b> <code>scale</code> <code>slide</code> <code>genie</code> <code>jelly</code> <code>flip</code> <code>exploader</code> <code>bouncyflip</code>
|
||||
<br><b>Default value:</b> <code>slide</code>
|
||||
</td>
|
||||
</tr>
|
||||
<td><code>alert_effect</code></td>
|
||||
<td>The animation effect to use for alerts.<br>
|
||||
<br><b>Possible values:</b> <code>scale</code> <code>slide</code> <code>genie</code> <code>jelly</code> <code>flip</code> <code>exploader</code> <code>bouncyflip</code>
|
||||
<br><b>Default value:</b> <code>jelly</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>display_time</code></td>
|
||||
<td>Time a notification is displayed in milliseconds.<br>
|
||||
<br><b>Possible values:</b> <code>int</code>
|
||||
<br><b>Default value:</b> <code>3500</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td><code>position</code></td>
|
||||
<td>Position where the notifications should be displayed.<br>
|
||||
<br><b>Possible values:</b> <code>left</code> <code>center</code> <code>right</code>
|
||||
<br><b>Default value:</b> <code>center</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>welcome_message</code></td>
|
||||
<td>Message shown at startup.<br>
|
||||
<br><b>Possible values:</b> <code>string</code> <code>false</code>
|
||||
<br><b>Default value:</b> <code>false</code> (no message at startup)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
| Option | Description
|
||||
| ----------------- | -----------
|
||||
| `effect` | The animation effect to use for notifications. <br><br> **Possible values:** `scale` `slide` `genie` `jelly` `flip` `exploader` `bouncyflip` <br> **Default value:** `slide`
|
||||
| `alert_effect` | The animation effect to use for alerts. <br><br> **Possible values:** `scale` `slide` `genie` `jelly` `flip` `exploader` `bouncyflip` <br> **Default value:** `jelly`
|
||||
| `display_time` | Time a notification is displayed in milliseconds. <br><br> **Possible values:** `int` <br> **Default value:** `3500`
|
||||
| `position` | Position where the notifications should be displayed. <br><br> **Possible values:** `left` `center` `right` <br> **Default value:** `center`
|
||||
| `welcome_message` | Message shown at startup. <br><br> **Possible values:** `string` `false` <br> **Default value:** `false` (no message at startup)
|
||||
|
||||
|
||||
## Developer notes
|
||||
For notifications use:
|
||||
|
||||
```
|
||||
self.sendNotification("SHOW_ALERT", {type: "notification"});
|
||||
self.sendNotification("SHOW_ALERT", {type: "notification"});
|
||||
```
|
||||
For alerts use:
|
||||
|
||||
```
|
||||
self.sendNotification("SHOW_ALERT", {});
|
||||
self.sendNotification("SHOW_ALERT", {});
|
||||
```
|
||||
|
||||
### Notification params
|
||||
<table width="100%">
|
||||
<!-- why, markdown... -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th width="100%">Description</th>
|
||||
</tr>
|
||||
<thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>title</code></td>
|
||||
<td>The title of the notification.<br>
|
||||
<br><b>Possible values:</b> <code>text</code> or <code>html</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>message</code></td>
|
||||
<td>The message of the notification.<br>
|
||||
<br><b>Possible values:</b> <code>text</code> or <code>html</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
| Option | Description
|
||||
| --------- | -----------
|
||||
| `title` | The title of the notification. <br><br> **Possible values:** `text` or `html`
|
||||
| `message` | The message of the notification. <br><br> **Possible values:** `text` or `html`
|
||||
|
||||
|
||||
### Alert params
|
||||
<table width="100%">
|
||||
<!-- why, markdown... -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th width="100%">Description</th>
|
||||
</tr>
|
||||
<thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>title</code></td>
|
||||
<td>The title of the alert.<br>
|
||||
<br><b>Possible values:</b> <code>text</code> or <code>html</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>message</code></td>
|
||||
<td>The message of the alert.<br>
|
||||
<br><b>Possible values:</b> <code>text</code> or <code>html</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>imageUrl</code> (optional)</td>
|
||||
<td>Image to show in the alert<br>
|
||||
<br><b>Possible values:</b> <code>url</code> <code>path</code>
|
||||
<br><b>Default value:</b> <code>none</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>imageFA</code> (optional)</td>
|
||||
<td>Font Awesome icon to show in the alert<br>
|
||||
<br><b>Possible values:</b> See <a href="http://fontawesome.io/icons/" target="_blank">Font Awsome</a> website.
|
||||
<br><b>Default value:</b> <code>none</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>imageHeight</code> (optional even with imageUrl set)</td>
|
||||
<td>Height of the image<br>
|
||||
<br><b>Possible values:</b> <code>intpx</code>
|
||||
<br><b>Default value:</b> <code>80px</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>timer</code> (optional)</td>
|
||||
<td>How long the alert should stay visible in ms.
|
||||
<br><b>Important:</b> If you do not use the <code>timer</code>, it is your duty to hide the alert by using <code>self.sendNotification("HIDE_ALERT");</code>!<br>
|
||||
<br><b>Possible values:</b> <code>int</code> <code>float</code>
|
||||
<br><b>Default value:</b> <code>none</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
| Option | Description
|
||||
| ----------------------------------------------- | -----------
|
||||
| `title` | The title of the alert. <br><br> **Possible values:** `text` or `html`
|
||||
| `message` | The message of the alert. <br><br> **Possible values:** `text` or `html`
|
||||
| `imageUrl` (optional) | Image to show in the alert <br><br> **Possible values:** `url` `path` <br> **Default value:** `none`
|
||||
| `imageFA` (optional) | Font Awesome icon to show in the alert <br><br> **Possible values:** See [Font Awsome](http://fontawesome.io/icons/) website. <br> **Default value:** `none`
|
||||
| `imageHeight` (optional even with imageUrl set) | Height of the image <br><br> **Possible values:** `intpx` <br> **Default value:** `80px`
|
||||
| `timer` (optional) | How long the alert should stay visible in ms. <br> **Important:** If you do not use the `timer`, it is your duty to hide the alert by using `self.sendNotification("HIDE_ALERT");`! <br><br>**Possible values:** `int` `float` <br> **Default value:** `none`
|
||||
|
||||
## Open Source Licenses
|
||||
###[NotificationStyles](https://github.com/codrops/NotificationStyles)
|
||||
### [NotificationStyles](https://github.com/codrops/NotificationStyles)
|
||||
See [ympanus.net](http://tympanus.net/codrops/licensing/) for license.
|
||||
|
@@ -30,7 +30,8 @@ Module.register("alert",{
|
||||
getTranslations: function() {
|
||||
return {
|
||||
en: "translations/en.json",
|
||||
de: "translations/de.json"
|
||||
de: "translations/de.json",
|
||||
nl: "translations/nl.json",
|
||||
};
|
||||
},
|
||||
show_notification: function(message) {
|
||||
|
4
modules/default/alert/translations/bg.json
Normal file
4
modules/default/alert/translations/bg.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror нотификация",
|
||||
"welcome": "Добре дошли, стартирането беше успешно"
|
||||
}
|
4
modules/default/alert/translations/hu.json
Normal file
4
modules/default/alert/translations/hu.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror értesítés",
|
||||
"welcome": "Üdvözöljük, indulás sikeres!"
|
||||
}
|
4
modules/default/alert/translations/nl.json
Normal file
4
modules/default/alert/translations/nl.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror Notificatie",
|
||||
"welcome": "Welkom, Succesvol gestart!"
|
||||
}
|
4
modules/default/alert/translations/ru.json
Normal file
4
modules/default/alert/translations/ru.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror Уведомление",
|
||||
"welcome": "Добро пожаловать, старт был успешным!"
|
||||
}
|
@@ -8,8 +8,8 @@ To use this module, add it to the modules array in the `config/config.js` file:
|
||||
````javascript
|
||||
modules: [
|
||||
{
|
||||
module: 'calendar',
|
||||
position: 'top_left', // This can be any of the regions. Best results in left or right regions.
|
||||
module: "calendar",
|
||||
position: "top_left", // This can be any of the regions. Best results in left or right regions.
|
||||
config: {
|
||||
// The config property is optional.
|
||||
// If no config is set, an example calendar is shown.
|
||||
@@ -24,206 +24,68 @@ modules: [
|
||||
The following properties can be configured:
|
||||
|
||||
|
||||
<table width="100%">
|
||||
<!-- why, markdown... -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th width="100%">Description</th>
|
||||
</tr>
|
||||
<thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>maximumEntries</code></td>
|
||||
<td>The maximum number of events shown.<br>
|
||||
<br><b>Possible values:</b> <code>0</code> - <code>100</code>
|
||||
<br><b>Default value:</b> <code>10</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>maximumNumberOfDays</code></td>
|
||||
<td>The maximum number of days in the future.<br>
|
||||
<br><b>Default value:</b> <code>365</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>displaySymbol</code></td>
|
||||
<td>Display a symbol in front of an entry.<br>
|
||||
<br><b>Possible values:</b> <code>true</code> or <code>false</code>
|
||||
<br><b>Default value:</b> <code>true</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>defaultSymbol</code></td>
|
||||
<td>The default symbol.<br>
|
||||
<br><b>Possible values:</b> See <a href="http://fontawesome.io/icons/" target="_blank">Font Awsome</a> website.
|
||||
<br><b>Default value:</b> <code>calendar</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>maxTitleLength</code></td>
|
||||
<td>The maximum title length.<br>
|
||||
<br><b>Possible values:</b> <code>10</code> - <code>50</code>
|
||||
<br><b>Default value:</b> <code>25</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fetchInterval</code></td>
|
||||
<td>How often does the content needs to be fetched? (Milliseconds)<br>
|
||||
<br><b>Possible values:</b> <code>1000</code> - <code>86400000</code>
|
||||
<br><b>Default value:</b> <code>300000</code> (5 minutes)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>animationSpeed</code></td>
|
||||
<td>Speed of the update animation. (Milliseconds)<br>
|
||||
<br><b>Possible values:</b><code>0</code> - <code>5000</code>
|
||||
<br><b>Default value:</b> <code>2000</code> (2 seconds)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fade</code></td>
|
||||
<td>Fade the future events to black. (Gradient)<br>
|
||||
<br><b>Possible values:</b> <code>true</code> or <code>false</code>
|
||||
<br><b>Default value:</b> <code>true</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fadePoint</code></td>
|
||||
<td>Where to start fade?<br>
|
||||
<br><b>Possible values:</b> <code>0</code> (top of the list) - <code>1</code> (bottom of list)
|
||||
<br><b>Default value:</b> <code>0.25</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>calendars</code></td>
|
||||
<td>The list of calendars.<br>
|
||||
<br><b>Possible values:</b> An array, see <i>calendar configuration</i> below.
|
||||
<br><b>Default value:</b> <i>An example calendar.</i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>titleReplace</code></td>
|
||||
<td>An object of textual replacements applied to the tile of the event. This allow to remove or replace certains words in the title.<br>
|
||||
<br><b>Example:</b> <br>
|
||||
<code>
|
||||
titleReplace: {'Birthday of ' : '', 'foo':'bar'}
|
||||
</code>
|
||||
<br><b>Default value:</b>
|
||||
<code>
|
||||
{
|
||||
"De verjaardag van ": "",
|
||||
"'s birthday": ""
|
||||
}
|
||||
</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>displayRepeatingCountTitle</code></td>
|
||||
<td>Show count title for yearly repeating events (e.g. "X. Birthday", "X. Anniversary")<br>
|
||||
<br><b>Possible values:</b> <code>true</code> or <code>false</code>
|
||||
<br><b>Default value:</b> <code>false</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>dateFormat</code></td>
|
||||
<td>Format to use for the date of events (when using absolute dates)<br>
|
||||
<br><b>Possible values:</b> See <a href="http://momentjs.com/docs/#/parsing/string-format/">Moment.js formats</a>
|
||||
<br><b>Default value:</b> <code>MMM Do</code> (e.g. Jan 18th)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>timeFormat</code></td>
|
||||
<td>Display event times as absolute dates, or relative time<br>
|
||||
<br><b>Possible values:</b> <code>absolute</code> or <code>relative</code>
|
||||
<br><b>Default value:</b> <code>relative</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>getRelative</code></td>
|
||||
<td>How much time (in hours) should be left until calendar events start getting relative?<br>
|
||||
<br><b>Possible values:</b> <code>0</code> (events stay absolute) - <code>48</code> (48 hours before the event starts)
|
||||
<br><b>Default value:</b> <code>6</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>urgency</code></td>
|
||||
<td>When using a timeFormat of <code>absolute</code>, the <code>urgency</code> setting allows you to display events within a specific time frame as <code>relative</code>
|
||||
This allows events within a certain time frame to be displayed as relative (in xx days) while others are displayed as absolute dates<br>
|
||||
<br><b>Possible values:</b> a positive integer representing the number of days for which you want a relative date, for example <code>7</code> (for 7 days)<br>
|
||||
<br><b>Default value:</b> <code>7</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>broadcastEvents</code></td>
|
||||
<td>If this property is set to true, the calendar will broadcast all the events to all other modules with the notification message: <code>CALENDAR_EVENTS</code>. The event objects are stored in an array and contain the following fields: <code>title</code>, <code>startDate</code>, <code>endDate</code>, <code>fullDayEvent</code>, <code>location</code> and <code>geo</code>.<br>
|
||||
<br><b>Possible values:</b> <code>true</code>, <code>false</code> <br>
|
||||
<br><b>Default value:</b> <code>true</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>hidePrivate</code></td>
|
||||
<td>Hides private calendar events.<br>
|
||||
<br><b>Possible values:</b> <code>true</code> or <code>false</code>
|
||||
<br><b>Default value:</b> <code>false</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
| Option | Description
|
||||
| ---------------------------- | -----------
|
||||
| `maximumEntries` | The maximum number of events shown. / **Possible values:** `0` - `100` <br> **Default value:** `10`
|
||||
| `maximumNumberOfDays` | The maximum number of days in the future. <br><br> **Default value:** `365`
|
||||
| `displaySymbol` | Display a symbol in front of an entry. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
|
||||
| `defaultSymbol` | The default symbol. <br><br> **Possible values:** See [Font Awsome](http://fontawesome.io/icons/) website. <br> **Default value:** `calendar`
|
||||
| `maxTitleLength` | The maximum title length. <br><br> **Possible values:** `10` - `50` <br> **Default value:** `25`
|
||||
| `wrapEvents` | Wrap event titles to multiple lines. Breaks lines at the length defined by `maxTitleLength`. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
|
||||
| `fetchInterval` | How often does the content needs to be fetched? (Milliseconds) <br><br> **Possible values:** `1000` - `86400000` <br> **Default value:** `300000` (5 minutes)
|
||||
| `animationSpeed` | Speed of the update animation. (Milliseconds) <br><br> **Possible values:**`0` - `5000` <br> **Default value:** `2000` (2 seconds)
|
||||
| `fade` | Fade the future events to black. (Gradient) <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
|
||||
| `fadePoint` | Where to start fade? <br><br> **Possible values:** `0` (top of the list) - `1` (bottom of list) <br> **Default value:** `0.25`
|
||||
| `calendars` | The list of calendars. <br><br> **Possible values:** An array, see _calendar configuration_ below. <br> **Default value:** _An example calendar._
|
||||
| `titleReplace` | An object of textual replacements applied to the tile of the event. This allow to remove or replace certains words in the title. <br><br> **Example:** `{'Birthday of ' : '', 'foo':'bar'}` <br> **Default value:** `{ "De verjaardag van ": "", "'s birthday": "" }`
|
||||
| `displayRepeatingCountTitle` | Show count title for yearly repeating events (e.g. "X. Birthday", "X. Anniversary") <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
|
||||
| `dateFormat` | Format to use for the date of events (when using absolute dates) <br><br> **Possible values:** See [Moment.js formats](http://momentjs.com/docs/#/parsing/string-format/) <br> **Default value:** `MMM Do` (e.g. Jan 18th)
|
||||
| `fullDayEventDateFormat` | Format to use for the date of full day events (when using absolute dates) <br><br> **Possible values:** See [Moment.js formats](http://momentjs.com/docs/#/parsing/string-format/) <br> **Default value:** `MMM Do` (e.g. Jan 18th)
|
||||
| `timeFormat` | Display event times as absolute dates, or relative time <br><br> **Possible values:** `absolute` or `relative` <br> **Default value:** `relative`
|
||||
| `getRelative` | How much time (in hours) should be left until calendar events start getting relative? <br><br> **Possible values:** `0` (events stay absolute) - `48` (48 hours before the event starts) <br> **Default value:** `6`
|
||||
| `urgency` | When using a timeFormat of `absolute`, the `urgency` setting allows you to display events within a specific time frame as `relative`. This allows events within a certain time frame to be displayed as relative (in xx days) while others are displayed as absolute dates <br><br> **Possible values:** a positive integer representing the number of days for which you want a relative date, for example `7` (for 7 days) <br><br> **Default value:** `7`
|
||||
| `broadcastEvents` | If this property is set to true, the calendar will broadcast all the events to all other modules with the notification message: `CALENDAR_EVENTS`. The event objects are stored in an array and contain the following fields: `title`, `startDate`, `endDate`, `fullDayEvent`, `location` and `geo`. <br><br> **Possible values:** `true`, `false` <br><br> **Default value:** `true`
|
||||
| `hidePrivate` | Hides private calendar events. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
|
||||
| `excludedEvents` | An array of words / phrases from event titles that will be excluded from being shown. <br><br>Additionally advanced filter objects can be passed in. Below is the configuration for the advance filtering object.<br>**Required**<br>`filterBy` - string used to determine if filter is applied.<br>**Optional**<br>`until` - Time before an event to display it Ex: [`'3 days'`, `'2 months'`, `'1 week'`]<br>`caseSensitive` - By default, excludedEvents are case insensitive, set this to true to enforce case sensitivity<br><br> **Example:** `['Birthday', 'Hide This Event', {filterBy: 'Payment', until: '6 days', caseSensitive: true}]` <br> **Default value:** `[]`
|
||||
|
||||
### Calendar configuration
|
||||
|
||||
The `calendars` property contains an array of the configured calendars.
|
||||
The `colored` property gives the option for an individual color for each calendar.
|
||||
|
||||
#### Default value:
|
||||
````javascript
|
||||
config: {
|
||||
colored: false,
|
||||
calendars: [
|
||||
{
|
||||
url: 'http://www.calendarlabs.com/templates/ical/US-Holidays.ics',
|
||||
symbol: 'calendar',
|
||||
auth: {
|
||||
user: 'username',
|
||||
pass: 'superstrongpassword',
|
||||
method: 'basic'
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
````
|
||||
|
||||
|
||||
#### Calendar configuration options:
|
||||
<table width="100%">
|
||||
<!-- why, markdown... -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th width="100%">Description</th>
|
||||
</tr>
|
||||
<thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>url</code></td>
|
||||
<td>The url of the calendar .ical. This property is required.<br>
|
||||
<br><b>Possible values:</b> Any public accessble .ical calendar.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>symbol</code></td>
|
||||
<td>The symbol to show in front of an event. This property is optional.<br>
|
||||
<br><b>Possible values:</b> See <a href="http://fontawesome.io/icons/" target="_blank">Font Awesome</a> website.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>repeatingCountTitle</code></td>
|
||||
<td>The count title for yearly repating events in this calendar. <br>
|
||||
<br><b>Example:</b> <br>
|
||||
<code>'Birthday'</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>user</code></td>
|
||||
<td>The username for HTTP Basic authentication.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pass</code></td>
|
||||
<td>The password for HTTP Basic authentication.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
| Option | Description
|
||||
| --------------------- | -----------
|
||||
| `url` | The url of the calendar .ical. This property is required. <br><br> **Possible values:** Any public accessble .ical calendar.
|
||||
| `symbol` | The symbol to show in front of an event. This property is optional. <br><br> **Possible values:** See [Font Awesome](http://fontawesome.io/icons/) website. To have multiple symbols you can define them in an array e.g. `["calendar", "plane"]`
|
||||
| `color` | The font color of an event from this calendar. This property should be set if the config is set to colored: true. <br><br> **Possible values:** HEX, RGB or RGBA values (#efefef, rgb(242,242,242), rgba(242,242,242,0.5)).
|
||||
| `repeatingCountTitle` | The count title for yearly repating events in this calendar. <br><br> **Example:** `'Birthday'`
|
||||
| `maximumEntries` | The maximum number of events shown. Overrides global setting. **Possible values:** `0` - `100`
|
||||
| `maximumNumberOfDays` | The maximum number of days in the future. Overrides global setting
|
||||
| `auth` | The object containing options for authentication against the calendar.
|
||||
|
||||
|
||||
#### Calendar authentication options:
|
||||
| Option | Description
|
||||
| --------------------- | -----------
|
||||
| `user` | The username for HTTP authentication.
|
||||
| `pass` | The password for HTTP authentication. (If you use Bearer authentication, this should be your BearerToken.)
|
||||
| `method` | Which authentication method should be used. HTTP Basic, Digest and Bearer authentication methods are supported. Basic authentication is used by default if this option is omitted. **Possible values:** `digest`, `basic`, `bearer` **Default value:** `basic`
|
||||
|
@@ -2,6 +2,7 @@
|
||||
padding-left: 0;
|
||||
padding-right: 10px;
|
||||
font-size: 80%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.calendar .symbol span {
|
||||
@@ -19,4 +20,5 @@
|
||||
.calendar .time {
|
||||
padding-left: 30px;
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
@@ -18,15 +18,18 @@ Module.register("calendar", {
|
||||
displayRepeatingCountTitle: false,
|
||||
defaultRepeatingCountTitle: "",
|
||||
maxTitleLength: 25,
|
||||
wrapEvents: false, // wrap events to multiple lines breaking at maxTitleLength
|
||||
fetchInterval: 5 * 60 * 1000, // Update every 5 minutes.
|
||||
animationSpeed: 2000,
|
||||
fade: true,
|
||||
urgency: 7,
|
||||
timeFormat: "relative",
|
||||
dateFormat: "MMM Do",
|
||||
fullDayEventDateFormat: "MMM Do",
|
||||
getRelative: 6,
|
||||
fadePoint: 0.25, // Start on 1/4th of the list.
|
||||
hidePrivate: false,
|
||||
colored: false,
|
||||
calendars: [
|
||||
{
|
||||
symbol: "calendar",
|
||||
@@ -37,7 +40,8 @@ Module.register("calendar", {
|
||||
"De verjaardag van ": "",
|
||||
"'s birthday": ""
|
||||
},
|
||||
broadcastEvents: true
|
||||
broadcastEvents: true,
|
||||
excludedEvents: []
|
||||
},
|
||||
|
||||
// Define required scripts.
|
||||
@@ -52,8 +56,8 @@ Module.register("calendar", {
|
||||
|
||||
// Define required translations.
|
||||
getTranslations: function () {
|
||||
// The translations for the defaut modules are defined in the core translation files.
|
||||
// Therefor we can just return false. Otherwise we should have returned a dictionairy.
|
||||
// The translations for the default modules are defined in the core translation files.
|
||||
// Therefor we can just return false. Otherwise we should have returned a dictionary.
|
||||
// If you're trying to build your own module including translations, check out the documentation.
|
||||
return false;
|
||||
},
|
||||
@@ -63,12 +67,28 @@ Module.register("calendar", {
|
||||
Log.log("Starting module: " + this.name);
|
||||
|
||||
// Set locale.
|
||||
moment.locale(config.language);
|
||||
moment.updateLocale(config.language, this.getLocaleSpecification(config.timeFormat));
|
||||
|
||||
for (var c in this.config.calendars) {
|
||||
var calendar = this.config.calendars[c];
|
||||
calendar.url = calendar.url.replace("webcal://", "http://");
|
||||
this.addCalendar(calendar.url, calendar.user, calendar.pass);
|
||||
|
||||
var calendarConfig = {
|
||||
maximumEntries: calendar.maximumEntries,
|
||||
maximumNumberOfDays: calendar.maximumNumberOfDays
|
||||
};
|
||||
|
||||
// we check user and password here for backwards compatibility with old configs
|
||||
if(calendar.user && calendar.pass) {
|
||||
Log.warn("Deprecation warning: Please update your calendar authentication configuration.");
|
||||
Log.warn("https://github.com/MichMich/MagicMirror/tree/v2.1.2/modules/default/calendar#calendar-authentication-options");
|
||||
calendar.auth = {
|
||||
user: calendar.user,
|
||||
pass: calendar.pass
|
||||
}
|
||||
}
|
||||
|
||||
this.addCalendar(calendar.url, calendar.auth, calendarConfig);
|
||||
}
|
||||
|
||||
this.calendarData = {};
|
||||
@@ -112,23 +132,36 @@ Module.register("calendar", {
|
||||
|
||||
for (var e in events) {
|
||||
var event = events[e];
|
||||
|
||||
var eventWrapper = document.createElement("tr");
|
||||
|
||||
if (this.config.colored) {
|
||||
eventWrapper.style.cssText = "color:" + this.colorForUrl(event.url);
|
||||
}
|
||||
|
||||
eventWrapper.className = "normal";
|
||||
|
||||
if (this.config.displaySymbol) {
|
||||
var symbolWrapper = document.createElement("td");
|
||||
symbolWrapper.className = "symbol";
|
||||
var symbol = document.createElement("span");
|
||||
symbol.className = "fa fa-" + this.symbolForUrl(event.url);
|
||||
symbolWrapper.appendChild(symbol);
|
||||
symbolWrapper.className = "symbol align-right";
|
||||
var symbols = this.symbolsForUrl(event.url);
|
||||
if(typeof symbols === "string") {
|
||||
symbols = [symbols];
|
||||
}
|
||||
|
||||
for(var i = 0; i < symbols.length; i++) {
|
||||
var symbol = document.createElement("span");
|
||||
symbol.className = "fa fa-fw fa-" + symbols[i];
|
||||
if(i > 0){
|
||||
symbol.style.paddingLeft = "5px";
|
||||
}
|
||||
symbolWrapper.appendChild(symbol);
|
||||
}
|
||||
eventWrapper.appendChild(symbolWrapper);
|
||||
}
|
||||
|
||||
var titleWrapper = document.createElement("td"),
|
||||
repeatingCountTitle = "";
|
||||
|
||||
|
||||
if (this.config.displayRepeatingCountTitle) {
|
||||
|
||||
repeatingCountTitle = this.countTitleForUrl(event.url);
|
||||
@@ -142,7 +175,13 @@ Module.register("calendar", {
|
||||
}
|
||||
|
||||
titleWrapper.innerHTML = this.titleTransform(event.title) + repeatingCountTitle;
|
||||
titleWrapper.className = "title bright";
|
||||
|
||||
if (!this.config.colored) {
|
||||
titleWrapper.className = "title bright";
|
||||
} else {
|
||||
titleWrapper.className = "title";
|
||||
}
|
||||
|
||||
eventWrapper.appendChild(titleWrapper);
|
||||
|
||||
var timeWrapper = document.createElement("td");
|
||||
@@ -177,7 +216,7 @@ Module.register("calendar", {
|
||||
// This event falls within the config.urgency period that the user has set
|
||||
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
|
||||
} else {
|
||||
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
|
||||
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.fullDayEventDateFormat));
|
||||
}
|
||||
} else {
|
||||
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
|
||||
@@ -214,7 +253,12 @@ Module.register("calendar", {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
timeWrapper.innerHTML = this.capFirst(this.translate("RUNNING")) + " " + moment(event.endDate, "x").fromNow(true);
|
||||
timeWrapper.innerHTML = this.capFirst(
|
||||
this.translate("RUNNING", {
|
||||
fallback: this.translate("RUNNING") + " {timeUntilEnd}",
|
||||
timeUntilEnd: moment(event.endDate, "x").fromNow(true)
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
//timeWrapper.innerHTML += ' - '+ moment(event.startDate,'x').format('lll');
|
||||
@@ -241,10 +285,35 @@ Module.register("calendar", {
|
||||
return wrapper;
|
||||
},
|
||||
|
||||
/**
|
||||
* This function accepts a number (either 12 or 24) and returns a moment.js LocaleSpecification with the
|
||||
* corresponding timeformat to be used in the calendar display. If no number is given (or otherwise invalid input)
|
||||
* it will a localeSpecification object with the system locale time format.
|
||||
*
|
||||
* @param {number} timeFormat Specifies either 12 or 24 hour time format
|
||||
* @returns {moment.LocaleSpecification}
|
||||
*/
|
||||
getLocaleSpecification: function(timeFormat) {
|
||||
switch (timeFormat) {
|
||||
case 12: {
|
||||
return { longDateFormat: {LT: "h:mm A"} };
|
||||
break;
|
||||
}
|
||||
case 24: {
|
||||
return { longDateFormat: {LT: "HH:mm"} };
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return { longDateFormat: {LT: moment.localeData().longDateFormat("LT")} };
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/* hasCalendarURL(url)
|
||||
* Check if this config contains the calendar url.
|
||||
*
|
||||
* argument url sting - Url to look for.
|
||||
* argument url string - Url to look for.
|
||||
*
|
||||
* return bool - Has calendar url
|
||||
*/
|
||||
@@ -273,8 +342,8 @@ Module.register("calendar", {
|
||||
var event = calendar[e];
|
||||
if(this.config.hidePrivate) {
|
||||
if(event.class === "PRIVATE") {
|
||||
// do not add the current event, skip it
|
||||
continue;
|
||||
// do not add the current event, skip it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
event.url = c;
|
||||
@@ -293,74 +362,117 @@ Module.register("calendar", {
|
||||
/* createEventList(url)
|
||||
* Requests node helper to add calendar url.
|
||||
*
|
||||
* argument url sting - Url to add.
|
||||
* argument url string - Url to add.
|
||||
*/
|
||||
addCalendar: function (url, user, pass) {
|
||||
addCalendar: function (url, auth, calendarConfig) {
|
||||
this.sendSocketNotification("ADD_CALENDAR", {
|
||||
url: url,
|
||||
maximumEntries: this.config.maximumEntries,
|
||||
maximumNumberOfDays: this.config.maximumNumberOfDays,
|
||||
excludedEvents: calendarConfig.excludedEvents || this.config.excludedEvents,
|
||||
maximumEntries: calendarConfig.maximumEntries || this.config.maximumEntries,
|
||||
maximumNumberOfDays: calendarConfig.maximumNumberOfDays || this.config.maximumNumberOfDays,
|
||||
fetchInterval: this.config.fetchInterval,
|
||||
user: user,
|
||||
pass: pass
|
||||
auth: auth
|
||||
});
|
||||
},
|
||||
|
||||
/* symbolForUrl(url)
|
||||
* Retrieves the symbol for a specific url.
|
||||
/* symbolsForUrl(url)
|
||||
* Retrieves the symbols for a specific url.
|
||||
*
|
||||
* argument url sting - Url to look for.
|
||||
* argument url string - Url to look for.
|
||||
*
|
||||
* return string - The Symbol
|
||||
* return string/array - The Symbols
|
||||
*/
|
||||
symbolForUrl: function (url) {
|
||||
for (var c in this.config.calendars) {
|
||||
var calendar = this.config.calendars[c];
|
||||
if (calendar.url === url && typeof calendar.symbol === "string") {
|
||||
return calendar.symbol;
|
||||
}
|
||||
}
|
||||
|
||||
return this.config.defaultSymbol;
|
||||
symbolsForUrl: function (url) {
|
||||
return this.getCalendarProperty(url, "symbol", this.config.defaultSymbol);
|
||||
},
|
||||
|
||||
/* colorForUrl(url)
|
||||
* Retrieves the color for a specific url.
|
||||
*
|
||||
* argument url string - Url to look for.
|
||||
*
|
||||
* return string - The Color
|
||||
*/
|
||||
colorForUrl: function (url) {
|
||||
return this.getCalendarProperty(url, "color", "#fff");
|
||||
},
|
||||
|
||||
/* countTitleForUrl(url)
|
||||
* Retrieves the name for a specific url.
|
||||
*
|
||||
* argument url sting - Url to look for.
|
||||
* argument url string - Url to look for.
|
||||
*
|
||||
* return string - The Symbol
|
||||
*/
|
||||
countTitleForUrl: function (url) {
|
||||
return this.getCalendarProperty(url, "repeatingCountTitle", this.config.defaultRepeatingCountTitle);
|
||||
},
|
||||
|
||||
/* getCalendarProperty(url, property, defaultValue)
|
||||
* Helper method to retrieve the property for a specific url.
|
||||
*
|
||||
* argument url string - Url to look for.
|
||||
* argument property string - Property to look for.
|
||||
* argument defaultValue string - Value if property is not found.
|
||||
*
|
||||
* return string - The Property
|
||||
*/
|
||||
getCalendarProperty: function (url, property, defaultValue) {
|
||||
for (var c in this.config.calendars) {
|
||||
var calendar = this.config.calendars[c];
|
||||
if (calendar.url === url && typeof calendar.repeatingCountTitle === "string") {
|
||||
return calendar.repeatingCountTitle;
|
||||
if (calendar.url === url && calendar.hasOwnProperty(property)) {
|
||||
return calendar[property];
|
||||
}
|
||||
}
|
||||
|
||||
return this.config.defaultRepeatingCountTitle;
|
||||
return defaultValue;
|
||||
},
|
||||
|
||||
/* shorten(string, maxLength)
|
||||
* Shortens a sting if it's longer than maxLenthg.
|
||||
* Adds an ellipsis to the end.
|
||||
/**
|
||||
* Shortens a string if it's longer than maxLength and add a ellipsis to the end
|
||||
*
|
||||
* argument string string - The string to shorten.
|
||||
* argument maxLength number - The max lenth of the string.
|
||||
*
|
||||
* return string - The shortened string.
|
||||
* @param {string} string Text string to shorten
|
||||
* @param {number} maxLength The max length of the string
|
||||
* @param {boolean} wrapEvents Wrap the text after the line has reached maxLength
|
||||
* @returns {string} The shortened string
|
||||
*/
|
||||
shorten: function (string, maxLength) {
|
||||
if (string.length > maxLength) {
|
||||
return string.slice(0, maxLength) + "…";
|
||||
shorten: function (string, maxLength, wrapEvents) {
|
||||
if (typeof string !== "string") {
|
||||
return "";
|
||||
}
|
||||
|
||||
return string;
|
||||
if (wrapEvents === true) {
|
||||
var temp = "";
|
||||
var currentLine = "";
|
||||
var words = string.split(" ");
|
||||
|
||||
for (var i = 0; i < words.length; i++) {
|
||||
var word = words[i];
|
||||
if (currentLine.length + word.length < (typeof maxLength === "number" ? maxLength : 25) - 1) { // max - 1 to account for a space
|
||||
currentLine += (word + " ");
|
||||
} else {
|
||||
if (currentLine.length > 0) {
|
||||
temp += (currentLine + "<br>" + word + " ");
|
||||
} else {
|
||||
temp += (word + "<br>");
|
||||
}
|
||||
currentLine = "";
|
||||
}
|
||||
}
|
||||
|
||||
return (temp + currentLine).trim();
|
||||
} else {
|
||||
if (maxLength && typeof maxLength === "number" && string.length > maxLength) {
|
||||
return string.trim().slice(0, maxLength) + "…";
|
||||
} else {
|
||||
return string.trim();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/* capFirst(string)
|
||||
* Capitalize the first letter of a string
|
||||
* Eeturn capitalized string
|
||||
* Return capitalized string
|
||||
*/
|
||||
|
||||
capFirst: function (string) {
|
||||
@@ -370,7 +482,7 @@ Module.register("calendar", {
|
||||
/* titleTransform(title)
|
||||
* Transforms the title of an event for usage.
|
||||
* Replaces parts of the text as defined in config.titleReplace.
|
||||
* Shortens title based on config.maxTitleLength
|
||||
* Shortens title based on config.maxTitleLength and config.wrapEvents
|
||||
*
|
||||
* argument title string - The title to transform.
|
||||
*
|
||||
@@ -379,10 +491,17 @@ Module.register("calendar", {
|
||||
titleTransform: function (title) {
|
||||
for (var needle in this.config.titleReplace) {
|
||||
var replacement = this.config.titleReplace[needle];
|
||||
|
||||
var regParts = needle.match(/^\/(.+)\/([gim]*)$/);
|
||||
if (regParts) {
|
||||
// the parsed pattern is a regexp.
|
||||
needle = new RegExp(regParts[1], regParts[2]);
|
||||
}
|
||||
|
||||
title = title.replace(needle, replacement);
|
||||
}
|
||||
|
||||
title = this.shorten(title, this.config.maxTitleLength);
|
||||
title = this.shorten(title, this.config.maxTitleLength, this.config.wrapEvents);
|
||||
return title;
|
||||
},
|
||||
|
||||
@@ -392,10 +511,12 @@ Module.register("calendar", {
|
||||
*/
|
||||
broadcastEvents: function () {
|
||||
var eventList = [];
|
||||
for (url in this.calendarData) {
|
||||
for (var url in this.calendarData) {
|
||||
var calendar = this.calendarData[url];
|
||||
for (e in calendar) {
|
||||
for (var e in calendar) {
|
||||
var event = cloneObject(calendar[e]);
|
||||
event.symbol = this.symbolsForUrl(url);
|
||||
event.color = this.colorForUrl(url);
|
||||
delete event.url;
|
||||
eventList.push(event);
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user