mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-10-16 09:17:52 +00:00
Compare commits
1723 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
8010e6220d | ||
|
09e6a6a4ad | ||
|
da772e6649 | ||
|
65b8f78ca1 | ||
|
23bcac2e55 | ||
|
e6a88eef35 | ||
|
6654c461a6 | ||
|
a48cb052c2 | ||
|
809d932087 | ||
|
19d906c5e4 | ||
|
2f933a90fd | ||
|
3607875714 | ||
|
fec8829041 | ||
|
f58e758a41 | ||
|
cebed0824f | ||
|
97120bd1f1 | ||
|
4237d0c7e3 | ||
|
243cdfefa2 | ||
|
a12a4cd8f2 | ||
|
980b8a2924 | ||
|
989dca5792 | ||
|
c9eb06bb70 | ||
|
af89906b81 | ||
|
de71b1b397 | ||
|
2decee1d87 | ||
|
6a184b069b | ||
|
48db615ac8 | ||
|
bb9caa4838 | ||
|
3c11c3fc16 | ||
|
6c1dbc5e61 | ||
|
21bf1dd1a3 | ||
|
78aa2b491c | ||
|
53d2c7e89f | ||
|
cdf78db629 | ||
|
15c5725829 | ||
|
96653170a2 | ||
|
eade89da48 | ||
|
9b86049964 | ||
|
9e2bbd50b6 | ||
|
0d18266ad1 | ||
|
83ee0534f2 | ||
|
8ad00025ad | ||
|
7d08a7c9cd | ||
|
c8d5a5319e | ||
|
a08fa721cf | ||
|
89bc3137ba | ||
|
4d8d30f897 | ||
|
d67e9468c0 | ||
|
7dda36ab97 | ||
|
32df3e80b1 | ||
|
3c33969d23 | ||
|
c2aec5ae36 | ||
|
7afc908c32 | ||
|
c66ecbdd29 | ||
|
e61bccab36 | ||
|
cfeef98261 | ||
|
c949548150 | ||
|
829ccfca88 | ||
|
950fa84d1c | ||
|
5666729fcf | ||
|
3a13886af9 | ||
|
db6e0ccf63 | ||
|
35f2ebc2aa | ||
|
60ac4a3e86 | ||
|
c966d34b07 | ||
|
a05d7059fb | ||
|
facc1dc944 | ||
|
df2ebbf384 | ||
|
e210471c7a | ||
|
f42d552dad | ||
|
edfd9e46b6 | ||
|
27644776f3 | ||
|
a4797dcc73 | ||
|
677312648b | ||
|
ace460c69b | ||
|
b922fe35ab | ||
|
222e2166ff | ||
|
7dc25ec425 | ||
|
980835981f | ||
|
e425adc230 | ||
|
2ea7ef327e | ||
|
6c9d33f05c | ||
|
c584b51726 | ||
|
3c8fa3382b | ||
|
e5c75259e0 | ||
|
f78e437a42 | ||
|
8f61b1d320 | ||
|
84f3c6f7e6 | ||
|
bfb6c001a0 | ||
|
f0fa9f2033 | ||
|
c82cc30968 | ||
|
b0cb14b515 | ||
|
7f86d2aa0a | ||
|
482dd4db76 | ||
|
0f44fd2290 | ||
|
690d461693 | ||
|
f1ca72aee9 | ||
|
a3a6fc0d15 | ||
|
4f4fe5f06b | ||
|
c59f47f4b5 | ||
|
0ae810b8c6 | ||
|
ac9f42a8fe | ||
|
eab7e56ece | ||
|
7329515a4d | ||
|
6d6bf2df3a | ||
|
3008bfbe0a | ||
|
9399517cfb | ||
|
b117fe222d | ||
|
62567264f0 | ||
|
c054b3b0de | ||
|
83fb5b4d1b | ||
|
07a86800c9 | ||
|
688b5f29b6 | ||
|
e311f17062 | ||
|
b4cdb22e24 | ||
|
b63194c0aa | ||
|
0bab32315c | ||
|
7e881dd850 | ||
|
d6aaafb069 | ||
|
731fcb8829 | ||
|
af0e94a4b7 | ||
|
deda869cc5 | ||
|
434089395a | ||
|
048985bdf4 | ||
|
70d6d4246d | ||
|
70befe900c | ||
|
9fd81bf6c7 | ||
|
a388fe3acb | ||
|
d4946d931a | ||
|
9812654bfa | ||
|
557426246c | ||
|
d0a3e8f789 | ||
|
50b37f2ddd | ||
|
e4197012f6 | ||
|
df00a1e0a3 | ||
|
41327bfb03 | ||
|
66ef72a040 | ||
|
af660bc631 | ||
|
18edb13e76 | ||
|
e05431c847 | ||
|
5c61f95093 | ||
|
9a407c727a | ||
|
cad655a15b | ||
|
1fa5c53b72 | ||
|
d01b1a706c | ||
|
678d5fc532 | ||
|
c4e01fa566 | ||
|
e694622b83 | ||
|
1d16c96ca4 | ||
|
ab1f34726b | ||
|
c9f201755e | ||
|
094ef592a7 | ||
|
e7493c38d1 | ||
|
6fd0fcfdb5 | ||
|
6da534a961 | ||
|
f26e43144e | ||
|
427529171c | ||
|
cd146cf822 | ||
|
ced412372c | ||
|
2f6e650c1b | ||
|
5c125ca9b4 | ||
|
89c0939d58 | ||
|
8ea955fb23 | ||
|
73a297f23f | ||
|
95f1382fbf | ||
|
80df01c8ee | ||
|
c2df864119 | ||
|
2cc9356c32 | ||
|
89234c0163 | ||
|
1b7fe286a6 | ||
|
7c2d797ed0 | ||
|
4d2eedc56b | ||
|
ece0d9301f | ||
|
cc10cd88d3 | ||
|
b9f308c832 | ||
|
6816bd8bad | ||
|
0e026de5bd | ||
|
a419ad201d | ||
|
a66a4f62bd | ||
|
e721d2204b | ||
|
f293cf6e81 | ||
|
0cde1d4969 | ||
|
bd49bd6e33 | ||
|
84dc1fa151 | ||
|
5858e862d9 | ||
|
8b004a549a | ||
|
4a53e4207c | ||
|
6510de5d29 | ||
|
a6191320a1 | ||
|
a75ff0c061 | ||
|
0d45eb91db | ||
|
95edbc16bb | ||
|
15a5e3c779 | ||
|
0433432e62 | ||
|
e7ca73bbad | ||
|
8d8374b8e2 | ||
|
4dc5bbe601 | ||
|
55371f9c78 | ||
|
af63f4098f | ||
|
2b4939a875 | ||
|
d5d22844ab | ||
|
7dab00be87 | ||
|
67582d388a | ||
|
0671e7d456 | ||
|
c9e6af96d0 | ||
|
ee48a56603 | ||
|
4c03f39437 | ||
|
d8d425c963 | ||
|
ec64ac9f1d | ||
|
7ed682c186 | ||
|
9dcb579741 | ||
|
19f837aa41 | ||
|
b267491934 | ||
|
a369dddde8 | ||
|
29d87d7c48 | ||
|
4847f50093 | ||
|
7c848b1253 | ||
|
607aa46032 | ||
|
430193891f | ||
|
cbb82ed635 | ||
|
1dfb72703a | ||
|
5edf377f4b | ||
|
43a33d267a | ||
|
c4048f4c7b | ||
|
501c89ae3a | ||
|
5899497aa7 | ||
|
2c758a9981 | ||
|
66eb99e506 | ||
|
b583140077 | ||
|
f378c93dd3 | ||
|
5d29fa5e62 | ||
|
54f04c9141 | ||
|
fc0c706100 | ||
|
02ac9fa0d7 | ||
|
ca95c75df3 | ||
|
4ff86795b9 | ||
|
30eaec29d3 | ||
|
742d38c9ec | ||
|
56a4597784 | ||
|
b2a7d3584b | ||
|
b8167f89e5 | ||
|
c916472b22 | ||
|
bdfe7c5e69 | ||
|
749475799a | ||
|
daf9c807b5 | ||
|
5dfecfdee3 | ||
|
d267c03ee2 | ||
|
864e3c7a50 | ||
|
747e1a818d | ||
|
f468dbf427 | ||
|
88572e90d4 | ||
|
41fc7dd73d | ||
|
c87c588c79 | ||
|
5ab4b57411 | ||
|
3c936c6957 | ||
|
0ca903a146 | ||
|
4e91919990 | ||
|
e023a68780 | ||
|
17452cd5b6 | ||
|
c76f2c8483 | ||
|
90207f9b68 | ||
|
0ec5371b61 | ||
|
8549aeb466 | ||
|
504d3cd131 | ||
|
83ed298eba | ||
|
a90f2cb156 | ||
|
1a54bafdd5 | ||
|
3cc186f7e4 | ||
|
a2497d7564 | ||
|
c01c882081 | ||
|
af279df5c9 | ||
|
1ea7ce2589 | ||
|
476c577bc1 | ||
|
e3b91c4d71 | ||
|
1e2d7acf4a | ||
|
1def120616 | ||
|
25ddf2c651 | ||
|
e5d384e808 | ||
|
63351553d8 | ||
|
4630740ac7 | ||
|
0ff4884d3a | ||
|
2dfdedf7b0 | ||
|
e1fa5fb180 | ||
|
4e47f9eb68 | ||
|
c4c8955bc2 | ||
|
f47b808478 | ||
|
493367da5e | ||
|
4ca185cf45 | ||
|
8f4effbb8d | ||
|
a4ba7f277e | ||
|
c3f97a0cf1 | ||
|
c1b8fc1233 | ||
|
a481638c03 | ||
|
c12dfc7b4d | ||
|
34bc527709 | ||
|
ee134d0d7c | ||
|
f795ee7fd9 | ||
|
1bba75881f | ||
|
34db2d9efa | ||
|
91dd308952 | ||
|
3d97e26cde | ||
|
104c1ecbec | ||
|
88266ec4e3 | ||
|
f5be159187 | ||
|
1194d86460 | ||
|
76d6dca63f | ||
|
1b69e62e2e | ||
|
e89839359f | ||
|
177d113cd9 | ||
|
853fa87012 | ||
|
a592ca25ff | ||
|
a8c9926fa7 | ||
|
bf5587fe09 | ||
|
7c8a27e894 | ||
|
d248b11ffc | ||
|
1282e010ee | ||
|
d5d1ca948e | ||
|
a08704e8ed | ||
|
2a8c0c0c08 | ||
|
2ae0c98b4c | ||
|
c427050a92 | ||
|
43a0829f44 | ||
|
e9b8b91861 | ||
|
6f104f5056 | ||
|
e720efabdc | ||
|
45cb770e41 | ||
|
3f1cc66767 | ||
|
d1f5118bb5 | ||
|
b90f1f786e | ||
|
c6f424201b | ||
|
a7db8cf7cd | ||
|
e2ed0f5e55 | ||
|
d000e3c8a9 | ||
|
cf465a1184 | ||
|
f3119824f4 | ||
|
ef069c1689 | ||
|
68e5a9dfda | ||
|
fd1a31afb6 | ||
|
6d7d4a15d9 | ||
|
518a7d0dcc | ||
|
a4bdfe6b7d | ||
|
281269aa0d | ||
|
33521ebd31 | ||
|
18ae08de8f | ||
|
3d3c4ba02f | ||
|
89917c1582 | ||
|
90c6758562 | ||
|
5be8084473 | ||
|
8a066a4ec0 | ||
|
b98237fa44 | ||
|
c4f9388668 | ||
|
0f44b27e54 | ||
|
10d05e1870 | ||
|
921557161d | ||
|
eb092b9f20 | ||
|
28ca8a9f9f | ||
|
d7be56df7a | ||
|
ce2c163f4b | ||
|
5272462368 | ||
|
42354a6728 | ||
|
754fe3df91 | ||
|
a7a0350c1a | ||
|
a8b2ca7cfe | ||
|
e82867820b | ||
|
13305d111e | ||
|
ae37fa2bc5 | ||
|
c81db752f6 | ||
|
5d9d71dee8 | ||
|
1f558d46a4 | ||
|
36eb899c3b | ||
|
e223d752cb | ||
|
fcb503e885 | ||
|
c1055234d8 | ||
|
35ec98cf13 | ||
|
66a9138666 | ||
|
279582ff21 | ||
|
a9540ffabe | ||
|
8f02cb83ed | ||
|
5e94c8be7b | ||
|
ea6e9af5da | ||
|
1bb72262ba | ||
|
61ace7bd97 | ||
|
1a417cc36f | ||
|
00a11f4343 | ||
|
491bbacddd | ||
|
1b9daa0adb | ||
|
ccdc981756 | ||
|
b44dcc9f30 | ||
|
479ceb14e4 | ||
|
50e41d1999 | ||
|
f6e2073de0 | ||
|
271aab9746 | ||
|
8d9bf5cf17 | ||
|
ceb7ed093e | ||
|
abdbcbb741 | ||
|
51ac07cd34 | ||
|
4b29698c6c | ||
|
f93285846a | ||
|
44347db52e | ||
|
f2749ff17f | ||
|
3735342dfa | ||
|
fed6e1e82f | ||
|
58a59b624a | ||
|
1338918a89 | ||
|
831e7cdb3f | ||
|
2b8583e652 | ||
|
b0220e4200 | ||
|
0d94ef10ce | ||
|
544a1381fc | ||
|
bc17ef896e | ||
|
0bcbf999fb | ||
|
0c4c44f4b6 | ||
|
486956b762 | ||
|
8d1ef5291f | ||
|
611627ca97 | ||
|
732225b781 | ||
|
58f88075c8 | ||
|
e17ef5eccf | ||
|
70c697f094 | ||
|
4b5625e0b4 | ||
|
7df48870df | ||
|
4a309c49e5 | ||
|
b45899bf90 | ||
|
5ae3c8acfe | ||
|
ef5df5f01b | ||
|
9af7451a61 | ||
|
7048c550b7 | ||
|
f035627f42 | ||
|
7659ff1f77 | ||
|
3df4062a55 | ||
|
02bbbe668b | ||
|
2b82c8fdc9 | ||
|
93abaed0c2 | ||
|
b7fff6d452 | ||
|
d09f01db90 | ||
|
e0c1a58b84 | ||
|
1b82649cca | ||
|
9317e2a36b | ||
|
51aff8ccdc | ||
|
bdd3ea45af | ||
|
3eb5c77ac4 | ||
|
ccc85784c6 | ||
|
46806e8607 | ||
|
95f914d6bd | ||
|
0f86011106 | ||
|
acdd3c0602 | ||
|
05b7237add | ||
|
51f4e9e160 | ||
|
7bbac35d6b | ||
|
d280fe457c | ||
|
b5772af4c3 | ||
|
7ab79bb76e | ||
|
00e3cc26de | ||
|
4b491c43ff | ||
|
41233a933e | ||
|
b84e553d34 | ||
|
19b9fa1996 | ||
|
6abdc4172b | ||
|
5e0c0137d3 | ||
|
3e468d1348 | ||
|
051af521b2 | ||
|
19b6ad3568 | ||
|
5ae514e9b8 | ||
|
826ee98165 | ||
|
f4c021a634 | ||
|
717ae1903e | ||
|
3cd7cdd4fb | ||
|
ff612799c0 | ||
|
d364b04cad | ||
|
212aef79aa | ||
|
9ca2b08a0e | ||
|
fc3ac88806 | ||
|
c550c4c212 | ||
|
f0df8a4a09 | ||
|
65923abcae | ||
|
c4f3009bb5 | ||
|
405a81c8f0 | ||
|
7a067a0f6e | ||
|
32c9f197b1 | ||
|
85151b23d3 | ||
|
fc0a4d90df | ||
|
9558616f59 | ||
|
5be5741ac3 | ||
|
80c0e6a4e0 | ||
|
9bc8f54233 | ||
|
a4c02cef54 | ||
|
935f4132ab | ||
|
a1cb82e7db | ||
|
146d782729 | ||
|
ac8bf5e939 | ||
|
2a70620844 | ||
|
7494b867a7 | ||
|
2874ee8609 | ||
|
e61145eabe | ||
|
4f01c574d2 | ||
|
06a471033c | ||
|
3761bfd26f | ||
|
07ad1b1de4 | ||
|
5725af55a8 | ||
|
d0f75ffda9 | ||
|
4117938140 | ||
|
f50c206d4f | ||
|
ed95cb791c | ||
|
9d0bf6d409 | ||
|
5e68a8200d | ||
|
15887994fe | ||
|
8bec8281d2 | ||
|
2e5a5812a9 | ||
|
744f9316a9 | ||
|
a6da9e8943 | ||
|
14d3bb273c | ||
|
37f8c0783d | ||
|
05a946e2bf | ||
|
a1b3e0778a | ||
|
93f8ade350 | ||
|
2a3a942b8e | ||
|
796583c0be | ||
|
df31b56cf3 | ||
|
e5f4446c82 | ||
|
0252495eca | ||
|
5e75a6b4a0 | ||
|
3002a4f9b3 | ||
|
f627c5e5a1 | ||
|
4d0a93fc3b | ||
|
3f040e7744 | ||
|
e572d544ef | ||
|
ab3f585a2b | ||
|
8b792c5fe0 | ||
|
f184116824 | ||
|
ab2c91ba20 | ||
|
f2a786254e | ||
|
7cbc8a0ede | ||
|
08bc644de1 | ||
|
f12125f2a4 | ||
|
4e86de7fc2 | ||
|
38796a0d40 | ||
|
8562bdfc4a | ||
|
74c96d8521 | ||
|
5f8ef64ca4 | ||
|
5a5f5318d1 | ||
|
db9e615e4d | ||
|
2aae0528b5 | ||
|
8d29bd5c32 | ||
|
77cb8530e5 | ||
|
bfd7404c85 | ||
|
836148a335 | ||
|
1ab00ac804 | ||
|
7a3db8f670 | ||
|
fd3676067a | ||
|
de61e4bf8c | ||
|
3d0de834e0 | ||
|
a897772df2 | ||
|
5726d796f9 | ||
|
8381960737 | ||
|
b61d41f569 | ||
|
af827b5645 | ||
|
13a01d76f7 | ||
|
2007f5ece4 | ||
|
a91a2c2609 | ||
|
0890fd521e | ||
|
4338cb305d | ||
|
84e02c828f | ||
|
be8707570c | ||
|
39602e0a98 | ||
|
bc2f2f5323 | ||
|
66e71282a0 | ||
|
bca56f88af | ||
|
35068631ec | ||
|
bd9557b11e | ||
|
0f2c19f40f | ||
|
4248ae7792 | ||
|
535a4205b1 | ||
|
ab14fc8440 | ||
|
86eea16271 | ||
|
52a9caa96e | ||
|
d8fe1a6e44 | ||
|
ae86090a03 | ||
|
87aa887b2f | ||
|
81378b5e25 | ||
|
6f615b8202 | ||
|
44c1266a8c | ||
|
920f720699 | ||
|
a7c394de6c | ||
|
4529675ca6 | ||
|
935909f24b | ||
|
7b61a0253c | ||
|
64e039cf8f | ||
|
65ae1f6f5a | ||
|
fa38ad3a74 | ||
|
fd500ac411 | ||
|
c6b5bb84e9 | ||
|
3b1f1a41dc | ||
|
d778f2dd8b | ||
|
287452169b | ||
|
6fdcd7f7e9 | ||
|
b16a49109f | ||
|
7d8cde2f0f | ||
|
a3fedb47cb | ||
|
095f08d443 | ||
|
a7948ee1da | ||
|
54eeb27f09 | ||
|
d0e57df1d8 | ||
|
b4df63f37f | ||
|
16a7ea2b75 | ||
|
3892ce3ed4 | ||
|
6c47de0170 | ||
|
8793196bb3 | ||
|
a3a9a8e3f1 | ||
|
bef5b48fce | ||
|
bc0ba2f524 | ||
|
1b387dabde | ||
|
fe8cb7c461 | ||
|
bfbf62d398 | ||
|
b22b845d57 | ||
|
104a62e24d | ||
|
4639911a04 | ||
|
6677f2f5d4 | ||
|
8adb16e1f6 | ||
|
9c9e93974e | ||
|
44a6604fe8 | ||
|
47a18666b6 | ||
|
a231ebc408 | ||
|
33ab2f9cc5 | ||
|
e56c1a5853 | ||
|
27a47d4e4a | ||
|
614e5e62af | ||
|
f45a2e7806 | ||
|
c03485b93e | ||
|
900e668cd6 | ||
|
2657e1194c | ||
|
ec5ad089a3 | ||
|
69a3354617 | ||
|
6e60d30970 | ||
|
d6fa1f9cbc | ||
|
bc8dccd4b5 | ||
|
14f48ec440 | ||
|
fd1b847077 | ||
|
ed3c0e42fb | ||
|
d72d6d925e | ||
|
4ae034b21c | ||
|
bbc7a1fff0 | ||
|
68ed3e4996 | ||
|
03ae6366ea | ||
|
af2f469b84 | ||
|
a64699d064 | ||
|
ac96baae66 | ||
|
15d9569fa4 | ||
|
bd99affb13 | ||
|
88166b7c73 | ||
|
3543ebb9a6 | ||
|
924f239d1b | ||
|
b641285cf4 | ||
|
c22ce3b9a2 | ||
|
64091b5463 | ||
|
09f0b251ab | ||
|
9bf22e5e13 | ||
|
360e1e327e | ||
|
4b8bc65fb7 | ||
|
1b9d91936b | ||
|
7457432c2a | ||
|
30bcc8e7f0 | ||
|
6232bf070d | ||
|
978b0ed811 | ||
|
24acd14da0 | ||
|
4950b69152 | ||
|
f25da27c58 | ||
|
12a5c4a311 | ||
|
3fcc163261 | ||
|
18a670ae29 | ||
|
f443fef091 | ||
|
58adf75c18 | ||
|
bee04bec44 | ||
|
01cc32004a | ||
|
5ede0d35f2 | ||
|
a1b8d951c1 | ||
|
880e2401e1 | ||
|
3434bd8460 | ||
|
239db35ba0 | ||
|
6310750168 | ||
|
133e41e3be | ||
|
b616cfa75c | ||
|
86a0b5f59a | ||
|
becf029583 | ||
|
f36f2357ef | ||
|
faa6d91f7c | ||
|
be5ecc6d0d | ||
|
00a18d54bb | ||
|
bad20c3e0b | ||
|
14a72dec15 | ||
|
7ef43caa1e | ||
|
244c4c6201 | ||
|
8d29947e32 | ||
|
29fc705672 | ||
|
2859db15fe | ||
|
9ab020518e | ||
|
838635ae4d | ||
|
a88e4932e6 | ||
|
56c94a2986 | ||
|
a4c3d9cb17 | ||
|
c31d392e19 | ||
|
a34a5c4db0 | ||
|
0441b9fb50 | ||
|
3e871ea33d | ||
|
54dda91726 | ||
|
0a19570f2c | ||
|
f280aa227e | ||
|
af34dc4d4d | ||
|
0ab1079c9c | ||
|
d2cb0bde91 | ||
|
5b34f7cf8c | ||
|
9e6e34eda0 | ||
|
cfd6cdc50f | ||
|
78346cda46 | ||
|
c547ab4570 | ||
|
939b54787e | ||
|
7b5b81504b | ||
|
5578a274f6 | ||
|
9e0c4a89f3 | ||
|
426728058c | ||
|
18390503b5 | ||
|
6e79b8404a | ||
|
4567272baa | ||
|
6ff3a37665 | ||
|
5420314784 | ||
|
f1dad4c9fb | ||
|
aba0b29c77 | ||
|
638263e8eb | ||
|
e2040b7194 | ||
|
0006e3b4bd | ||
|
06efd01032 | ||
|
1a20bd2f4d | ||
|
598cf61eb4 | ||
|
7b6772e77b | ||
|
c4bfa6d4d8 | ||
|
700ebc53f8 | ||
|
4db83293e5 | ||
|
0fcdc2e118 | ||
|
4b8d44aa2e | ||
|
915e716936 | ||
|
bb63232f55 | ||
|
43288b2e97 | ||
|
824eade8b2 | ||
|
df26058172 | ||
|
2f3219704e | ||
|
5c0532e6c6 | ||
|
a4d5b74b57 | ||
|
864106d336 | ||
|
5a612e792e | ||
|
e473dfc15e | ||
|
f931316dc8 | ||
|
e323a315e1 | ||
|
a55a2bcca0 | ||
|
f0c739c94c | ||
|
60db5abb54 | ||
|
9ab7356730 | ||
|
7f2537f9b1 | ||
|
6fe3ca1058 | ||
|
99dbb3abed | ||
|
053bdd0467 | ||
|
5554dce924 | ||
|
ec7a4734df | ||
|
40e3e71b48 | ||
|
5eed80c28e | ||
|
e2436ad796 | ||
|
7f0d82e960 | ||
|
5ecdca28fb | ||
|
67bd6eac99 | ||
|
f8ce8ece1e | ||
|
679521c8a0 | ||
|
f8c1644dee | ||
|
882e9731ac | ||
|
3cd882f618 | ||
|
98e333cef9 | ||
|
21a0063756 | ||
|
76f1a82746 | ||
|
e38dd346d9 | ||
|
a8184ec17d | ||
|
5fb751222b | ||
|
9910af04b0 | ||
|
6f5c86775b | ||
|
9c78af65e1 | ||
|
e14010da35 | ||
|
c61278f1ad | ||
|
576c668d84 | ||
|
f93a464b7d | ||
|
06271678a6 | ||
|
af0ea4d555 | ||
|
e448e87252 | ||
|
e4c54cc655 | ||
|
671338425a | ||
|
5237cf9eb1 | ||
|
33586f4bdf | ||
|
2cc05e1878 | ||
|
44c0f409a9 | ||
|
dde6d8e623 | ||
|
f387e28ed4 | ||
|
e0072630f3 | ||
|
fdf80c4584 | ||
|
68b3f1a8e6 | ||
|
f044f3ece2 | ||
|
46312cb44c | ||
|
167a548112 | ||
|
a381c91af7 | ||
|
eb0446bfe1 | ||
|
62fcf8402c | ||
|
f320f6e9b6 | ||
|
2c387f6ae6 | ||
|
702145dada | ||
|
a1973faa06 | ||
|
ac7fc587ee | ||
|
240487c258 | ||
|
4e07c7ef4b | ||
|
147791dbcd | ||
|
caf7056ce0 | ||
|
38e42cf71e | ||
|
584a12e1f5 | ||
|
d601919dc4 | ||
|
e5351c573b | ||
|
ccff5c5921 | ||
|
c3ef992219 | ||
|
c698d27a29 | ||
|
05162ebd59 | ||
|
6f991b3238 | ||
|
a14373a3e6 | ||
|
6a61dc47ac | ||
|
971729dfdd | ||
|
5c1feb2848 | ||
|
4455bf13c9 | ||
|
f28b0316f4 | ||
|
9999b3aa0c | ||
|
f9734dbc8b | ||
|
c1bd469086 | ||
|
900ba886ed | ||
|
ab0dce5dea | ||
|
6327670b6f | ||
|
7717b295bd | ||
|
b243d25399 | ||
|
59b339aba5 | ||
|
0d6ada13c4 | ||
|
dd32e61fcc | ||
|
cbf533aa7d | ||
|
5bccb1a555 | ||
|
139a364da6 | ||
|
60fcafa3b8 | ||
|
d7ef4adbc8 | ||
|
c89e5785d8 | ||
|
af910aa3f7 | ||
|
7e873880f5 | ||
|
1be0edaf03 | ||
|
609e498973 | ||
|
1585b7a142 | ||
|
c55ac659c1 | ||
|
1de52f7b64 | ||
|
26a6eb506e | ||
|
ede3e669c7 | ||
|
d00aa21c96 | ||
|
6181098f68 | ||
|
1d4c1624c4 | ||
|
899d05bc32 | ||
|
15856574d7 | ||
|
c09f8e97f7 | ||
|
4fb7f099de | ||
|
90c6283742 | ||
|
872d8584fb | ||
|
74afe433fb | ||
|
e85d6a39f8 | ||
|
2a41e55ff2 | ||
|
1e30811112 | ||
|
22f0d752ea | ||
|
4675bae804 | ||
|
57bd3c5a4e | ||
|
29db2dcd14 | ||
|
df153ee1a1 | ||
|
331c12269c | ||
|
d7844a1e39 | ||
|
d96b82500a | ||
|
74f1da81f2 | ||
|
472bc529f9 | ||
|
4266c0b279 |
6
.eslintignore
Normal file
6
.eslintignore
Normal file
@@ -0,0 +1,6 @@
|
||||
vendor/*
|
||||
!/vendor/vendor.js
|
||||
!/modules/default/**
|
||||
!/modules/node_helper
|
||||
!/modules/node_helper/**
|
||||
!/modules/default/defaultmodules.js
|
16
.eslintrc.json
Normal file
16
.eslintrc.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"rules": {
|
||||
"indent": ["error", "tab"],
|
||||
"quotes": ["error", "double"],
|
||||
"max-len": ["error", 250],
|
||||
"curly": "error",
|
||||
"camelcase": ["error", {"properties": "never"}],
|
||||
"no-trailing-spaces": ["error", {"ignoreComments": false }],
|
||||
"no-irregular-whitespace": ["error"]
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
}
|
||||
}
|
49
.github/CONTRIBUTING.md
vendored
Normal file
49
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
Contribution Policy for MagicMirror²
|
||||
====================================
|
||||
|
||||
Thanks for contributing to MagicMirror²!
|
||||
|
||||
We hold our code to standard, and these standards are documented below.
|
||||
|
||||
If you wish to run both linters, use `grunt` without any arguments.
|
||||
|
||||
### JavaScript: Run ESLint
|
||||
|
||||
We use [ESLint](http://eslint.org) on our JavaScript files.
|
||||
|
||||
Our ESLint configuration is in our .eslintrc.json and .eslintignore files.
|
||||
|
||||
To run ESLint, use `grunt eslint`.
|
||||
|
||||
### CSS: Run StyleLint
|
||||
|
||||
We use [StyleLint](http://stylelint.io) to lint our CSS. Our configuration is in our .stylelintrc file.
|
||||
|
||||
To run StyleLint, use `grunt stylelint`.
|
||||
|
||||
### Submitting Issues
|
||||
|
||||
Please only submit reproducible issues.
|
||||
|
||||
If you're not sure if it's a real bug or if it's just you, please open a topic on the forum: [https://forum.magicmirror.builders/category/15/bug-hunt](https://forum.magicmirror.builders/category/15/bug-hunt)
|
||||
Problems installing or configuring your MagicMirror? Check out: [https://forum.magicmirror.builders/category/10/troubleshooting](https://forum.magicmirror.builders/category/10/troubleshooting)
|
||||
|
||||
When submitting a new issue, please supply the following information:
|
||||
|
||||
**Platform**: Place your platform here... give us your web browser/Electron version *and* your hardware (Raspberry Pi 2/3, Windows, Mac, Linux, System V UNIX).
|
||||
|
||||
**Node Version**: Make sure it's version 0.12.13 or later.
|
||||
|
||||
**MagicMirror Version**: Now that the versions have split, tell us if you are using the PHP version (v1) or the newer JavaScript version (v2).
|
||||
|
||||
**Description**: Provide a detailed description about the issue and include specific details to help us understand the problem. Adding screenshots will help describing the problem.
|
||||
|
||||
**Steps to Reproduce**: List the step by step process to reproduce the issue.
|
||||
|
||||
**Expected Results**: Describe what you expected to see.
|
||||
|
||||
**Actual Results**: Describe what you actually saw.
|
||||
|
||||
**Configuration**: What does the used config.js file look like? Don't forget to remove any sensitive information!
|
||||
|
||||
**Additional Notes**: Provide any other relevant notes not previously mentioned. This is optional.
|
24
.github/ISSUE_TEMPLATE.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
Please only submit reproducible issues.
|
||||
|
||||
If you're not sure if it's a real bug or if it's just you, please open a topic on the forum: [https://forum.magicmirror.builders/category/15/bug-hunt](https://forum.magicmirror.builders/category/15/bug-hunt)
|
||||
Problems installing or configuring your MagicMirror? Check out: [https://forum.magicmirror.builders/category/10/troubleshooting](https://forum.magicmirror.builders/category/10/troubleshooting)
|
||||
|
||||
When submitting a new issue, please supply the following information:
|
||||
|
||||
**Platform**: Place your platform here... give us your web browser/Electron version *and* your hardware (Raspberry Pi 2/3, Windows, Mac, Linux, System V UNIX).
|
||||
|
||||
**Node Version**: Make sure it's version 0.12.13 or later.
|
||||
|
||||
**MagicMirror Version**: Now that the versions have split, tell us if you are using the PHP version (v1) or the newer JavaScript version (v2).
|
||||
|
||||
**Description**: Provide a detailed description about the issue and include specific details to help us understand the problem. Adding screenshots will help describing the problem.
|
||||
|
||||
**Steps to Reproduce**: List the step by step process to reproduce the issue.
|
||||
|
||||
**Expected Results**: Describe what you expected to see.
|
||||
|
||||
**Actual Results**: Describe what you actually saw.
|
||||
|
||||
**Configuration**: What does the used config.js file look like? Don't forget to remove any sensitive information!
|
||||
|
||||
**Additional Notes**: Provide any other relevant notes not previously mentioned. This is optional.
|
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
> 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.
|
||||
* If it includes major visual changes please add screenshots.
|
BIN
.github/header.png
vendored
Normal file
BIN
.github/header.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
.github/header.psd
vendored
Normal file
BIN
.github/header.psd
vendored
Normal file
Binary file not shown.
80
.gitignore
vendored
Normal file
80
.gitignore
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
# Various Node ignoramuses.
|
||||
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
lib-cov
|
||||
coverage
|
||||
.grunt
|
||||
.lock-wscript
|
||||
build/Release
|
||||
node_modules
|
||||
jspm_modules
|
||||
.npm
|
||||
.node_repl_history
|
||||
|
||||
# Visual Studio Code ignoramuses.
|
||||
.vscode/
|
||||
|
||||
# Various Windows ignoramuses.
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
*.lnk
|
||||
|
||||
# Various OSX ignoramuses.
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
Icon
|
||||
._*
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# Various Linux ignoramuses.
|
||||
|
||||
.fuse_hidden*
|
||||
.directory
|
||||
.Trash-*
|
||||
|
||||
# Various Magic Mirror ignoramuses and anti-ignoramuses.
|
||||
|
||||
# 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
|
14
.snyk
Normal file
14
.snyk
Normal file
@@ -0,0 +1,14 @@
|
||||
version: v1.5.2
|
||||
ignore: {}
|
||||
patch:
|
||||
'npm:minimatch:20160620':
|
||||
- snyk > recursive-readdir > minimatch:
|
||||
patched: '2016-07-30T14:02:31.280Z'
|
||||
'npm:negotiator:20160616':
|
||||
- socket.io > engine.io > accepts > negotiator:
|
||||
patched: '2016-07-30T14:02:31.280Z'
|
||||
'npm:ws:20160624':
|
||||
- socket.io > engine.io > ws:
|
||||
patched: '2016-07-30T14:02:31.280Z'
|
||||
- socket.io > socket.io-client > engine.io-client > ws:
|
||||
patched: '2016-07-30T14:02:31.280Z'
|
5
.stylelintrc
Normal file
5
.stylelintrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "stylelint-config-standard",
|
||||
"font-family-name-quotes": "double-where-recommended",
|
||||
"block-no-empty": false
|
||||
}
|
20
.travis.yml
Normal file
20
.travis.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "8"
|
||||
- "7"
|
||||
- "6"
|
||||
- "5.1"
|
||||
before_script:
|
||||
- npm install grunt-cli -g
|
||||
- "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
|
323
CHANGELOG.md
Normal file
323
CHANGELOG.md
Normal file
@@ -0,0 +1,323 @@
|
||||
# MagicMirror² Change Log
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [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`
|
||||
|
||||
### Added
|
||||
- Finnish translation.
|
||||
- Danish translation.
|
||||
- Turkish translation.
|
||||
- Option to limit access to certain IP addresses based on the value of `ipWhitelist` in the `config.js`, default is access from localhost only (Issue [#456](https://github.com/MichMich/MagicMirror/issues/456)).
|
||||
- Added ability to change the point of time when calendar events get relative.
|
||||
- Add Splash screen on boot.
|
||||
- Add option to show humidity in currentWeather module.
|
||||
- Add VSCode IntelliSense support.
|
||||
- Module API: Add Visibility locking to module system. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#visibility-locking) for more information.
|
||||
- Module API: Method to overwrite the module's header. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#getheader) for more information.
|
||||
- Module API: Option to define the minimum MagicMirror version to run a module. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#requiresversion) for more information.
|
||||
- Calendar module now broadcasts the event list to all other modules using the notification system. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/calendar) for more information.
|
||||
- Possibility to use the the calendar feed as the source for the weather (currentweather & weatherforecast) location data. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/weatherforecast) for more information.
|
||||
- Added option to show rain amount in the weatherforecast default module
|
||||
- Add module `updatenotification` to get an update whenever a new version is availabe. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/updatenotification) for more information.
|
||||
- Add the abilty to set timezone on the date display in the Clock Module
|
||||
- Ability to set date format in calendar module
|
||||
- Possibility to use currentweather for the compliments
|
||||
- Added option `disabled` for modules.
|
||||
- Added option `address` to set bind address.
|
||||
- Added option `onlyTemp` for currentweather module to show show only current temperature and weather icon.
|
||||
- Added option `remoteFile` to compliments module to load compliment array from filesystem.
|
||||
- Added option `zoom` to scale the whole mirror display with a given factor.
|
||||
- Added option `roundTemp` for currentweather and weatherforecast modules to display temperatures rounded to nearest integer.
|
||||
- Added abilty set the classes option to compliments module for style and text size of compliments.
|
||||
- Added ability to configure electronOptions
|
||||
- Calendar module: option to hide private events
|
||||
- Add root_path for global vars
|
||||
|
||||
### Updated
|
||||
- Modified translations for Frysk.
|
||||
- Modified core English translations.
|
||||
- Updated package.json as a result of Snyk security update.
|
||||
- Improve object instantiation to prevent reference errors.
|
||||
- Improve logger. `Log.log()` now accepts multiple arguments.
|
||||
- Remove extensive logging in newsfeed node helper.
|
||||
- Calendar times are now uniformly capitalized.
|
||||
- Modules are now secure, and Helmet is now used to prevent abuse of the Mirror's API.
|
||||
|
||||
### Fixed
|
||||
- Solve an issue where module margins would appear when the first module of a section was hidden.
|
||||
- Solved visual display errors on chrome, if all modules in one of the right sections are hidden.
|
||||
- Global and Module default config values are no longer modified when setting config values.
|
||||
- Hide a region if all modules in a region are hidden. Prevention unwanted margins.
|
||||
- Replaced `electron-prebuilt` package with `electron` in order to fix issues that would happen after 2017.
|
||||
- Documentation of alert module
|
||||
|
||||
## [2.0.5] - 2016-09-20
|
||||
|
||||
### Added
|
||||
- Added ability to remove tags from the beginning or end of newsfeed items in 'newsfeed.js'.
|
||||
- Added ability to define "the day after tomorrow" for calendar events (Definition for German and Dutch already included).
|
||||
- Added CII Badge (we are compliant with the CII Best Practices)
|
||||
- Add support for doing http basic auth when loading calendars
|
||||
- Add the abilty to turn off and on the date display in the Clock Module
|
||||
|
||||
### Fixed
|
||||
- Fix typo in installer.
|
||||
- Add message to unsupported Pi error to mention that Pi Zeros must use server only mode, as ARMv6 is unsupported. Closes #374.
|
||||
- Fix API url for weather API.
|
||||
|
||||
### Updated
|
||||
- Force fullscreen when kioskmode is active.
|
||||
- Update the .github templates and information with more modern information.
|
||||
- Update the Gruntfile with a more functional StyleLint implementation.
|
||||
|
||||
## [2.0.4] - 2016-08-07
|
||||
|
||||
### Added
|
||||
- Brazilian Portuguese Translation.
|
||||
- Option to enable Kiosk mode.
|
||||
- Added ability to start the app with Dev Tools.
|
||||
- Added ability to turn off the date display in `clock.js` when in analog mode.
|
||||
- Greek Translation
|
||||
|
||||
### Fixed
|
||||
- Prevent `getModules()` selectors from returning duplicate entries.
|
||||
- Append endpoints of weather modules with `/` to retreive the correct data. (Issue [#337](https://github.com/MichMich/MagicMirror/issues/337))
|
||||
- Corrected grammer in `module.js` from 'suspend' to 'suspended'.
|
||||
- Fixed openweathermap.org URL in config sample.
|
||||
- Prevent currentweather module from crashing when received data object is incorrect.
|
||||
- Fix issue where translation loading prevented the UI start-up when the language was set to 'en'. (Issue [#388](https://github.com/MichMich/MagicMirror/issues/388))
|
||||
|
||||
### Updated
|
||||
- Updated package.json to fix possible vulnerabilities. (Using Snyk)
|
||||
- Updated weathericons
|
||||
- Updated default weatherforecast to work with the new icons.
|
||||
- More detailed error message in case config file couldn't be loaded.
|
||||
|
||||
## [2.0.3] - 2016-07-12
|
||||
### Added
|
||||
- Add max newsitems parameter to the newsfeed module.
|
||||
- Translations for Simplified Chinese, Traditional Chinese and Japanese.
|
||||
- Polish Translation
|
||||
- Add an analog clock in addition to the digital one.
|
||||
|
||||
### Fixed
|
||||
- Edit Alert Module to display title & message if they are provided in the notification (Issue [#300](https://github.com/MichMich/MagicMirror/issues/300))
|
||||
- Removed 'null' reference from updateModuleContent(). This fixes recent Edge and Internet Explorer browser displays (Issue [#319](https://github.com/MichMich/MagicMirror/issues/319))
|
||||
|
||||
### Changed
|
||||
- Added default string to calendar titleReplace.
|
||||
|
||||
## [2.0.2] - 2016-06-05
|
||||
### Added
|
||||
- Norwegian Translations (nb and nn)
|
||||
- Portuguese Translation
|
||||
- Swedish Translation
|
||||
|
||||
### Fixed
|
||||
- Added reference to Italian Translation.
|
||||
- Added the missing NE translation to all languages. [#344](https://github.com/MichMich/MagicMirror/issues/344)
|
||||
- Added proper User-Agent string to calendar call.
|
||||
|
||||
### Changed
|
||||
- Add option to use locationID in weather modules.
|
||||
|
||||
## [2.0.1] - 2016-05-18
|
||||
### Added
|
||||
- Changelog
|
||||
- Italian Translation
|
||||
|
||||
### Changed
|
||||
- Improve the installer by fetching the latest Node.js without any 3rd party interferences.
|
||||
|
||||
## [2.0.0] - 2016-05-03
|
||||
### Initial release of MagicMirror²
|
||||
It includes (but is not limited to) the following features:
|
||||
- Modular system allowing 3rd party plugins.
|
||||
- An Node/Electron based application taking away the need for external servers or browsers.
|
||||
- A complete development API documentation.
|
||||
- Small cute fairies that kiss you while you sleep.
|
||||
|
||||
## [1.0.0] - 2014-02-16
|
||||
### Initial release of MagicMirror.
|
||||
This was part of the blogpost: [http://michaelteeuw.nl/post/83916869600/magic-mirror-part-vi-production-of-the](http://michaelteeuw.nl/post/83916869600/magic-mirror-part-vi-production-of-the)
|
103
Gruntfile.js
Normal file
103
Gruntfile.js
Normal file
@@ -0,0 +1,103 @@
|
||||
module.exports = function(grunt) {
|
||||
require("time-grunt")(grunt);
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON("package.json"),
|
||||
eslint: {
|
||||
options: {
|
||||
configFile: ".eslintrc.json"
|
||||
},
|
||||
target: [
|
||||
"js/*.js",
|
||||
"modules/default/*.js",
|
||||
"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: {
|
||||
simple: {
|
||||
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"
|
||||
]
|
||||
}
|
||||
},
|
||||
jsonlint: {
|
||||
main: {
|
||||
src: [
|
||||
"package.json",
|
||||
".eslintrc.json",
|
||||
".stylelintrc",
|
||||
"translations/*.json",
|
||||
"modules/default/*/translations/*.json",
|
||||
"installers/pm2_MagicMirror.json",
|
||||
"vendor/package.js"
|
||||
],
|
||||
options: {
|
||||
reporter: "jshint"
|
||||
}
|
||||
}
|
||||
},
|
||||
markdownlint: {
|
||||
all: {
|
||||
options: {
|
||||
config: {
|
||||
"default": true,
|
||||
"line-length": false,
|
||||
"blanks-around-headers": false,
|
||||
"no-duplicate-header": false,
|
||||
"no-inline-html": false,
|
||||
"MD010": false,
|
||||
"MD001": false,
|
||||
"MD031": false,
|
||||
"MD040": false,
|
||||
"MD002": false,
|
||||
"MD029": false,
|
||||
"MD041": false,
|
||||
"MD032": false,
|
||||
"MD036": false,
|
||||
"MD037": false,
|
||||
"MD009": false,
|
||||
"MD018": false,
|
||||
"MD012": false,
|
||||
"MD026": false,
|
||||
"MD038": false
|
||||
}
|
||||
},
|
||||
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"
|
||||
]
|
||||
}
|
||||
});
|
||||
grunt.loadNpmTasks("grunt-eslint");
|
||||
grunt.loadNpmTasks("grunt-stylelint");
|
||||
grunt.loadNpmTasks("grunt-jsonlint");
|
||||
grunt.loadNpmTasks("grunt-yamllint");
|
||||
grunt.loadNpmTasks("grunt-markdownlint");
|
||||
grunt.registerTask("default", ["eslint", "stylelint", "jsonlint", "markdownlint", "yamllint"]);
|
||||
};
|
18
LICENSE.md
Normal file
18
LICENSE.md
Normal file
@@ -0,0 +1,18 @@
|
||||
The MIT License (MIT)
|
||||
=====================
|
||||
|
||||
Copyright © 2016-2017 Michael Teeuw
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the “Software”), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
**The software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.**
|
188
README.md
188
README.md
@@ -1,52 +1,186 @@
|
||||
MagicMirror
|
||||
===========
|
||||

|
||||
|
||||
##Introduction
|
||||
<p align="center">
|
||||
<a href="https://david-dm.org/MichMich/MagicMirror"><img src="https://david-dm.org/MichMich/MagicMirror.svg" alt="Dependency Status"></a>
|
||||
<a href="https://david-dm.org/MichMich/MagicMirror#info=devDependencies"><img src="https://david-dm.org/MichMich/MagicMirror/dev-status.svg" alt="devDependency Status"></a>
|
||||
<a href="https://bestpractices.coreinfrastructure.org/projects/347"><img src="https://bestpractices.coreinfrastructure.org/projects/347/badge"></a>
|
||||
<a href="http://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
||||
<a href="https://travis-ci.org/MichMich/MagicMirror"><img src="https://travis-ci.org/MichMich/MagicMirror.svg" alt="Travis"></a>
|
||||
<a href="https://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>
|
||||
|
||||
The super magic interface of my personal Magic Mirror. More information about this project can be found on my [blog](http://michaelteeuw.nl/tagged/magicmirror).
|
||||
**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).
|
||||
|
||||
Runs as a php script on a web server with basically no external dependencies. *Can use socket.io for XBEE integration, but isn't required for basic functionality*.
|
||||
MagicMirror² focuses on a modular plugin system and uses [Electron](http://electron.atom.io/) as an application wrapper. So no more web server or browser installs necessary!
|
||||
|
||||
## Table Of Contents
|
||||
|
||||
##Configuration
|
||||
- [Usage](#usage)
|
||||
- [Configuration](#configuration)
|
||||
- [Modules](#modules)
|
||||
- [Known Issues](#known-issues)
|
||||
- [Community](#community)
|
||||
- [Contributing Guidelines](#contributing-guidelines)
|
||||
|
||||
Modify `js/config.js` to change some general variables (language, weather location, compliments, news feed RSS and to add your own ICS calendar)
|
||||
## Usage
|
||||
|
||||
To use the OpenWeatherMap API, you'll need a free API key. Checkout [this blogpost](http://michaelteeuw.nl/post/131504229357/what-happened-to-the-weather) for more information.
|
||||
### 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.
|
||||
|
||||
##Code
|
||||
### Automatic Installer (Raspberry Pi Only!)
|
||||
|
||||
###[main.js](js/main.js)
|
||||
Execute the following command on your Raspberry Pi to install MagicMirror²:
|
||||
````
|
||||
bash -c "$(curl -sL https://raw.githubusercontent.com/MichMich/MagicMirror/master/installers/raspberry.sh)"
|
||||
````
|
||||
|
||||
This file initiates the separate pieces of functionality that will appear in the view. It also includes various utility functions that are used to update what is visible.
|
||||
### Manual Installation
|
||||
|
||||
###[Calendar](js/calendar)
|
||||
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: `npm install && npm start`
|
||||
|
||||
Parsing functionality for the calendar that retrieves and updates the calendar based on the interval set at the top of the [calendar.js](js/calendar/calendar.js) file. This was actually a straight pull from the original main.js file but the parsing code may deserve an upgrade.
|
||||
**Important:** `npm start` does **not** work via SSH, use `DISPLAY=:0 nohup npm start &` instead. This starts the mirror on the remote display.
|
||||
|
||||
###[Compliments](js/compliments)
|
||||
**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.
|
||||
|
||||
Functionality related to inserting compliments into the view and rotating them based on a specific interval set at the top of the [compliments.js](js/compliments/compliments.js) file.
|
||||
### Server Only
|
||||
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.
|
||||
|
||||
###[News](js/news)
|
||||
### Client Only
|
||||
When you have a server running remotely and want to connect a standalone client to this instance, you can manually run `node clientonly --address 192.168.1.5 --port 8080`. (Specify the ip address and port number of the server)
|
||||
|
||||
Takes an array of news feeds (or a single string) from the config file and retrieves each one so that it can be displayed in a loop based on the interval set at the top of the [news.js](js/news/news.js) file.
|
||||
**Important:** Make sure that you whitelist the interface/ip in the server config where you want the client to connect to, otherwise it will not be allowed to connect to the server
|
||||
|
||||
###[Time](js/time)
|
||||
#### Docker
|
||||
|
||||
Updates the time on the screen on one second interval.
|
||||
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:
|
||||
|
||||
###[Version](js/version)
|
||||
```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
|
||||
```
|
||||
|
||||
Checks the git version and refreshes if a new version has been pulled.
|
||||
| **Volumes** | **Description** |
|
||||
| --- | --- |
|
||||
| `/opt/magic_mirror/config` | Mount this volume to insert your own config into the docker container. |
|
||||
| `/opt/magic_mirror/modules` | Mount this volume to add your own custom modules into the docker container. |
|
||||
|
||||
###[Weather](js/weather)
|
||||
You may need to add your Docker Host IP to your `ipWhitelist` option. If you have some issues setting up this configuration, check [this forum post](https://forum.magicmirror.builders/topic/1326/ipwhitelist-howto).
|
||||
|
||||
Takes the user's inserted location, language, unit type, and OpenWeatherMap API key and grabs the five day weather forecast from OpenWeatherMap. You need to set the API key in the config for this to work. (See *configuration*.)
|
||||
```javascript
|
||||
var config = {
|
||||
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:172.17.0.1"]
|
||||
};
|
||||
```
|
||||
|
||||
##Extensions
|
||||
If you want to run the server on a raspberry pi, use the `raspberry` tag. (bastilimbach/docker-magicmirror:raspberry)
|
||||
|
||||
###[MagicMirror-Extensions by PaViRo](https://github.com/paviro/MagicMirror-Extensions)
|
||||
#### Manual
|
||||
|
||||
**Current features:** FRITZ!Box Callmonitor <br>
|
||||
**Future features:** Faceregognition, personalized views, online banking through HBCI and multiple calenders based on faceregognition.
|
||||
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: `npm install && node serveronly`
|
||||
|
||||
### Raspberry Configuration & Auto Start.
|
||||
|
||||
The following wiki links are helpful in the configuration of your MagicMirror² operating system:
|
||||
- [Configuring the Raspberry Pi](https://github.com/MichMich/MagicMirror/wiki/Configuring-the-Raspberry-Pi)
|
||||
- [Auto Starting MagicMirror](https://github.com/MichMich/MagicMirror/wiki/Auto-Starting-MagicMirror)
|
||||
|
||||
### Updating 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
|
||||
```
|
||||
|
||||
If you changed nothing more than the config or the modules, this should work without any problems.
|
||||
Type `git status` to see your changes, if there are any, you can reset them with `git reset --hard`. After that, git pull should be possible.
|
||||
|
||||
## Configuration
|
||||
|
||||
1. Duplicate `config/config.js.sample` to `config/config.js`. **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 the follow command:
|
||||
```bash
|
||||
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 `localhost`. 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"]]`). Set `[]` to allow all IP addresses. For more information about how configure this directive see the [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 (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:
|
||||
|
||||
| **Option** | **Description** |
|
||||
| --- | --- |
|
||||
| `module` | The name of the module. This can also contain the subfolder. Valid examples include `clock`, `default/calendar` and `custommodules/mymodule`. |
|
||||
| `position` | The location of the module in which the module will be loaded. Possible values are `top_ bar`, `top_left`, `top_center`, `top_right`, `upper_third`, `middle_center`, `lower_third`, `bottom_left`, `bottom_center`, `bottom_right`, `bottom_bar`, `fullscreen_above`, and `fullscreen_below`. This field is optional but most modules require this field to set. Check the documentation of the module for more information. Multiple modules with the same position will be ordered based on the order in the configuration file. |
|
||||
| `classes` | Additional classes which are passed to the module. The field is optional. |
|
||||
| `header` | To display a header text above the module, add the header property. This field is optional. |
|
||||
| `disabled` | Set disabled to `true` to skip creating the module. This field is optional. |
|
||||
| `config` | An object with the module configuration properties. Check the documentation of the module for more information. This field is optional, unless the module requires extra configuration. |
|
||||
|
||||
## Modules
|
||||
|
||||
The following modules are installed by default.
|
||||
|
||||
- [**Clock**](modules/default/clock)
|
||||
- [**Calendar**](modules/default/calendar)
|
||||
- [**Current Weather**](modules/default/currentweather)
|
||||
- [**Weather Forecast**](modules/default/weatherforecast)
|
||||
- [**News Feed**](modules/default/newsfeed)
|
||||
- [**Compliments**](modules/default/compliments)
|
||||
- [**Hello World**](modules/default/helloworld)
|
||||
- [**Alert**](modules/default/alert)
|
||||
|
||||
For more available modules, check out out the wiki page: [MagicMirror² Modules](https://github.com/MichMich/MagicMirror/wiki/MagicMirror²-Modules). If you want to build your own modules, check out the [MagicMirror² Module Development Documentation](modules) and don't forget to add it to the wiki and the [forum](https://forum.magicmirror.builders/category/7/showcase)!
|
||||
|
||||
## Known issues
|
||||
|
||||
- Electron seems to have some issues on certain Raspberry Pi 2's. See [#145](https://github.com/MichMich/MagicMirror/issues/145).
|
||||
- MagicMirror² (Electron) sometimes quits without an error after an extended period of use. See [#150](https://github.com/MichMich/MagicMirror/issues/150).
|
||||
|
||||
## Community
|
||||
|
||||
The community around the MagicMirror² is constantly growing. We even have a [forum](https://forum.magicmirror.builders) now where you can share your ideas, ask questions, help others and get inspired by other builders. We would love to see you there!
|
||||
|
||||
## Contributing Guidelines
|
||||
|
||||
Contributions of all kinds are welcome, not only in the form of code but also with regards bug reports and documentation.
|
||||
|
||||
Please keep the following in mind:
|
||||
|
||||
- **Bug Reports**: Make sure you're running the latest version. If the issue(s) still persist: please open a clearly documented issue with a clear title.
|
||||
- **Minor Bug Fixes**: Please send a pull request with a clear explanation of the issue or a link to the issue it solves.
|
||||
- **Major Bug Fixes**: please discuss your approach in an GitHub issue before you start to alter a big part of the code.
|
||||
- **New Features**: please please discuss in a GitHub issue before you start to alter a big part of the code. Without discussion upfront, the pull request will not be accepted / merged.
|
||||
|
||||
Thanks for your help in making MagicMirror² better!
|
||||
|
||||
<p align="center">
|
||||
<br>
|
||||
<a href="https://forum.magicmirror.builders/topic/728/magicmirror-is-voted-number-1-in-the-magpi-top-50"><img src="https://magicmirror.builders/img/magpi-best-watermark-custom.png" width="150" alt="MagPi Top 50"></a>
|
||||
</p>
|
||||
|
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
config/.gitignore
vendored
Normal file
2
config/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!config.js.sample
|
95
config/config.js.sample
Normal file
95
config/config.js.sample
Normal file
@@ -0,0 +1,95 @@
|
||||
/* Magic Mirror Config Sample
|
||||
*
|
||||
* 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"], // 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",
|
||||
timeFormat: 24,
|
||||
units: "metric",
|
||||
|
||||
modules: [
|
||||
{
|
||||
module: "alert",
|
||||
},
|
||||
{
|
||||
module: "updatenotification",
|
||||
position: "top_bar"
|
||||
},
|
||||
{
|
||||
module: "clock",
|
||||
position: "top_left"
|
||||
},
|
||||
{
|
||||
module: "calendar",
|
||||
header: "US Holidays",
|
||||
position: "top_left",
|
||||
config: {
|
||||
calendars: [
|
||||
{
|
||||
symbol: "calendar-check-o ",
|
||||
url: "webcal://www.calendarlabs.com/templates/ical/US-Holidays.ics"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
module: "compliments",
|
||||
position: "lower_third"
|
||||
},
|
||||
{
|
||||
module: "currentweather",
|
||||
position: "top_right",
|
||||
config: {
|
||||
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",
|
||||
config: {
|
||||
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",
|
||||
config: {
|
||||
feeds: [
|
||||
{
|
||||
title: "New York Times",
|
||||
url: "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml"
|
||||
}
|
||||
],
|
||||
showSourceTitle: true,
|
||||
showPublishDate: true
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {module.exports = config;}
|
@@ -1,5 +0,0 @@
|
||||
<?php
|
||||
include "functions/gzip.php";
|
||||
$url = $_GET["url"];
|
||||
echo get_url($url);
|
||||
?>
|
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
/*
|
||||
* @function get_url
|
||||
* @purpose To fetch GZipped web content.
|
||||
* @author Michael Teeuw
|
||||
*/
|
||||
function get_url($url) {
|
||||
/*
|
||||
* @array
|
||||
* Prepare the options that we need for our GZip request.
|
||||
*/
|
||||
$opts = array(
|
||||
"http" => array(
|
||||
"method" => "GET",
|
||||
"header" => "Accept-Language: en-US,en;q=0.8rn" . "Accept-Encoding: gzip,deflate,sdchrn" . "Accept-Charset:UTF-8,*;q=0.5rn" . "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:19.0) Gecko/20100101 Firefox/19.0 FirePHP/0.4rn",
|
||||
"ignore_errors" => true
|
||||
),
|
||||
/*
|
||||
* @array
|
||||
* Put a Band-Aid over some SSL issues.
|
||||
*/
|
||||
"ssl" => array(
|
||||
"verify_peer" => false,
|
||||
"verify_peer_name" => false
|
||||
)
|
||||
);
|
||||
$context = stream_context_create($opts);
|
||||
$content = file_get_contents($url, false, $context);
|
||||
/*
|
||||
* @note If http response header mentions that content is gzipped, then uncompress it.
|
||||
*/
|
||||
foreach($http_response_header as $c => $h) {
|
||||
if(stristr($h, "content-encoding") and stristr($h, "gzip")) {
|
||||
/*
|
||||
* @note Now, let's begin the actual purpose of this function:
|
||||
*/
|
||||
$content = gzinflate(substr($content, 10, -8));
|
||||
}
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
?>
|
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
echo json_encode(
|
||||
array(
|
||||
"gitHash" => trim(`git rev-parse HEAD`)
|
||||
)
|
||||
);
|
||||
?>
|
14
css/custom.css
Normal file
14
css/custom.css
Normal file
@@ -0,0 +1,14 @@
|
||||
/*****************************************************
|
||||
* Magic Mirror *
|
||||
* Custom CSS *
|
||||
* *
|
||||
* By Michael Teeuw http://michaelteeuw.nl *
|
||||
* MIT Licensed. *
|
||||
* *
|
||||
* Add any custom CSS below. *
|
||||
* Changes to this files will be ignored by GIT. *
|
||||
*****************************************************/
|
||||
|
||||
body {
|
||||
|
||||
}
|
400
css/main.css
400
css/main.css
@@ -1,224 +1,242 @@
|
||||
body,
|
||||
html {
|
||||
cursor: none;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: "HelveticaNeue-Light", sans-serif;
|
||||
letter-spacing: -2px;
|
||||
color: #fff;
|
||||
font-size: 75px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 60px;
|
||||
position: absolute;
|
||||
height: calc(100% - 120px);
|
||||
width: calc(100% - 120px);
|
||||
background: #000;
|
||||
color: #aaa;
|
||||
font-family: "Roboto Condensed", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 2em;
|
||||
line-height: 1.5em;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: geometricprecision;
|
||||
}
|
||||
|
||||
.wi {
|
||||
line-height: 75px;
|
||||
/**
|
||||
* Default styles.
|
||||
*/
|
||||
|
||||
.dimmed {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.top {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
.normal {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.left {
|
||||
position: absolute;
|
||||
left: 50px;
|
||||
}
|
||||
|
||||
.right {
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.center-ver {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
height: 200px;
|
||||
margin-top: -100px;
|
||||
line-height: 100px;
|
||||
}
|
||||
|
||||
.lower-third {
|
||||
position: absolute;
|
||||
top: 66.666%;
|
||||
height: 200px;
|
||||
margin-top: -100px;
|
||||
line-height: 100px;
|
||||
}
|
||||
|
||||
.center-hor {
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
left: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
position: absolute;
|
||||
bottom: 50px;
|
||||
}
|
||||
|
||||
.xxsmall,
|
||||
.xsmall,
|
||||
.small {
|
||||
font-family: "HelveticaNeue-Medium", sans-serif;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.xxsmall {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.xxsmall .wi {
|
||||
line-height: 15px;
|
||||
.bright {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.xsmall {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.xsmall .wi {
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.small .wi {
|
||||
font-size: 20px;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.medium {
|
||||
font-size: 35px;
|
||||
letter-spacing: -1px;
|
||||
font-family: "HelveticaNeue-Light", sans-serif;
|
||||
}
|
||||
|
||||
.medium .wi {
|
||||
font-size: 30px;
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
.xdimmed {
|
||||
color: #666;
|
||||
.large {
|
||||
font-size: 65px;
|
||||
line-height: 65px;
|
||||
}
|
||||
|
||||
.dimmed {
|
||||
color: #aaa;
|
||||
.xlarge {
|
||||
font-size: 75px;
|
||||
line-height: 75px;
|
||||
letter-spacing: -3px;
|
||||
}
|
||||
|
||||
.thin {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.light {
|
||||
font-family: "HelveticaNeue-UltraLight", sans-serif;
|
||||
font-family: "Roboto Condensed", sans-serif;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
top: -10px;
|
||||
display: inline-block;
|
||||
font-size: 45px;
|
||||
padding-right: 5px;
|
||||
font-weight: 100;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.icon-small {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
font-size: 20px;
|
||||
padding-left: 10px;
|
||||
padding-right: -10px;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.time .sec {
|
||||
font-size: 25px;
|
||||
color: #666;
|
||||
padding-left: 5px;
|
||||
position: relative;
|
||||
top: -35px;
|
||||
}
|
||||
|
||||
.forecast-table {
|
||||
float: right;
|
||||
text-align: right;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.forecast-table .day,
|
||||
.forecast-table .temp-min,
|
||||
.forecast-table .temp-max {
|
||||
width: 50px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.forecast-table .temp-max {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.forecast-table .day {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.calendar-table {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.calendar-table .days {
|
||||
padding-left: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dishwasher {
|
||||
background-color: white;
|
||||
color: black;
|
||||
margin: 0 200px;
|
||||
font-size: 60px;
|
||||
border-radius: 1000px;
|
||||
border-radius: 1200px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'HelveticaNeue-UltraLight';
|
||||
src: url('font/HelveticaNeue-UltraLight.eot');
|
||||
/* IE9 Compat Modes */
|
||||
src: url('font/HelveticaNeue-UltraLight.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('font/HelveticaNeue-UltraLight.woff') format('woff'), /* Modern Browsers */
|
||||
url('font/HelveticaNeue-UltraLight.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('font/HelveticaNeue-UltraLight.svg#9453ea8da727d260bcdbfa605bdbb5d2') format('svg');
|
||||
/* Legacy iOS */
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'HelveticaNeue-Medium';
|
||||
src: url('font/HelveticaNeue-Medium.eot');
|
||||
/* IE9 Compat Modes */
|
||||
src: url('font/HelveticaNeue-Medium.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('font/HelveticaNeue-Medium.woff') format('woff'), /* Modern Browsers */
|
||||
url('font/HelveticaNeue-Medium.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('font/HelveticaNeue-Medium.svg#d7af0fd9278f330eed98b60dddea7bd6') format('svg');
|
||||
/* Legacy iOS */
|
||||
font-style: normal;
|
||||
.regular {
|
||||
font-family: "Roboto Condensed", sans-serif;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'HelveticaNeue-Light';
|
||||
src: url('font/HelveticaNeue-Light.eot');
|
||||
/* IE9 Compat Modes */
|
||||
src: url('font/HelveticaNeue-Light.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('font/HelveticaNeue-Light.woff') format('woff'), /* Modern Browsers */
|
||||
url('font/HelveticaNeue-Light.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('font/HelveticaNeue-Light.svg#7384ecabcada72f0e077cd45d8e1c705') format('svg');
|
||||
/* Legacy iOS */
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
}
|
||||
.bold {
|
||||
font-family: "Roboto Condensed", sans-serif;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
header {
|
||||
text-transform: uppercase;
|
||||
font-size: 15px;
|
||||
font-family: "Roboto Condensed";
|
||||
font-weight: 400;
|
||||
border-bottom: 1px solid #666;
|
||||
line-height: 15px;
|
||||
padding-bottom: 5px;
|
||||
margin-bottom: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
sup {
|
||||
font-size: 50%;
|
||||
line-height: 50%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Module styles.
|
||||
*/
|
||||
|
||||
.module {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.region.bottom .module {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.no-wrap {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Region Definitions.
|
||||
*/
|
||||
|
||||
.region {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.region.fullscreen {
|
||||
position: absolute;
|
||||
top: -60px;
|
||||
left: -60px;
|
||||
right: -60px;
|
||||
bottom: -60px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.region.fullscreen * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.region.right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.region.top {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.region.top .container {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.region.top .container:empty {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.region.top.center,
|
||||
.region.bottom.center {
|
||||
left: 50%;
|
||||
-moz-transform: translateX(-50%);
|
||||
-o-transform: translateX(-50%);
|
||||
-webkit-transform: translateX(-50%);
|
||||
-ms-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.region.top.right,
|
||||
.region.top.left,
|
||||
.region.top.center {
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
.region.bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.region.bottom .container {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.region.bottom .container:empty {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.region.bottom.right,
|
||||
.region.bottom.center,
|
||||
.region.bottom.left {
|
||||
bottom: 100%;
|
||||
}
|
||||
|
||||
.region.bar {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.region.third,
|
||||
.region.middle.center {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
-moz-transform: translateY(-50%);
|
||||
-o-transform: translateY(-50%);
|
||||
-webkit-transform: translateY(-50%);
|
||||
-ms-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.region.upper.third {
|
||||
top: 33%;
|
||||
}
|
||||
|
||||
.region.middle.center {
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.region.lower.third {
|
||||
top: 66%;
|
||||
}
|
||||
|
||||
.region.left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.region.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.region table {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
border-collapse: separate;
|
||||
}
|
||||
|
@@ -1,327 +0,0 @@
|
||||
/*!
|
||||
* Weather Icons Beta 1
|
||||
* Weather themed icons for Bootstrap
|
||||
* ------------------------------------------------------------------------------
|
||||
* Maintained at http://erikflowers.github.io/weather-icons
|
||||
* http://twitter.com/Erik_UX
|
||||
*
|
||||
* License
|
||||
* ------------------------------------------------------------------------------
|
||||
* - Fpmt licensed under SIL OFL 1.1 -
|
||||
* http://scripts.sil.org/OFL
|
||||
* - CSS and LESS are licensed under MIT License -
|
||||
* http://opensource.org/licenses/mit-license.html
|
||||
* - Documentation licensed under CC BY 3.0 -
|
||||
* http://creativecommons.org/licenses/by/3.0/
|
||||
* - Inspired by and works great as a companion with Font Aweosme
|
||||
* "Font Awesome by Dave Gandy - http://fontawesome.io"
|
||||
*
|
||||
* Weather Icons Bootstrap Package Author - Erik Flowers - erik@helloerik.com
|
||||
* Weather Icons gives full credit for inspiration to Font Awesome and makes no
|
||||
* claim to invention, intellectual property, or ownership of methodology.
|
||||
*
|
||||
* Support Open Source!
|
||||
*
|
||||
* ------------------------------------------------------------------------------
|
||||
* Email: erik@helloerik.com
|
||||
* Twitter: http://twitter.com/Erik_UX
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'weather';
|
||||
src: url('../font/weathericons-regular-webfont.eot');
|
||||
src: url('../font/weathericons-regular-webfont.eot?#iefix') format('embedded-opentype'), url('../font/weathericons-regular-webfont.woff') format('woff'), url('../font/weathericons-regular-webfont.ttf') format('truetype'), url('../font/weathericons-regular-webfont.svg#weathericons-regular-webfontRg') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
[class^="wi-"],
|
||||
[class*=" wi-"] {
|
||||
font-family: weather;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
text-decoration: inherit;
|
||||
text-transform: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
*margin-right: .3em;
|
||||
}
|
||||
[class^="wi-"]:before,
|
||||
[class*=" wi-"]:before {
|
||||
text-decoration: inherit;
|
||||
display: inline-block;
|
||||
speak: none;
|
||||
}
|
||||
.wi-day-cloudy-gusts:before {
|
||||
content: "\f000";
|
||||
}
|
||||
.wi-day-cloudy-windy:before {
|
||||
content: "\f001";
|
||||
}
|
||||
.wi-day-cloudy:before {
|
||||
content: "\f002";
|
||||
}
|
||||
.wi-day-fog:before {
|
||||
content: "\f003";
|
||||
}
|
||||
.wi-day-hail:before {
|
||||
content: "\f004";
|
||||
}
|
||||
.wi-day-lightning:before {
|
||||
content: "\f005";
|
||||
}
|
||||
.wi-day-rain-mix:before {
|
||||
content: "\f006";
|
||||
}
|
||||
.wi-day-rain-wind:before {
|
||||
content: "\f007";
|
||||
}
|
||||
.wi-day-rain:before {
|
||||
content: "\f008";
|
||||
}
|
||||
.wi-day-showers:before {
|
||||
content: "\f009";
|
||||
}
|
||||
.wi-day-snow:before {
|
||||
content: "\f00a";
|
||||
}
|
||||
.wi-day-sprinkle:before {
|
||||
content: "\f00b";
|
||||
}
|
||||
.wi-day-sunny-overcast:before {
|
||||
content: "\f00c";
|
||||
}
|
||||
.wi-day-sunny:before {
|
||||
content: "\f00d";
|
||||
}
|
||||
.wi-day-storm-showers:before {
|
||||
content: "\f00e";
|
||||
}
|
||||
.wi-day-thunderstorm:before {
|
||||
content: "\f010";
|
||||
}
|
||||
.wi-cloudy-gusts:before {
|
||||
content: "\f011";
|
||||
}
|
||||
.wi-cloudy-windy:before {
|
||||
content: "\f012";
|
||||
}
|
||||
.wi-cloudy:before {
|
||||
content: "\f013";
|
||||
}
|
||||
.wi-fog:before {
|
||||
content: "\f014";
|
||||
}
|
||||
.wi-hail:before {
|
||||
content: "\f015";
|
||||
}
|
||||
.wi-lightning:before {
|
||||
content: "\f016";
|
||||
}
|
||||
.wi-rain-mix:before {
|
||||
content: "\f017";
|
||||
}
|
||||
.wi-rain-wind:before {
|
||||
content: "\f018";
|
||||
}
|
||||
.wi-rain:before {
|
||||
content: "\f019";
|
||||
}
|
||||
.wi-showers:before {
|
||||
content: "\f01a";
|
||||
}
|
||||
.wi-snow:before {
|
||||
content: "\f01b";
|
||||
}
|
||||
.wi-sprinkle:before {
|
||||
content: "\f01c";
|
||||
}
|
||||
.wi-storm-showers:before {
|
||||
content: "\f01d";
|
||||
}
|
||||
.wi-thunderstorm:before {
|
||||
content: "\f01e";
|
||||
}
|
||||
.wi-windy:before {
|
||||
content: "\f021";
|
||||
}
|
||||
.wi-night-alt-cloudy-gusts:before {
|
||||
content: "\f022";
|
||||
}
|
||||
.wi-night-alt-cloudy-windy:before {
|
||||
content: "\f023";
|
||||
}
|
||||
.wi-night-alt-hail:before {
|
||||
content: "\f024";
|
||||
}
|
||||
.wi-night-alt-lightning:before {
|
||||
content: "\f025";
|
||||
}
|
||||
.wi-night-alt-rain-mix:before {
|
||||
content: "\f026";
|
||||
}
|
||||
.wi-night-alt-rain-wind:before {
|
||||
content: "\f027";
|
||||
}
|
||||
.wi-night-alt-rain:before {
|
||||
content: "\f028";
|
||||
}
|
||||
.wi-night-alt-showers:before {
|
||||
content: "\f029";
|
||||
}
|
||||
.wi-night-alt-snow:before {
|
||||
content: "\f02a";
|
||||
}
|
||||
.wi-night-alt-sprinkle:before {
|
||||
content: "\f02b";
|
||||
}
|
||||
.wi-night-alt-storm-showers:before {
|
||||
content: "\f02c";
|
||||
}
|
||||
.wi-night-alt-thunderstorm:before {
|
||||
content: "\f02d";
|
||||
}
|
||||
.wi-night-clear:before {
|
||||
content: "\f02e";
|
||||
}
|
||||
.wi-night-cloudy-gusts:before {
|
||||
content: "\f02f";
|
||||
}
|
||||
.wi-night-cloudy-windy:before {
|
||||
content: "\f030";
|
||||
}
|
||||
.wi-night-cloudy:before {
|
||||
content: "\f031";
|
||||
}
|
||||
.wi-night-hail:before {
|
||||
content: "\f032";
|
||||
}
|
||||
.wi-night-lightning:before {
|
||||
content: "\f033";
|
||||
}
|
||||
.wi-night-rain-mix:before {
|
||||
content: "\f034";
|
||||
}
|
||||
.wi-night-rain-wind:before {
|
||||
content: "\f035";
|
||||
}
|
||||
.wi-night-rain:before {
|
||||
content: "\f036";
|
||||
}
|
||||
.wi-night-showers:before {
|
||||
content: "\f037";
|
||||
}
|
||||
.wi-night-snow:before {
|
||||
content: "\f038";
|
||||
}
|
||||
.wi-night-sprinkle:before {
|
||||
content: "\f039";
|
||||
}
|
||||
.wi-night-storm-showers:before {
|
||||
content: "\f03a";
|
||||
}
|
||||
.wi-night-thunderstorm:before {
|
||||
content: "\f03b";
|
||||
}
|
||||
.wi-celcius:before {
|
||||
content: "\f03c";
|
||||
}
|
||||
.wi-cloud-down:before {
|
||||
content: "\f03d";
|
||||
}
|
||||
.wi-cloud-refresh:before {
|
||||
content: "\f03e";
|
||||
}
|
||||
.wi-cloud-up:before {
|
||||
content: "\f040";
|
||||
}
|
||||
.wi-cloud:before {
|
||||
content: "\f041";
|
||||
}
|
||||
.wi-degrees:before {
|
||||
content: "\f042";
|
||||
}
|
||||
.wi-down-left:before {
|
||||
content: "\f043";
|
||||
}
|
||||
.wi-down:before {
|
||||
content: "\f044";
|
||||
}
|
||||
.wi-fahrenheit:before {
|
||||
content: "\f045";
|
||||
}
|
||||
.wi-horizon-alt:before {
|
||||
content: "\f046";
|
||||
}
|
||||
.wi-horizon:before {
|
||||
content: "\f047";
|
||||
}
|
||||
.wi-left:before {
|
||||
content: "\f048";
|
||||
}
|
||||
.wi-lightning:before {
|
||||
content: "\f016";
|
||||
}
|
||||
.wi-night-fog:before {
|
||||
content: "\f04a";
|
||||
}
|
||||
.wi-refresh-alt:before {
|
||||
content: "\f04b";
|
||||
}
|
||||
.wi-refresh:before {
|
||||
content: "\f04c";
|
||||
}
|
||||
.wi-right:before {
|
||||
content: "\f04d";
|
||||
}
|
||||
.wi-sprinkles:before {
|
||||
content: "\f04e";
|
||||
}
|
||||
.wi-strong-wind:before {
|
||||
content: "\f050";
|
||||
}
|
||||
.wi-sunrise:before {
|
||||
content: "\f051";
|
||||
}
|
||||
.wi-sunset:before {
|
||||
content: "\f052";
|
||||
}
|
||||
.wi-thermometer-exterior:before {
|
||||
content: "\f053";
|
||||
}
|
||||
.wi-thermometer-internal:before {
|
||||
content: "\f054";
|
||||
}
|
||||
.wi-thermometer:before {
|
||||
content: "\f055";
|
||||
}
|
||||
.wi-tornado:before {
|
||||
content: "\f056";
|
||||
}
|
||||
.wi-up-right:before {
|
||||
content: "\f057";
|
||||
}
|
||||
.wi-up:before {
|
||||
content: "\f058";
|
||||
}
|
||||
.wi-wind-east:before {
|
||||
content: "\f059";
|
||||
}
|
||||
.wi-wind-north-east:before {
|
||||
content: "\f05a";
|
||||
}
|
||||
.wi-wind-north-west:before {
|
||||
content: "\f05b";
|
||||
}
|
||||
.wi-wind-north:before {
|
||||
content: "\f05c";
|
||||
}
|
||||
.wi-wind-south-east:before {
|
||||
content: "\f05d";
|
||||
}
|
||||
.wi-wind-south-west:before {
|
||||
content: "\f05e";
|
||||
}
|
||||
.wi-wind-south:before {
|
||||
content: "\f060";
|
||||
}
|
||||
.wi-wind-west:before {
|
||||
content: "\f061";
|
||||
}
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 338 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 383 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 401 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,121 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata></metadata>
|
||||
<defs>
|
||||
<font id="weather_iconsregular" horiz-adv-x="2548" >
|
||||
<font-face units-per-em="2048" ascent="1755" descent="-293" />
|
||||
<missing-glyph horiz-adv-x="685" />
|
||||
<glyph horiz-adv-x="2048" />
|
||||
<glyph horiz-adv-x="2048" />
|
||||
<glyph unicode="
" horiz-adv-x="2048" />
|
||||
<glyph unicode=" " horiz-adv-x="685" />
|
||||
<glyph unicode="	" horiz-adv-x="685" />
|
||||
<glyph unicode=" " horiz-adv-x="685" />
|
||||
<glyph unicode=" " horiz-adv-x="1122" />
|
||||
<glyph unicode=" " horiz-adv-x="2245" />
|
||||
<glyph unicode=" " horiz-adv-x="1122" />
|
||||
<glyph unicode=" " horiz-adv-x="2245" />
|
||||
<glyph unicode=" " horiz-adv-x="748" />
|
||||
<glyph unicode=" " horiz-adv-x="561" />
|
||||
<glyph unicode=" " horiz-adv-x="374" />
|
||||
<glyph unicode=" " horiz-adv-x="374" />
|
||||
<glyph unicode=" " horiz-adv-x="280" />
|
||||
<glyph unicode=" " horiz-adv-x="449" />
|
||||
<glyph unicode=" " horiz-adv-x="124" />
|
||||
<glyph unicode=" " horiz-adv-x="449" />
|
||||
<glyph unicode=" " horiz-adv-x="561" />
|
||||
<glyph unicode="" horiz-adv-x="571" d="M0 0z" />
|
||||
<glyph unicode="" d="M-287 90q0 -38 29 -64q27 -27 65 -27h627q41 0 72.5 -30t31.5 -73t-31.5 -74t-72.5 -31t-73 32q-29 26 -65 26q-38 0 -64 -25.5t-26 -63.5t26 -64q85 -85 202 -85q118 0 201 83.5t83 201.5t-83 202t-201 84h-627q-38 0 -66 -27.5t-28 -64.5zM-287 411q0 -35 29 -61 q27 -27 65 -27h1170q118 0 201.5 83.5t83.5 201.5t-83 200t-202 82q-121 0 -201 -81q-25 -26 -25 -65t24.5 -63.5t63.5 -24.5q38 0 66 25q30 30 72 30t72.5 -30t30.5 -73t-30.5 -74t-72.5 -31h-1170q-38 0 -66 -27.5t-28 -64.5zM-3 662q0 -13 17 -13h153q12 0 22 15 q36 87 111.5 143.5t169.5 63.5l56 8q20 0 20 18l7 55q17 173 146 289t304 116q176 0 305.5 -115.5t147.5 -289.5l7 -62q0 -19 19 -19h174q144 0 245.5 -100.5t101.5 -242.5q0 -143 -101.5 -244.5t-245.5 -101.5h-736q-19 0 -19 -19v-146q0 -18 19 -18h736q143 0 264.5 71 t192 193t70.5 265q0 118 -45 216q121 159 121 353q0 150 -75 279t-204 204.5t-279 75.5q-248 0 -413 -185q-128 65 -285 65q-225 0 -398 -139.5t-220 -356.5q-136 -32 -240.5 -131t-145.5 -234v-4q-2 -3 -2 -9zM869 1838q0 -38 28 -64l65 -69q26 -26 65 -26q40 0 65.5 24.5 t25.5 64.5q0 38 -24 64l-70 69q-25 28 -63 28t-65 -26.5t-27 -64.5zM1437 1368q115 109 264 109q155 0 267 -112t112 -268q0 -104 -55 -195q-153 153 -369 153h-35q-38 173 -184 313zM1609 1927q0 -37 27 -62.5t65 -25.5t64.5 25.5t26.5 62.5v218q0 38 -26.5 65t-64.5 27 t-65 -27t-27 -65v-218zM2201 1685q0 -39 24 -64q27 -27 65 -27.5t61 27.5l156 153q27 27 27 65q0 37 -27 64t-65 27t-64 -26l-153 -157q-24 -25 -24 -62zM2283 423q0 -37 27 -64l68 -69q32 -26 67 -26q30 0 62 26q27 27 27 64q0 35 -27 65l-68 69q-26 26 -62 26 q-40 0 -67 -26.5t-27 -64.5zM2445 1097q0 -39 26 -63q24 -28 62 -28h216q37 0 64 27t27 64q0 38 -27 65t-64 27h-216q-38 0 -63 -27t-25 -65z" />
|
||||
<glyph unicode="" d="M-253 94q0 -38 27 -65q70 -29 93 -29h840q38 0 65 27.5t27 65.5q0 37 -27 62.5t-65 25.5h-840q-42 0 -81 -25.5t-39 -61.5zM-104 414q0 -38 27 -63q24 -28 62 -28h1004q38 0 64.5 26.5t26.5 64.5t-26.5 65t-64.5 27h-1004q-37 0 -63 -27t-26 -65zM8 667q0 -14 18 -14h149 q19 0 23 14q41 85 115.5 140.5t168.5 66.5h59q17 0 17 20l8 60q16 173 146 290t306 117q174 0 304.5 -117t148.5 -290l8 -60q0 -20 18 -20h172q142 0 245 -102t103 -242q0 -144 -102.5 -246.5t-245.5 -102.5h-735q-19 0 -19 -20v-142q0 -19 19 -19h735q144 0 266 71t193 193 t71 266q0 114 -47 214q125 155 125 350q0 150 -76 279t-205.5 204.5t-280.5 75.5q-117 0 -223.5 -47.5t-185.5 -133.5q-132 69 -288 69q-226 0 -401 -140t-223 -358q-136 -34 -239.5 -133.5t-144.5 -235.5q-2 -2 -2 -7zM85 -246q0 -39 27 -63q26 -27 63 -27h1004 q37 0 63.5 26.5t26.5 63.5q0 40 -26 66.5t-64 26.5h-1004q-38 0 -64 -26.5t-26 -66.5zM882 1835q0 -39 27 -64l67 -70q26 -26 64 -26q41 0 67 25.5t26 65.5q0 36 -27 63l-68 69q-27 27 -62 27q-40 0 -67 -26.5t-27 -63.5zM1455 1368q106 101 260 101q156 0 265.5 -109.5 t109.5 -265.5q0 -107 -49 -193q-151 152 -372 152h-32q-46 184 -182 315zM1625 1925q0 -38 26.5 -63t63.5 -25q40 0 65 25t25 63v218q0 39 -25.5 65.5t-64.5 26.5q-37 0 -63.5 -27t-26.5 -65v-218zM2215 1681q0 -39 26 -64q20 -26 64 -26q43 0 63 26l157 154q26 24 26 66 q0 37 -27 63.5t-65 26.5t-65 -25l-153 -158q-26 -25 -26 -63zM2299 416q0 -38 26 -64l69 -67q39 -29 65 -29q27 0 66 29q26 26 26 64q0 37 -26 65l-69 65q-30 28 -69 28q-38 0 -63 -26t-25 -65zM2454 1090q0 -40 28 -65q29 -27 64 -27h217q36 0 63 27t27 65q0 36 -26.5 60.5 t-63.5 24.5h-217q-39 0 -65.5 -24.5t-26.5 -60.5z" />
|
||||
<glyph unicode="" d="M8 528q0 -144 70.5 -266t191.5 -192.5t264 -70.5h1155q143 0 265 70.5t193 192.5t71 266q0 106 -45 213q122 149 122 353q0 114 -44 217.5t-119 178.5t-178.5 119t-217.5 44q-237 0 -414 -186q-124 70 -288 70q-225 0 -398 -139.5t-222 -357.5q-179 -41 -292.5 -184 t-113.5 -328zM190 528q0 134 89.5 231t224.5 113l53 3q20 0 20 19l7 58q22 173 150 289.5t300 116.5q176 0 306.5 -117t146.5 -289l8 -62q4 -18 22 -18h172q141 0 243 -102t102 -242q0 -145 -101.5 -247.5t-243.5 -102.5h-1155q-140 0 -242 103.5t-102 246.5zM905 1837 q0 -39 26 -67l70 -68q40 -30 68 -27q33 0 59 27.5t26 66.5t-28 63l-63 70q-29 26 -65 26q-39 0 -66 -26.5t-27 -64.5zM1475 1365q111 107 261 107q158 0 268.5 -110t110.5 -268q0 -100 -54 -196q-155 153 -372 153h-34q-40 174 -180 314zM1648 1928q0 -41 25 -66t63 -25 q41 0 66 25t25 66v218q0 38 -25.5 63t-65.5 25q-38 0 -63 -25t-25 -63v-218zM2237 1683q0 -41 24 -66q34 -26 66 -26q29 0 63 26l153 153q26 29 26 68q0 38 -26 64t-63 26q-38 0 -62 -26l-157 -153q-24 -28 -24 -66zM2322 421q0 -38 27 -67l69 -67q24 -26 62 -26t63.5 26.5 t25.5 66.5q0 36 -26 62l-69 69q-26 26 -61 26q-38 0 -64.5 -26t-26.5 -64zM2478 1094q0 -37 28 -62q26 -26 65 -26h218q37 0 61.5 25t24.5 63t-24.5 64.5t-61.5 26.5h-218q-38 0 -65.5 -27t-27.5 -64z" />
|
||||
<glyph unicode="" d="M-290 85q0 -38 26.5 -63t66.5 -25h1997q38 0 64.5 25.5t26.5 62.5q0 38 -26.5 64.5t-64.5 26.5h-1997q-40 0 -66.5 -26.5t-26.5 -64.5zM-10 419q0 -37 27 -62q27 -29 64 -29h1997q37 0 62.5 26.5t25.5 64.5q0 37 -25 62.5t-63 25.5h-1997q-38 0 -64.5 -25.5t-26.5 -62.5z M2 675q0 -14 17 -14h154q10 0 21 17q38 83 113.5 136t166.5 60l59 8q18 0 18 19l7 54q17 173 146.5 289t304.5 116q173 0 302 -115t147 -286l8 -62q0 -18 21 -18h171q103 0 187.5 -55t125.5 -146q11 -17 22 -17h153q22 0 15 24l-22 59q122 151 122 353q0 113 -44 216 t-118 178t-178 119t-218 44q-247 0 -408 -179q-135 67 -286 67q-224 0 -399 -141t-224 -359q-284 -75 -381 -357q-3 -5 -3 -10zM176 -243q0 -38 29 -64q25 -28 62 -28h1999q38 0 65 27.5t27 64.5t-27 61.5t-65 24.5h-1999q-38 0 -64.5 -24.5t-26.5 -61.5zM877 1834 q0 -38 25 -63l68 -68q27 -29 63 -29t63 26.5t27 65.5t-26 67l-68 65q-26 28 -64 28q-39 0 -63.5 -26.5t-24.5 -65.5zM1443 1367q108 108 260 108q157 0 267.5 -111t110.5 -267q0 -104 -52 -192q-152 152 -371 152h-35q-44 186 -180 310zM1615 1925q0 -37 25.5 -62.5 t62.5 -25.5q40 0 67 25.5t27 62.5v218q0 37 -28 64t-66 27q-37 0 -62.5 -26.5t-25.5 -64.5v-218zM2201 1682q0 -37 25 -64q59 -58 132 0l152 153q26 28 26 68q0 37 -26 63t-63 26q-38 0 -64 -26l-157 -157q-25 -27 -25 -63zM2287 424q0 -38 27 -64l69 -69q26 -26 62 -26 q33 0 65 26q26 28 26 68q0 36 -26 60l-69 70q-28 26 -64 26q-38 0 -64 -26.5t-26 -64.5zM2445 1097q0 -39 28 -64q24 -27 63 -27h218q37 0 62.5 26.5t25.5 64.5t-25.5 64t-62.5 26h-218q-38 0 -64.5 -26.5t-26.5 -63.5z" />
|
||||
<glyph unicode="" d="M1 530q0 -214 149 -367.5t363 -163.5q20 0 20 18v143q0 19 -20 19q-137 7 -233.5 109.5t-96.5 241.5q0 133 90.5 231t224.5 114l57 4q21 0 21 19l7 59q17 173 146.5 289.5t305.5 116.5q174 0 305.5 -116.5t149.5 -289.5l8 -62q0 -20 19 -20h172q143 0 247.5 -102.5 t104.5 -242.5q0 -139 -97 -241.5t-234 -109.5q-21 0 -21 -19v-143q0 -18 21 -18q214 7 362.5 161.5t148.5 369.5q0 116 -45 214q126 154 126 355q0 151 -75.5 280.5t-205 205.5t-280.5 76q-250 0 -416 -187q-128 70 -290 70q-226 0 -401.5 -140t-225.5 -359 q-177 -42 -292 -186t-115 -329zM570 -223q16 -35 49 -48q32 -16 67.5 -2.5t47.5 47.5q16 35 2.5 69t-47.5 47q-32 17 -66 3t-50 -50q-16 -28 -3 -66zM639 80q0 -22 10 -41q31 -49 95 -49q51 0 74 69l111 343q13 39 -7.5 71.5t-58.5 39.5q-35 11 -67.5 -6.5t-43.5 -53.5 l-110 -344q-3 -15 -3 -29zM838 -519q0 -21 5 -31q14 -35 48 -48q15 -8 37 -8q10 0 32 6q35 13 50.5 48.5t-0.5 70.5t-48 48.5t-66 -0.5q-31 -12 -44.5 -37.5t-13.5 -48.5zM903 1843q0 -40 26 -64l69 -69q26 -26 58 -29q33 -5 65.5 24.5t32.5 68.5q0 38 -26 64l-67 68 q-30 27 -67 27q-39 0 -65 -26t-26 -64zM917 -232q0 -26 16.5 -51t50.5 -35q18 -4 26 -4q24 0 41 8q32 13 46 61l192 655q11 38 -6 69.5t-53 41.5q-38 11 -70 -6t-43 -54l-197 -660q-3 -15 -3 -25zM1285 -191q0 -22 6 -33q14 -33 48 -47q17 -8 37 -8q10 0 32 6q36 14 49 47 q13 35 0.5 67.5t-44.5 48.5q-36 17 -70.5 3t-51.5 -50q-6 -11 -6 -34zM1361 83q0 -25 16.5 -48.5t49.5 -33.5q13 -3 27 -3q62 0 84 65l110 339q12 37 -7 69t-55 42q-38 11 -68.5 -6t-42.5 -54l-109 -341q-5 -22 -5 -29zM1475 1371q107 103 266 103q156 0 266.5 -109.5 t110.5 -265.5q0 -100 -55 -197q-153 154 -374 154h-33q-47 185 -181 315zM1648 1933q0 -38 27.5 -65t65.5 -27q37 0 62.5 27t25.5 65v220q0 38 -25.5 65t-62.5 27q-38 0 -65.5 -27t-27.5 -65v-220zM2240 1689q0 -39 27 -64q24 -28 61.5 -27.5t64.5 27.5l154 154q29 24 29 64 q0 38 -27.5 65t-65.5 27q-35 0 -61 -29l-155 -153q-27 -25 -27 -64zM2326 419q0 -36 26 -64l70 -67q23 -29 64 -29q38 0 61 29q29 26 29 64t-29 65l-69 66q-25 28 -62 28t-63.5 -27t-26.5 -65zM2482 1099q0 -38 28 -64q28 -28 66 -28h217q38 0 65.5 27t27.5 65 q0 37 -27 62.5t-66 25.5h-217q-40 0 -67 -25.5t-27 -62.5z" />
|
||||
<glyph unicode="" d="M5 528q0 -213 148 -366t362 -163q19 0 19 18v146q0 19 -19 19q-139 11 -234 110.5t-95 235.5q0 134 91 233.5t224 110.5l56 8q20 0 20 18l7 54q17 173 146.5 289.5t304.5 116.5q174 0 304 -116t149 -290l7 -62q0 -18 19 -18h172q145 0 247 -101t102 -243 q0 -136 -95 -235.5t-234 -110.5q-20 0 -20 -19v-146q0 -18 20 -18q213 7 361 161t148 368q0 108 -50 221q126 150 126 349q0 114 -44.5 217.5t-119 178.5t-178 119t-216.5 44q-247 0 -414 -185q-137 66 -284 66q-227 0 -401.5 -139t-223.5 -359q-176 -41 -290.5 -184.5 t-114.5 -327.5zM805 -674h32l566 837q6 7 3 14.5t-14 7.5h-233l244 449q12 23 -14 23h-315q-13 0 -23 -15l-229 -610q-4 -22 15 -22h230zM898 1841q0 -39 27 -66l69 -69q67 -51 129 0q27 30 27 68q0 36 -27 63l-67 69q-30 27 -66 27q-38 0 -65 -27.5t-27 -64.5zM1469 1369 q109 109 264 109q156 0 266 -111t110 -269q0 -97 -54 -193q-156 150 -369 150h-33q-43 182 -184 314zM1641 1933q0 -40 26 -66t66 -26q38 0 63 25.5t25 66.5v218q0 38 -25 63t-63 25t-65 -25t-27 -63v-218zM2230 1686q0 -38 28 -64q29 -27 60 -27q26 0 66 27l153 153 q28 30 28 67q0 39 -27 65t-65 26q-35 0 -62 -27l-153 -153q-28 -30 -28 -67zM2317 425q0 -37 26 -65l68 -70q26 -26 62 -26q38 0 65 27.5t27 66.5q0 35 -28 61l-65 70q-30 25 -66 25q-38 0 -63.5 -26t-25.5 -63zM2472 1098q0 -36 27 -61q27 -27 64 -27h219q38 0 64.5 25.5 t26.5 62.5q0 38 -27 65t-64 27h-219q-37 0 -64 -27t-27 -65z" />
|
||||
<glyph unicode="" d="M1 523q0 -212 146 -363t360 -161q19 0 19 18v142q0 19 -19 19q-137 7 -231 106.5t-94 238.5q0 135 90 234t224 110l56 8q17 0 17 15l8 59q20 175 147.5 290t302.5 115t305.5 -115t147.5 -287l7 -62q4 -18 23 -18h171q142 0 244 -102.5t102 -246.5q0 -139 -94 -238.5 t-231 -106.5q-21 0 -21 -19v-142q0 -18 21 -18q213 7 359 159t146 365q0 108 -41 214q124 154 124 357q0 113 -44 216.5t-119 178.5t-178.5 119.5t-217.5 44.5q-246 0 -412 -186q-143 70 -292 70q-226 0 -398.5 -140t-221.5 -360q-180 -41 -293 -184.5t-113 -329.5z M573 -227q0 -27 17 -54t50 -37q37 -11 68.5 4t42.5 60l15 65q8 36 -10 67.5t-55 42.5q-36 11 -68.5 -8t-42.5 -57l-15 -63q-2 -6 -2 -20zM654 82q0 -35 26 -61q25 -27 61 -27q38 0 64 25.5t26 62.5t-26 62.5t-64 25.5q-37 0 -62 -25t-25 -63zM719 325q-2 -25 14 -48.5 t51 -34.5q33 -10 66.5 7.5t44.5 54.5l30 96q12 39 -7.5 69.5t-58.5 41.5q-35 11 -67 -7t-43 -53l-27 -98q-3 -27 -3 -28zM842 -560q0 -27 17 -52.5t52 -35.5q14 -3 26 -3q69 0 85 65l15 63q10 40 -9 72.5t-57 39.5q-34 11 -66.5 -7.5t-43.5 -54.5l-15 -63q-4 -18 -4 -24z M902 1834q0 -38 26 -64l70 -68q22 -25 57.5 -27.5t66.5 27.5q27 27 27 63q0 37 -27 64l-67 69q-25 25 -63 28q-37 0 -63.5 -27t-26.5 -65zM926 -250q0 -36 26 -62t62 -26q38 0 63 25t25 63q0 37 -25 62t-63 25t-63 -25t-25 -62zM992 -8q-2 -24 14.5 -50t48.5 -32 q37 -10 68 6t44 60l28 96q11 35 -7.5 67t-56.5 43q-35 11 -68 -8.5t-44 -56.5l-24 -96q-3 -14 -3 -29zM1287 -237q0 -27 16 -51.5t49 -34.5q5 0 15 -2t15 -2q65 0 81 70l15 64q11 34 -7.5 67t-54.5 44q-39 10 -71.5 -8.5t-43.5 -56.5l-11 -63q0 -2 -1.5 -12t-1.5 -15z M1366 78q0 -37 26 -61q24 -26 62 -26t63 25t25 62q0 38 -25 63t-63 25t-63 -25t-25 -63zM1431 325q0 -27 17.5 -51.5t52.5 -34.5q3 0 12.5 -2t14.5 -2q17 0 39 10q33 17 43 56l27 96q10 36 -8 67.5t-54 42.5q-37 11 -68.5 -6t-42.5 -54l-30 -97q0 -3 -1.5 -11.5t-1.5 -13.5z M1469 1361q106 106 262 106q158 0 269 -108.5t111 -264.5q0 -106 -57 -200q-157 157 -373 157h-33q-45 176 -179 310zM1645 1923q0 -37 24.5 -61.5t61.5 -24.5q39 0 66 24.5t27 61.5v220q0 37 -27.5 64t-65.5 27q-37 0 -61.5 -26.5t-24.5 -64.5v-220zM2233 1681 q0 -40 24 -64q57 -57 129 0l153 153q27 27 27 67q0 38 -26.5 64.5t-64.5 26.5q-37 0 -65 -26l-153 -158q-24 -23 -24 -63zM2317 417q0 -38 28 -63l65 -67q32 -26 65 -26q32 0 64 26q27 25 27 63q0 36 -27 66l-69 65q-24 27 -61 27t-64 -27q-28 -26 -28 -64zM2474 1094 q0 -38 28 -63q29 -29 66 -29h216q37 0 62.5 27t25.5 65t-25.5 64.5t-62.5 26.5h-216q-38 0 -66 -27t-28 -64z" />
|
||||
<glyph unicode="" d="M-3 527q0 -180 106.5 -321.5t274.5 -188.5q16 -2 27 8l124 150q-143 0 -244.5 103.5t-101.5 248.5q0 134 90 231.5t225 113.5l53 3q20 0 20 20l8 58q17 173 147 290t306 117t307 -117t148 -290l7 -63q7 -18 23 -18h174q141 0 243.5 -101.5t102.5 -243.5 q0 -135 -90.5 -236t-220.5 -112q-78 -9 -96 -30l-235 -301q-22 -30 -17 -67t33 -60q24 -27 63.5 -22t63.5 38l206 262q98 10 185.5 56t150.5 116t100 163t37 193q0 117 -44 214q125 151 125 355q0 113 -44.5 217t-120 179t-179.5 120t-218 45q-241 0 -415 -186 q-127 70 -289 70q-226 0 -400.5 -140.5t-221.5 -359.5q-181 -41 -297 -185t-116 -329zM413 -352q12 -35 49 -51q35 -16 71 -1t49 50q15 33 0.5 66.5t-47.5 49.5q-35 16 -70.5 2t-48.5 -49q-13 -41 -3 -67zM606 -59v-12q3 -35 32 -59q28 -24 67 -21t61 31l233 301 q23 29 19.5 67.5t-31.5 60.5q-29 24 -67 20t-64 -33l-230 -299q-20 -25 -20 -56zM661 -578q0 -14 5 -32q14 -35 49 -49q18 -7 38 -7q15 0 33 5q34 13 47 47q16 35 3 71t-47 49q-35 16 -71.5 1.5t-48.5 -48.5q-8 -17 -8 -37zM839 -325v-11q3 -37 34 -62q24 -26 64 -21.5 t64 36.5l442 558q22 32 18.5 68.5t-32.5 62.5q-29 23 -66 18.5t-60 -33.5l-445 -561q-19 -23 -19 -55zM901 1839q0 -39 28 -63l65 -70q25 -25 59 -28q33 -5 65.5 24t32.5 68q0 36 -26 64l-69 69q-24 26 -63 26q-38 0 -65 -26t-27 -64zM1215 -447q0 -19 7 -33q14 -35 48 -49 q17 -8 36 -8q10 0 32 6q36 14 49 49q16 35 3 70.5t-47 48.5q-35 16 -71.5 2t-49.5 -48q-7 -18 -7 -38zM1472 1368q113 103 264 103q158 0 270 -109.5t112 -265.5q0 -106 -55 -198q-155 155 -372 155h-35q-44 184 -184 315zM1645 1929q0 -37 26.5 -62.5t64.5 -25.5 q39 0 66 25.5t27 62.5v221q0 37 -27.5 64t-65.5 27t-64.5 -26.5t-26.5 -64.5v-221zM2240 1686q0 -38 24 -64q28 -27 65.5 -27.5t60.5 27.5l159 154q26 26 26 65q0 37 -27.5 65t-65.5 28q-35 0 -64 -27l-154 -157q-24 -26 -24 -64zM2322 416q0 -37 27 -64l69 -67 q26 -26 59 -29h5q29 0 67 29q26 26 26 62q0 38 -26 67l-70 66q-25 27 -63 27q-40 0 -67 -26.5t-27 -64.5zM2482 1096q0 -37 27 -64q26 -29 64 -29h218q37 0 64 27.5t27 65.5q0 37 -26.5 62.5t-64.5 25.5h-218q-38 0 -64.5 -25.5t-26.5 -62.5z" />
|
||||
<glyph unicode="" d="M5 529q0 -213 148.5 -366.5t363.5 -163.5q18 0 18 18v143q0 18 -18 18q-137 7 -234 109.5t-97 241.5q0 132 91 230t225 114l56 5q20 0 20 18l7 58q17 173 147 290t305 117q174 0 304.5 -117t148.5 -290l8 -61q0 -20 19 -20h172q142 0 246 -102t104 -242 q0 -139 -96 -241.5t-233 -109.5q-21 0 -21 -18v-143q0 -18 21 -18q213 7 361 161.5t148 368.5q0 117 -44 214q124 154 124 353q0 152 -75.5 281.5t-204.5 204.5t-280 75q-248 0 -413 -186q-131 70 -289 70q-226 0 -401.5 -140.5t-224.5 -358.5q-177 -42 -291.5 -185.5 t-114.5 -327.5zM581 -202q0 -27 17 -52.5t52 -35.5q18 -4 30 -4q62 0 81 68l169 627q11 38 -8.5 71t-56.5 40q-35 11 -67.5 -7t-43.5 -54l-169 -630q-4 -16 -4 -23zM858 -531q0 -30 16 -55.5t55 -31.5q18 -4 29 -4q25 0 47.5 18.5t28.5 51.5l256 953q10 37 -7 68.5t-53 42.5 q-37 11 -69.5 -7t-42.5 -54l-256 -953q0 -3 -2 -13.5t-2 -15.5zM904 1840q0 -39 26 -65l70 -70q32 -24 65 -24q34 0 62 25t28 63q0 39 -26 68l-67 69q-29 27 -64 27q-40 0 -67 -27.5t-27 -65.5zM1302 -205q0 -26 16.5 -50.5t49.5 -34.5q18 -4 26 -4q29 0 53.5 15.5 t31.5 52.5l169 627q10 37 -8 68.5t-54 42.5q-38 11 -69.5 -7t-41.5 -54l-169 -630q-4 -18 -4 -26zM1475 1368q113 107 264 107q157 0 267.5 -110.5t110.5 -268.5q0 -100 -55 -197q-155 155 -373 155h-34q-45 185 -180 314zM1648 1928q0 -37 26.5 -62.5t64.5 -25.5 q37 0 62.5 25t25.5 63v219q0 41 -25 67t-63 26q-40 0 -65.5 -26.5t-25.5 -66.5v-219zM2238 1685q0 -39 28 -63q22 -25 59 -27.5t67 27.5l153 153q29 27 29 67q0 39 -27 65t-66 26q-33 0 -62 -27l-153 -154q-28 -27 -28 -67zM2325 421q0 -38 26 -67l68 -67q28 -22 64 -22 l3 -2q36 0 62 26t26 65q0 38 -29 62l-65 70q-29 26 -66 26t-63 -26.5t-26 -64.5zM2480 1096q0 -39 27 -63q28 -28 67 -28h217q37 0 64 26.5t27 64.5t-27 65.5t-64 27.5h-217q-38 0 -66 -27.5t-28 -65.5z" />
|
||||
<glyph unicode="" d="M1 530q0 -213 148.5 -365.5t362.5 -163.5q18 0 18 18v143q0 19 -18 19q-136 7 -231 108.5t-95 240.5q0 133 90 230.5t225 113.5l52 4q21 0 21 19l7 57q17 173 146.5 289.5t304.5 116.5t305 -116.5t146 -289.5l8 -61q0 -19 19 -19h178q140 0 242 -102t102 -242 q0 -136 -95.5 -237t-230.5 -112q-18 0 -18 -19v-143q0 -18 18 -18q140 4 256 76.5t182.5 192t66.5 260.5q0 114 -44 209q121 150 121 350q0 114 -44 217.5t-119 178.5t-178.5 119t-217.5 44q-241 0 -406 -177q-127 68 -291 68q-226 0 -399 -139.5t-220 -357.5 q-180 -41 -296 -184t-116 -328zM579 -184q0 -29 17 -56.5t49 -38.5q31 -11 65.5 6.5t46.5 58.5l26 112q11 33 -7.5 66.5t-54.5 44.5q-40 10 -72 -9t-39 -57l-29 -106q-2 -14 -2 -21zM718 319q0 -61 68 -84q35 -12 68 5.5t44 56.5l27 110q11 33 -7.5 66t-54.5 45 q-39 10 -71 -8.5t-39 -55.5l-32 -109q-3 -14 -3 -26zM848 -522q0 -26 18 -53t52 -38q1 0 10.5 -1.5t15.5 -1.5q22 0 39 8q30 13 44 62l29 106q11 37 -7.5 69.5t-54.5 42.5q-37 11 -69.5 -7.5t-42.5 -54.5l-29 -109q-5 -20 -5 -23zM897 1832q0 -40 28 -66l69 -69 q54 -54 125 0q26 28 26 65t-26 65l-66 70q-26 26 -64 26t-65 -27t-27 -64zM992 -11q0 -27 17 -54t50 -37q37 -11 68 5t43 60l27 108q11 37 -7 69.5t-54 43.5q-40 11 -72.5 -8.5t-39.5 -56.5l-30 -110q-2 -6 -2 -20zM1289 -195q4 -59 68 -88l26 -4q26 0 51 17t35 53l30 108 q10 38 -9.5 70.5t-56.5 39.5q-33 11 -66 -7.5t-44 -54.5l-29 -109zM1438 313q0 -26 16.5 -51.5t48.5 -34.5l27 -3q31 0 55 19t30 50l30 106q11 37 -7.5 69t-54.5 42q-37 11 -68.5 -6.5t-41.5 -53.5l-32 -113q-3 -13 -3 -24zM1473 1365q106 102 256 102q157 0 268.5 -110.5 t111.5 -267.5q0 -88 -51 -188q-153 153 -370 153h-35q-43 180 -180 311zM1638 1923q0 -40 25.5 -65.5t65.5 -25.5t66 25.5t26 65.5v215q0 40 -26 66t-66 26t-65.5 -26t-25.5 -66v-215zM2230 1678q0 -40 24 -65q33 -27 66 -27q30 0 63 27l153 153q26 29 26 67t-26 64t-63 26 t-65 -26l-154 -153q-24 -28 -24 -66zM2314 416q0 -37 28 -67l66 -68q33 -33 66 -33q30 0 62 33q29 29 28 65.5t-28 63.5l-69 70q-26 26 -61 26q-38 0 -65 -26.5t-27 -63.5zM2471 1089q0 -36 26 -62t62 -26h218q41 0 67 25t26 63q0 40 -26.5 66t-66.5 26h-218 q-37 0 -62.5 -27t-25.5 -65z" />
|
||||
<glyph unicode="" d="M1 525q0 -138 68 -257t185.5 -191t256.5 -76q18 0 18 18v142q0 20 -18 20q-136 7 -232.5 108.5t-96.5 235.5q0 132 90.5 230t223.5 114l56 6q19 0 19 20l8 54q17 175 146 291.5t304 116.5q174 0 304 -116.5t147 -288.5l8 -62q0 -18 18 -18h172q144 0 246 -102.5 t102 -244.5q0 -134 -96.5 -235.5t-231.5 -108.5q-20 0 -20 -20v-142q0 -18 20 -18q212 7 360 160t148 364q0 121 -46 217q126 151 126 352q0 150 -75 278.5t-204 203.5t-279 75q-246 0 -413 -185q-130 70 -286 70q-225 0 -399.5 -139.5t-223.5 -356.5q-179 -44 -292 -187 t-113 -328zM678 93q0 -38 25.5 -65t62.5 -27t62.5 27t25.5 65q0 36 -25.5 62t-62.5 26t-62.5 -26t-25.5 -62zM678 -294q0 -33 26 -61q28 -26 62 -26q38 0 63 25t25 62q0 38 -25 63t-63 25t-63 -25t-25 -63zM896 1832q0 -39 26 -63l70 -70q26 -26 57 -27q33 -5 65 23.5 t32 67.5t-26 68l-67 65q-24 27 -63 27q-40 0 -67 -26.5t-27 -64.5zM1017 -112q0 -36 26 -64q26 -26 62 -26q38 0 65 26t27 64q0 37 -27 63.5t-65 26.5q-36 0 -62 -26.5t-26 -63.5zM1017 272q0 -36 26 -62t62 -26q38 0 65 25.5t27 62.5t-27 63t-65 26q-36 0 -62 -26t-26 -63z M1017 -502q0 -35 26 -61q27 -27 62 -27q38 0 65 25t27 63q0 37 -27 64t-65 27q-35 0 -61.5 -27t-26.5 -64zM1361 93q0 -38 26 -65t63 -27t62.5 27t25.5 65q0 36 -25.5 62t-62.5 26t-63 -26t-26 -62zM1361 -294q0 -34 25 -61q28 -26 64 -26q38 0 63 25t25 62q0 38 -25 63 t-63 25t-63.5 -25.5t-25.5 -62.5zM1465 1365q110 106 263 106q155 0 265 -111t110 -266q0 -100 -54 -196q-153 153 -371 153h-33q-47 186 -180 314zM1637 1921q0 -37 26.5 -62t64.5 -25t63 25t25 62v218q0 38 -25.5 65t-62.5 27t-64 -27t-27 -65v-218zM2224 1679 q0 -37 27 -63q24 -25 59.5 -27.5t66.5 27.5l153 153q28 28 28 65q0 38 -27 65t-65 27q-34 0 -61 -27l-154 -156q-27 -25 -27 -64zM2310 421q0 -40 26 -66l69 -67q39 -26 67 -26q33 0 59.5 27.5t26.5 65.5q0 36 -28 62l-66 69q-28 26 -65 26t-63 -26.5t-26 -64.5zM2464 1094 q0 -37 29 -63q27 -29 65 -29h216q37 0 64 27t27 65t-26.5 64.5t-64.5 26.5h-216q-38 0 -66 -27t-28 -64z" />
|
||||
<glyph unicode="" d="M-2 529q0 -139 67.5 -258.5t184.5 -191.5t256 -78q20 0 20 18v142q0 20 -20 20q-136 7 -232 108.5t-96 239.5q0 132 90 229.5t223 113.5l56 3q21 0 21 19l8 58q17 173 145.5 289t303.5 116q172 0 302.5 -116.5t149.5 -288.5l6 -62q0 -18 19 -18h172q141 0 245 -102 t104 -241q0 -138 -96 -239.5t-233 -108.5q-20 0 -20 -20v-142q0 -18 20 -18q139 4 256 76.5t184 192.5t67 259q0 118 -44 213q124 152 124 352q0 151 -75 279.5t-203 203t-278 74.5q-248 0 -413 -185q-131 70 -287 70q-225 0 -399.5 -139.5t-223.5 -356.5 q-176 -42 -290 -185t-114 -326zM627 435q0 -61 45.5 -104t108.5 -43q64 0 106.5 42.5t42.5 104.5q0 40 -37 107.5t-69 105.5q-34 36 -43 45l-38 -43q-43 -46 -79.5 -110t-36.5 -105zM893 1834q0 -41 26 -65l68 -70q68 -51 130 0q26 31 26 68t-26 63l-68 69q-30 27 -65 27 q-39 0 -65 -27t-26 -65zM945 13q0 -105 72.5 -176.5t175.5 -71.5q104 0 176.5 73t72.5 175q0 87 -85 210q-74 97 -136 159q-13 9 -28 24l-25 -24q-57 -52 -136 -157q-87 -121 -87 -212zM1102 719q0 -40 30.5 -69t73.5 -29q41 0 70 29t29 69q0 66 -99 171l-27 -27 q-29 -31 -53 -74t-24 -70zM1462 1365q106 106 263 106q156 0 265 -110t109 -267q0 -96 -54 -196q-154 153 -371 153h-34q-46 186 -178 314zM1633 1926q0 -38 27.5 -65t64.5 -27t61.5 27t24.5 65v217q0 38 -24.5 63t-61.5 25q-38 0 -65 -25t-27 -63v-217zM2221 1679 q0 -39 27 -63q28 -26 59 -26q29 0 66 26l153 153q28 28 28 66q0 39 -26.5 65t-64.5 26q-35 0 -62 -27l-153 -153q-27 -27 -27 -67zM2306 423q0 -40 25 -68l70 -62q23 -29 63 -29q39 0 62 29q28 25 28 62q0 36 -28 62l-65 69q-28 26 -66 26q-37 0 -63 -26t-26 -63zM2461 1094 q0 -36 27 -62q28 -26 66 -26h216q38 0 65 25.5t27 62.5t-27 64t-65 27h-216q-38 0 -65.5 -27t-27.5 -64z" />
|
||||
<glyph unicode="" horiz-adv-x="1971" d="M-282 899q0 -43 30 -71t77 -28h180q43 0 73.5 28.5t30.5 70.5q0 46 -30 76t-74 30h-180q-47 0 -77 -29.5t-30 -76.5zM-94 239q0 -155 113.5 -268.5t268.5 -113.5h731q155 0 265.5 112t110.5 270q0 79 -21 131q133 85 210.5 226.5t77.5 302.5q0 130 -51 248.5t-136.5 204 t-204 136.5t-247.5 51q-175 0 -322.5 -86t-232 -233t-84.5 -321v-36q-160 -91 -211 -266q-121 -38 -194 -135t-73 -223zM73 1750q0 -45 28 -72l172 -180q75 -57 150 0q30 30 30 75q0 43 -30 75l-176 175q-34 31 -74 31q-45 0 -72.5 -30t-27.5 -74zM118 239q0 67 43 115 t109 54l66 9q21 0 21 24l10 60q11 92 77.5 154t157.5 62q93 0 161.5 -62t79.5 -154l10 -69q10 -24 26 -24h140q69 0 120 -50t51 -119q0 -71 -51 -122.5t-120 -51.5h-731q-72 0 -121 51t-49 123zM590 921q11 175 135 294t298 119q178 0 302.5 -127t124.5 -308 q0 -112 -54 -207.5t-147 -154.5q-94 78 -217 78q-47 140 -164.5 223t-265.5 83h-12zM919 1854q0 -44 30.5 -74.5t73.5 -30.5q44 0 74.5 30.5t30.5 74.5v250q0 42 -30.5 70.5t-74.5 28.5q-43 0 -73.5 -28.5t-30.5 -70.5v-250zM1593 230q0 -43 29 -75l175 -173q66 -68 149 0 q29 27 29 72q0 42 -29 71l-179 179q-29 27 -73 27t-72.5 -29t-28.5 -72zM1593 1575q0 -45 29 -77q29 -29 72 -29q44 0 73 29l179 180q29 27 29 72q0 44 -30.5 74t-74.5 30q-42 0 -73 -31l-175 -175q-29 -29 -29 -73zM1869 899q0 -43 30 -71t77 -28h179q43 0 73.5 28.5 t30.5 70.5q0 46 -30 76t-74 30h-179q-47 0 -77 -29.5t-30 -76.5z" />
|
||||
<glyph unicode="" horiz-adv-x="1828" d="M-248 771q0 39 27 66q28 26 64 26h218q37 0 61.5 -27t24.5 -65t-24.5 -64.5t-61.5 -26.5h-218q-37 0 -64 27t-27 64zM57 32q0 37 25 65l157 152q24 25 63 25q38 0 63.5 -24t25.5 -61q0 -39 -26 -68l-152 -152q-65 -51 -131 0q-25 27 -25 63zM57 1509q0 37 25 65 q31 26 68 26q35 0 63 -26l152 -157q26 -24 26 -63q0 -38 -25.5 -63.5t-63.5 -25.5q-39 0 -63 26l-157 152q-25 27 -25 66zM329 771q0 149 75 277.5t203.5 203.5t277.5 75q112 0 215 -44.5t177.5 -119t118.5 -177.5t44 -215q0 -150 -74.5 -278t-202.5 -202.5t-278 -74.5 t-278 74.5t-203 202.5t-75 278zM510 771q0 -156 109.5 -266.5t265.5 -110.5t266.5 110.5t110.5 266.5q0 154 -110.5 263t-266.5 109q-155 0 -265 -109t-110 -263zM794 -58q0 38 26.5 64t64.5 26q39 0 65 -26t26 -64v-212q0 -39 -26.5 -66t-64.5 -27t-64.5 27t-26.5 66v212z M794 1595v218q0 37 27 64t64 27t64 -27t27 -64v-218q0 -37 -26.5 -61.5t-64.5 -24.5t-64.5 24.5t-26.5 61.5zM1383 189q0 37 24 60q24 25 60 25q39 0 64 -25l156 -152q26 -28 26 -65t-26 -63q-64 -50 -128 0l-152 152q-24 27 -24 68zM1383 1354q0 40 24 63l152 157 q28 26 63 26q38 0 64.5 -27t26.5 -64q0 -40 -26 -66l-156 -152q-29 -26 -64 -26q-36 0 -60 25.5t-24 63.5zM1624 771q0 38 26 66q26 26 61 26h216q37 0 64.5 -27.5t27.5 -64.5t-27.5 -64t-64.5 -27h-216q-37 0 -62 26.5t-25 64.5z" />
|
||||
<glyph unicode="" d="M-1 530q0 -179 106.5 -320t275.5 -188l-70 -181q-8 -23 15 -23h227l-152 -449h31l465 604q6 7 1.5 14.5t-15.5 7.5h-233l265 496q12 23 -14 23h-316q-15 0 -25 -16l-114 -307q-115 29 -190 123.5t-75 215.5q0 135 90 233t225 114l54 3q19 0 24 15l8 63 q17 173 146.5 290.5t305.5 117.5q174 0 305.5 -117.5t149.5 -290.5l8 -63q0 -18 18 -18h173q143 0 246 -102.5t103 -244.5q0 -139 -95.5 -241.5t-232.5 -109.5q-21 0 -21 -19v-143q0 -18 21 -18q141 4 258.5 76.5t185.5 193t68 261.5q0 117 -46 215q122 154 122 351 q0 152 -75.5 281.5t-205 205t-281.5 75.5q-115 0 -224 -48t-188 -133q-129 69 -289 69q-227 0 -402.5 -140.5t-224.5 -359.5q-177 -41 -292.5 -186t-115.5 -330zM872 -528q0 -24 17 -50t49 -36q18 -4 27 -4q24 0 41 8q33 13 45 61l30 110q10 40 -9 71.5t-57 39.5 q-33 11 -66 -8t-45 -55l-27 -111q-5 -22 -5 -26zM897 1841q0 -38 26 -64l70 -70q25 -25 58 -28q33 -5 65.5 24t32.5 68q0 37 -27 64l-67 70q-26 26 -64 26q-40 0 -67 -26t-27 -64zM1015 -16q-1 -26 15 -50.5t51 -34.5q34 -11 66.5 6.5t44.5 59.5l32 110q11 35 -8.5 67.5 t-57.5 43.5q-36 11 -68.5 -7.5t-43.5 -56.5l-27 -112q-4 -18 -4 -26zM1317 -195q0 -23 11 -42q21 -34 58 -46q18 -6 30 -6q20 0 33 8q32 12 48 64l26 108q11 38 -6.5 69.5t-53.5 41.5q-37 11 -69.5 -7t-43.5 -54l-30 -111q-3 -14 -3 -25zM1463 319q0 -28 17.5 -53.5 t52.5 -35.5q27 -9 65 5q31 13 45 61l30 107q10 40 -8.5 71.5t-56.5 39.5q-36 11 -68 -6.5t-43 -53.5l-31 -113q-3 -21 -3 -22zM1474 1369q107 103 261 103q157 0 267.5 -109.5t110.5 -266.5q0 -105 -50 -193q-154 154 -375 154h-33q-47 190 -181 312zM1643 1931 q0 -38 27 -65t65 -27t63.5 26.5t25.5 65.5v220q0 39 -25.5 65.5t-63.5 26.5t-65 -27t-27 -65v-220zM2239 1687q0 -39 25 -64q22 -25 58.5 -27.5t67.5 27.5l154 154q27 25 27 64t-27 64q-26 28 -64 28q-37 0 -62 -28l-154 -154q-25 -25 -25 -64zM2322 416q0 -37 27 -64 l69 -67q29 -27 65 -27l3 -2q35 0 58 29q27 26 27 64t-27 65l-66 66q-29 29 -65 29q-38 0 -64.5 -27.5t-26.5 -65.5zM2478 1096q0 -39 28 -63q25 -28 64 -28h220q38 0 65 26.5t27 64.5t-26.5 63.5t-65.5 25.5h-220q-39 0 -65.5 -25.5t-26.5 -63.5z" />
|
||||
<glyph unicode="" d="M-1 528q0 -177 105 -316t274 -190l-68 -181q-7 -22 15 -22h226l-133 -421h31l445 575q6 7 1.5 14.5t-15.5 7.5h-232l264 494q11 23 -15 23h-314q-15 0 -25 -15l-115 -307q-114 29 -189 124t-75 214q0 133 91 231t225 114l56 7q20 0 20 19l7 54q17 173 147 290t305 117 q173 0 303.5 -117t149.5 -290l7 -62q0 -18 19 -18h172q145 0 247.5 -101t102.5 -244q0 -136 -95 -235.5t-234 -110.5q-21 0 -21 -19v-146q0 -18 21 -18q213 7 361 161t148 368q0 120 -45 214q125 151 125 354q0 150 -75.5 279t-204.5 204.5t-280 75.5q-246 0 -413 -185 q-129 69 -288 69q-226 0 -401 -140t-224 -358q-177 -41 -291.5 -184.5t-114.5 -328.5zM862 -494q-2 -24 14.5 -47t49.5 -35q10 -3 21 -3q27 0 54.5 16t36.5 50l244 914q10 37 -7 68.5t-52 42.5q-37 11 -69.5 -7t-42.5 -54l-246 -917q-3 -13 -3 -28zM897 1837q0 -39 26 -63 l69 -70q27 -27 65 -27.5t61 27.5q29 23 29 64q0 39 -26 63l-67 70q-28 26 -65 26q-40 0 -66 -26t-26 -64zM1305 -168q0 -19 12 -40q20 -32 53 -46q11 -5 32 -5q24 0 35 6q32 13 44 62l159 592q10 38 -8 69t-54 42q-37 11 -69 -7t-42 -54l-158 -595q0 -4 -2 -12t-2 -12z M1467 1367q107 103 264 103q156 0 267 -109t111 -265q0 -100 -55 -197q-159 157 -373 157h-33q-49 186 -181 311zM1640 1927q0 -38 27 -65t64 -27q38 0 63 27t25 65v219q0 38 -25 65t-63 27q-37 0 -64 -27t-27 -65v-219zM2230 1683q0 -39 28 -64q23 -27 60.5 -27t64.5 27 l154 155q28 25 28 63t-27 65t-65 27q-36 0 -62 -28l-153 -154q-28 -25 -28 -64zM2315 418q0 -35 27 -64l69 -67q20 -23 59 -26l2 -1q1 0 4 -0.5t5 -0.5q30 0 56 28q28 26 28 64q0 37 -28 65l-70 65q-26 29 -61 29q-37 0 -64 -27t-27 -65zM2471 1096q0 -38 28 -64 q29 -29 66 -29h217q37 0 64.5 27.5t27.5 65.5q0 37 -27 62.5t-65 25.5h-217q-40 0 -67 -25.5t-27 -62.5z" />
|
||||
<glyph unicode="" horiz-adv-x="2194" d="M-231 90q0 39 28 67q28 26 64 26h577q118 0 201.5 -84.5t83.5 -202.5q0 -119 -83 -202.5t-202 -83.5t-203 84q-25 25 -25 64t24.5 64.5t64.5 25.5q38 0 66 -26q32 -31 73 -31q42 0 73 30.5t31 74.5q0 42 -31 72.5t-73 30.5h-577q-38 0 -65 27t-27 64zM-231 414 q0 38 28 66q28 26 64 26h1124q42 0 73 31t31 74t-31 73.5t-73 30.5q-44 0 -73 -30q-28 -24 -67 -24t-63.5 24.5t-24.5 63.5q0 40 24 65q81 81 204 81q119 0 202.5 -83t83.5 -201t-83.5 -201.5t-202.5 -83.5h-1124q-38 0 -65 25.5t-27 62.5zM-2 666q0 -13 17 -13h154 q13 0 23 16q36 87 112 144t170 64l56 8q21 0 21 18l8 55q17 173 146.5 290.5t305.5 117.5q177 0 307 -116.5t148 -291.5l8 -63q0 -18 19 -18h172q145 0 248.5 -102t103.5 -245t-103.5 -245t-248.5 -102h-737q-19 0 -19 -18v-148q0 -18 19 -18h737q145 0 267.5 71t194 193.5 t71.5 266.5q0 145 -71.5 267t-194 193t-267.5 71h-33q-50 213 -223.5 349t-397.5 136q-226 0 -400 -140.5t-221 -359.5q-137 -32 -243 -131.5t-147 -235.5v-4q-2 -5 -2 -9z" />
|
||||
<glyph unicode="" horiz-adv-x="2194" d="M-352 87q0 39 27 65t67 26h957q38 0 63 -25.5t25 -65.5q0 -38 -25.5 -63.5t-62.5 -25.5h-957q-40 0 -67 25.5t-27 63.5zM-71 413q0 38 29 64q23 24 63 24h960q37 0 61.5 -25t24.5 -63t-24.5 -65t-61.5 -27h-960q-37 0 -64.5 27.5t-27.5 64.5zM-5 662q0 -15 18 -15h152 q15 0 25 15q36 87 111 144t168 64l58 7q18 0 18 19l7 55q18 174 148.5 290t306.5 116q174 0 302 -114.5t150 -288.5l8 -63q0 -17 19 -17h171q145 0 247.5 -102t102.5 -247q0 -140 -104 -243.5t-246 -103.5h-735q-19 0 -19 -18v-143q0 -19 19 -19h735q108 0 206.5 41.5 t169 112.5t112.5 168.5t42 204.5q0 144 -70.5 265.5t-192.5 192t-267 70.5h-33q-52 215 -224.5 351.5t-392.5 136.5q-227 0 -401.5 -141.5t-221.5 -361.5q-138 -31 -242 -129.5t-145 -236.5v-2q-2 -4 -2 -8zM117 -243q0 39 28 64q24 25 63 25h959q39 0 65.5 -25.5 t26.5 -63.5t-27 -65t-65 -27h-959q-37 0 -64 27t-27 65z" />
|
||||
<glyph unicode="" horiz-adv-x="2377" d="M3 454q0 159 99.5 282.5t254.5 158.5q41 188 189.5 307.5t342.5 119.5q189 0 337.5 -116.5t192.5 -298.5h29q188 0 321.5 -132.5t133.5 -320.5q0 -123 -61 -228t-166 -166t-228 -61h-990q-92 0 -176.5 36t-145.5 97t-97 145.5t-36 176.5zM159 454q0 -122 87.5 -209.5 t211.5 -87.5h990q124 0 212.5 87.5t88.5 209.5t-88.5 209t-212.5 87h-147q-16 0 -16 16l-7 52q-16 151 -127 250.5t-262 99.5t-262 -100t-125 -250l-7 -45q0 -16 -17 -16l-48 -7q-115 -10 -193 -95t-78 -201zM1103 1384q-16 -15 8 -22q66 -29 114 -59q18 -5 24 3 q97 92 226 92q130 0 224.5 -86.5t105.5 -213.5l10 -68h151q104 0 178.5 -74.5t74.5 -177.5q0 -96 -65.5 -167t-162.5 -82q-16 0 -16 -17v-121q0 -17 16 -17q161 10 272 127t111 277q0 169 -119.5 288.5t-288.5 119.5h-16q-42 160 -175.5 263.5t-299.5 103.5 q-225 0 -372 -169z" />
|
||||
<glyph unicode="" horiz-adv-x="2171" d="M-294 86q0 39 27 65t67 26h1991q40 0 65.5 -25.5t25.5 -65.5q0 -37 -26.5 -62t-64.5 -25h-1991q-40 0 -67 25t-27 62zM-15 421q0 38 29 64q24 24 63 24h1992q37 0 61.5 -25t24.5 -63t-25 -64.5t-61 -26.5h-1992q-38 0 -65 27t-27 64zM-1 675q0 -14 16 -14h153q11 0 22 17 q38 83 113.5 136t165.5 60l58 8q18 0 18 19l7 53q17 173 146.5 288.5t303.5 115.5q173 0 301.5 -114t146.5 -286l8 -61q0 -18 21 -18h170q103 0 187.5 -55t125.5 -146q11 -17 22 -17h153q19 0 15 24q-47 164 -186 268t-317 104h-34q-53 213 -223.5 348.5t-389.5 135.5 q-224 0 -397.5 -140.5t-222.5 -358.5q-136 -32 -239 -129t-143 -232v4q-1 -3 -1 -10zM171 -241q0 38 28 63q24 24 64 24h1993q38 0 64.5 -25t26.5 -62q0 -38 -27 -65t-64 -27h-1993q-37 0 -64.5 27.5t-27.5 64.5z" />
|
||||
<glyph unicode="" horiz-adv-x="2211" d="M2 528q0 -213 148.5 -366t362.5 -163q18 0 18 18v143q0 18 -18 18q-137 7 -233 109t-96 241q0 132 90 230t223 114l57 3q21 0 21 20l7 57q17 173 146 289.5t304 116.5q174 0 304.5 -116.5t148.5 -289.5l8 -62q0 -18 18 -18h172q142 0 246 -102t104 -242 q0 -139 -96.5 -241t-232.5 -109q-21 0 -21 -18v-143q0 -18 21 -18q104 3 198.5 46.5t162.5 114t108 167t40 201.5q0 143 -71.5 263.5t-193.5 190t-265 69.5h-34q-52 214 -224.5 350t-392.5 136q-225 0 -400 -139.5t-224 -357.5q-177 -41 -291.5 -184.5t-114.5 -327.5z M583 -240q15 -34 49 -49q32 -16 66.5 -2t47.5 48q16 35 3 68.5t-48 46.5q-32 17 -66 3t-50 -50q-15 -26 -2 -65zM650 61q0 -25 17 -48.5t50 -34.5q31 -18 66 -1.5t47 63.5l96 361q10 40 -8.5 71.5t-56.5 38.5q-13 3 -27 3q-25 0 -49.5 -16t-34.5 -47l-95 -362q-5 -22 -5 -28 zM849 -535q0 -19 6 -31q14 -35 48 -48q15 -8 35 -8q10 0 32 6q36 13 51.5 48t-0.5 70t-48 49t-67 0q-30 -13 -43.5 -38t-13.5 -48zM927 -257q0 -59 67 -78q18 -4 27 -4q24 0 41 8q32 12 44 61l179 671q10 38 -7 69t-53 41q-14 3 -28 3q-27 0 -52.5 -16t-31.5 -47l-182 -676 q-4 -22 -4 -32zM1294 -208q0 -19 8 -34q13 -31 47 -47q18 -7 36 -7q10 0 32 6q34 13 49 47q12 35 -0.5 67t-44.5 48q-36 17 -69.5 3t-49.5 -50q-8 -15 -8 -33zM1370 66q0 -23 17 -48t48 -35q25 -5 29 -5q15 0 39 11q32 15 43 56l96 356q0 3 2 13t2 15q0 25 -16.5 49 t-48.5 33q-14 3 -29 3q-26 0 -49.5 -15.5t-32.5 -47.5l-96 -358q0 -3 -2 -12t-2 -15z" />
|
||||
<glyph unicode="" horiz-adv-x="1371" d="M-62 -283h43l733 1086q15 29 -16 29h-303l319 581q14 29 -20 29h-407q-17 0 -31 -19l-296 -789q-4 -29 21 -29h292zM846 392h28l557 818q8 13 4 21t-18 8h-224l233 430q19 32 -19 32h-292q-21 0 -32 -20l-221 -585q-10 -30 20 -30h219z" />
|
||||
<glyph unicode="" horiz-adv-x="2211" d="M-5 522q0 -211 149 -364t361 -160q20 0 20 18v142q0 19 -20 19q-135 7 -232 108.5t-97 236.5q0 134 90.5 233.5t223.5 110.5l56 8q21 0 21 19l8 53q16 175 145 293t305 118q173 0 303.5 -117t149.5 -289l7 -62q0 -19 19 -19h171q144 0 247 -103t103 -245 q0 -135 -96.5 -236.5t-232.5 -108.5q-21 0 -21 -19v-142q0 -18 21 -18q213 7 361 159.5t148 364.5q0 143 -71.5 264.5t-193.5 192t-265 70.5h-33q-53 214 -225 350.5t-392 136.5q-225 0 -400 -140t-224 -357q-180 -46 -293 -188.5t-113 -328.5zM573 -227q0 -27 17 -54 t50 -37q37 -11 68.5 4t42.5 60l15 65q8 36 -10 67.5t-55 42.5q-36 11 -68.5 -8t-42.5 -57l-15 -63q-2 -6 -2 -20zM654 82q0 -35 26 -61q25 -27 61 -27q38 0 64 25.5t26 62.5t-26 62.5t-64 25.5q-37 0 -62 -25t-25 -63zM719 325q-2 -25 14 -48.5t51 -34.5q33 -10 66.5 7.5 t44.5 54.5l30 96q12 39 -7.5 69.5t-58.5 41.5q-35 11 -67 -7t-43 -53l-27 -98q-3 -27 -3 -28zM842 -560q0 -27 17 -52.5t52 -35.5q14 -3 26 -3q69 0 85 65l15 63q10 40 -9 72.5t-57 39.5q-34 11 -66.5 -7.5t-43.5 -54.5l-15 -63q-4 -18 -4 -24zM926 -250q0 -36 26 -62 t62 -26q38 0 63 25t25 63q0 37 -25 62t-63 25t-63 -25t-25 -62zM992 -8q-2 -24 14.5 -50t48.5 -32q37 -10 68 6t44 60l28 96q11 35 -7.5 67t-56.5 43q-35 11 -68 -8.5t-44 -56.5l-24 -96q-3 -14 -3 -29zM1287 -237q0 -27 16 -51.5t49 -34.5q5 0 15 -2t15 -2q65 0 81 70 l15 64q11 34 -7.5 67t-54.5 44q-39 10 -71.5 -8.5t-43.5 -56.5l-11 -63q0 -2 -1.5 -12t-1.5 -15zM1366 78q0 -37 26 -61q24 -26 62 -26t63 25t25 62q0 38 -25 63t-63 25t-63 -25t-25 -63zM1431 325q0 -27 17.5 -51.5t52.5 -34.5q3 0 12.5 -2t14.5 -2q17 0 39 10q33 17 43 56 l27 96q10 36 -8 67.5t-54 42.5q-37 11 -68.5 -6t-42.5 -54l-30 -97q0 -3 -1.5 -11.5t-1.5 -13.5z" />
|
||||
<glyph unicode="" horiz-adv-x="2217" d="M2 525q0 -179 104.5 -318.5t272.5 -190.5q14 -3 27 8l123 153q-142 0 -243 102t-101 246q0 135 90 234.5t224 110.5l56 7q18 0 18 14l8 59q20 176 148 291.5t303 115.5q176 0 307 -116.5t147 -290.5l8 -62q4 -18 23 -18h172q144 0 245 -101t101 -244q0 -133 -89.5 -232.5 t-220.5 -115.5q-20 0 -53 -6q-30 -3 -43 -24l-253 -298q-23 -29 -17.5 -67t33.5 -61q17 -20 61 -20q42 0 65 37l225 260q199 20 336 172t137 355q0 108 -41.5 206t-112 168.5t-168 112.5t-205.5 42h-34q-53 212 -226.5 347.5t-396.5 135.5q-149 0 -280.5 -63.5t-221.5 -177 t-121 -258.5q-180 -40 -293.5 -183.5t-113.5 -329.5zM363 -330q0 -10 6 -32q14 -34 47 -47q35 -16 71 -2t49 48q16 35 2 69t-49 46q-36 17 -69 3t-49 -50q-8 -17 -8 -35zM560 -70v-11q3 -36 32 -60q36 -20 66 -20q36 0 65 35l248 295q23 28 21 70q-4 34 -29.5 57t-55.5 23 q-8 0 -14 -1q-38 -8 -61 -35l-251 -296q-21 -24 -21 -57zM642 -573q0 -19 8 -34q13 -34 47 -50q17 -8 36 -8q20 0 33 8q36 14 49 44q16 35 2.5 71t-47.5 49q-35 16 -71.5 2t-48.5 -48q-8 -15 -8 -34zM821 -323v-12q4 -35 32.5 -58.5t58.5 -23.5q36 0 67 35l464 559 q19 22 19 55v13q-4 36 -29.5 57t-56.5 21h-14q-37 -2 -59 -33l-464 -555q-18 -26 -18 -58zM1195 -446q0 -14 6 -32q14 -35 49 -50q22 -6 33 -6q23 0 36 8q36 14 49 45q16 27 2 71q-13 33 -47 47l-37 10q-17 -3 -35 -8q-35 -13 -48 -47q-8 -19 -8 -38z" />
|
||||
<glyph unicode="" horiz-adv-x="2217" d="M2 527q0 184 115 327.5t291 184.5q50 218 225 358t400 140q220 0 392.5 -136.5t224.5 -350.5h35q143 0 265 -69.5t193 -190t71 -263.5q0 -105 -40 -201.5t-108 -167t-162.5 -114t-198.5 -46.5q-20 0 -20 18v142q0 19 20 19q136 7 232.5 109t96.5 241q0 140 -103.5 242 t-245.5 102h-173q-18 0 -18 18l-8 62q-18 173 -149 289.5t-304 116.5q-175 0 -304.5 -116.5t-145.5 -289.5l-8 -57q0 -20 -21 -20l-56 -3q-133 -16 -223.5 -114t-90.5 -230q0 -139 96 -241t233 -109q18 0 18 -19v-142q0 -18 -18 -18q-214 10 -362.5 163t-148.5 366z M574 -186q0 10 3 21l174 615q10 31 34.5 47t49.5 16q12 0 27 -3q38 -7 57 -39t8 -71l-174 -611q-20 -68 -88 -68q-5 0 -11 2q-9 3 -12 3q-34 10 -51 35.5t-17 52.5zM854 -486l259 936q7 31 32 47t53 16q13 0 28 -3q36 -10 53 -41.5t7 -68.5l-260 -937q-6 -27 -30.5 -46 t-53.5 -19q-16 0 -28 5q-32 8 -55 42q-17 27 -5 69zM1294 -189q0 11 3 24l174 615q9 31 32.5 47t49.5 16q14 0 29 -3q32 -9 48.5 -33t16.5 -50q0 -5 -2 -14.5t-2 -12.5l-173 -611q-6 -31 -30.5 -49.5t-54.5 -18.5l-26 5q-32 9 -48.5 34t-16.5 51z" />
|
||||
<glyph unicode="" horiz-adv-x="2228" d="M-1 525q0 185 115.5 329t296.5 185q47 220 221.5 361.5t400.5 141.5q223 0 396.5 -136t226.5 -352h35q108 0 206 -42t168 -112.5t111 -168.5t41 -206q0 -214 -146.5 -367t-359.5 -160q-20 0 -20 18v143q0 18 20 18q135 11 231 111.5t96 236.5q0 143 -102.5 246 t-244.5 103h-173q-19 0 -23 19l-8 61q-11 114 -74.5 206.5t-163.5 145t-216 52.5q-176 0 -305.5 -116.5t-146.5 -290.5l-7 -58q0 -15 -17 -15l-56 -8q-135 -11 -225.5 -110t-90.5 -235q0 -139 94.5 -239.5t231.5 -108.5q19 0 19 -18v-143q0 -18 -19 -18q-105 5 -200 48 t-163.5 113t-108.5 165.5t-40 200.5zM577 -197q0 5 2 14t2 12l29 110q11 36 42.5 54.5t68.5 7.5q36 -11 55 -42.5t8 -68.5l-26 -108q-21 -70 -81 -70q-4 0 -9.5 0.5t-12 1.5t-10.5 1q-34 10 -51 36.5t-17 51.5zM718 317q0 4 1.5 12t1.5 10l32 112q10 31 34 47.5t50 16.5 l27 -3q36 -11 54.5 -42.5t7.5 -68.5l-27 -107q-11 -44 -43 -62.5t-69 -2.5q-35 10 -52 36t-17 52zM849 -531q0 5 2 13.5t2 12.5l30 106q10 38 42.5 57.5t69.5 8.5q36 -11 54.5 -43.5t7.5 -69.5l-27 -111q-17 -65 -85 -65q-8 0 -26 4q-35 6 -52.5 32.5t-17.5 54.5zM992 -22 q0 14 3 27l30 110q11 36 43.5 54.5t69.5 7.5q36 -10 54 -41.5t6 -69.5l-27 -106q-20 -72 -76 -72q-7 0 -33 6q-35 7 -52.5 31.5t-17.5 52.5zM1293 -201q0 11 3 26l30 106q11 38 43.5 57.5t67.5 9.5q37 -11 56.5 -43.5t9.5 -67.5l-31 -113q-16 -65 -85 -65q-13 0 -27 3 q-32 6 -49 32.5t-18 54.5zM1440 307q0 5 5 28l32 110q6 34 31 50.5t52 14.5q7 1 26 -3q35 -8 55 -41q19 -27 8 -69l-26 -110q-21 -69 -81 -69q-10 0 -36 6q-33 10 -49.5 33.5t-16.5 49.5z" />
|
||||
<glyph unicode="" horiz-adv-x="2217" d="M-1 523q0 185 113.5 328.5t292.5 187.5q50 218 224.5 358t400.5 140q220 0 392.5 -136.5t224.5 -350.5h34q143 0 265 -70.5t193 -192t71 -264.5q0 -212 -148 -365t-360 -160q-21 0 -21 18v142q0 19 21 19q136 7 232.5 108.5t96.5 237.5q0 142 -103 245t-247 103h-172 q-19 0 -19 18l-8 62q-18 173 -148.5 290t-303.5 117q-176 0 -305.5 -118t-146.5 -293l-7 -53q0 -20 -20 -20l-56 -7q-133 -11 -223.5 -110.5t-90.5 -233.5q0 -136 96.5 -237.5t232.5 -108.5q18 0 18 -19v-142q0 -18 -18 -18q-214 7 -362.5 160t-148.5 365zM678 89 q0 36 25.5 62t62.5 26t62.5 -26t25.5 -62q0 -38 -25.5 -64.5t-62.5 -26.5t-62.5 26.5t-25.5 64.5zM678 -298q0 38 26 64q26 24 62 24q38 0 63 -25t25 -63t-25 -63t-63 -25t-63 25t-25 63zM1018 -117q0 37 27 66q26 26 61 26q38 0 65 -27.5t27 -64.5t-27 -63.5t-65 -26.5 q-36 0 -62 26.5t-26 63.5zM1018 269q0 37 27 64q28 26 61 26q38 0 65 -26.5t27 -63.5t-27 -62.5t-65 -25.5q-36 0 -62 25.5t-26 62.5zM1018 -507q0 36 27 65q26 26 61 26q38 0 65 -27t27 -64t-27 -62.5t-65 -25.5q-36 0 -62 25.5t-26 62.5zM1362 89q0 35 27 61.5t64 26.5 t62.5 -26t25.5 -62q0 -38 -25.5 -64.5t-62.5 -26.5t-64 27t-27 64zM1362 -298q0 35 27 64q25 24 64 24q37 0 62.5 -25t25.5 -63t-25.5 -63t-62.5 -25q-38 0 -64.5 25.5t-26.5 62.5z" />
|
||||
<glyph unicode="" horiz-adv-x="2211" d="M-1 527q0 184 115 327.5t291 184.5q49 218 224 357.5t401 139.5q220 0 392 -136t225 -350h33q143 0 265 -69.5t193 -190t71 -263.5q0 -214 -147.5 -368t-360.5 -161q-21 0 -21 18v142q0 19 21 19q137 7 233 109t96 241t-104 241.5t-246 102.5h-171q-20 0 -20 18l-7 62 q-17 172 -147.5 289t-304.5 117q-175 0 -305 -116.5t-147 -289.5l-7 -58q0 -19 -20 -19l-56 -3q-131 -6 -222.5 -107t-91.5 -237q0 -139 96 -241t233 -109q18 0 18 -19v-142q0 -18 -18 -18q-214 10 -362.5 163t-148.5 366zM631 433q0 39 39 105.5t73 107.5q34 38 41 45 l38 -43q41 -44 76.5 -108.5t35.5 -106.5q0 -63 -43 -105t-107 -42q-63 0 -108 43t-45 104zM949 8q0 44 25.5 102t62.5 109q29 41 71 90t63 69q22 20 27 25l26 -25q60 -53 136 -156q38 -53 63 -111t25 -103q0 -103 -72.5 -175.5t-177.5 -72.5q-103 0 -176 72t-73 176z M1106 718q0 67 104 171l25 -27q75 -92 75 -144q0 -41 -29.5 -70.5t-70.5 -29.5q-43 0 -73.5 29.5t-30.5 70.5z" />
|
||||
<glyph unicode="" horiz-adv-x="2211" d="M1 527q0 -177 106 -317t274 -191l-70 -179q-5 -23 15 -23h227l-111 -403h30l423 557q6 7 2 14.5t-15 7.5h-232l264 495q10 22 -15 22h-314q-15 0 -24 -15l-115 -306q-114 29 -188.5 124t-74.5 214q0 133 90 230.5t224 113.5l56 8q21 0 21 18l8 55q16 173 145.5 290 t304.5 117q174 0 304.5 -117t149.5 -290l7 -62q0 -19 19 -19h173q145 0 247 -100.5t102 -243.5q0 -136 -95 -235.5t-234 -109.5q-20 0 -20 -20v-146q0 -18 20 -18q104 3 198.5 47t162.5 114.5t108 167t40 200.5q0 144 -71 265.5t-193 192t-265 70.5h-35q-53 213 -225 348 t-393 135q-226 0 -401 -140t-224 -358q-176 -41 -291 -184.5t-115 -328.5zM871 -525q0 -25 16.5 -49.5t49.5 -34.5q22 -5 28 -5q14 0 40 11q33 15 44 56l30 112q10 35 -8.5 67t-56.5 43q-35 10 -67.5 -8.5t-43.5 -56.5l-29 -107q-3 -27 -3 -28zM1014 -16q0 -23 16.5 -46.5 t48.5 -35.5q36 -12 68 5t43 56l32 111q10 35 -8.5 67.5t-56.5 43.5q-36 10 -69.5 -8.5t-44.5 -55.5l-26 -108q-3 -27 -3 -29zM1315 -192q0 -27 17.5 -54t50.5 -37q4 0 12.5 -2t12.5 -2q21 0 40 8q31 12 45 61l26 108q11 37 -6.5 70.5t-53.5 44.5q-40 10 -72.5 -9t-39.5 -57 l-29 -110q-3 -9 -3 -21zM1461 319q-1 -25 15.5 -50t51.5 -40q12 -6 29 -6q15 0 35 9q33 16 47 61l31 110q3 21 3 23q0 27 -17.5 52.5t-52.5 35.5q-4 0 -12 2t-12 2q-27 0 -51.5 -16.5t-34.5 -51.5l-30 -108q-2 -8 -2 -23z" />
|
||||
<glyph unicode="" horiz-adv-x="2211" d="M2 527q0 -177 106 -317t275 -191l-70 -179q-5 -23 15 -23h227l-105 -459h30l418 613q6 7 2 14.5t-15 7.5h-232l264 495q11 23 -15 23h-315q-15 0 -25 -16l-114 -306q-114 29 -189 124t-75 214q0 133 90.5 231t224.5 114l56 7q21 0 21 19l7 54q17 173 146.5 290t304.5 117 q174 0 304.5 -117t149.5 -290l7 -62q0 -18 19 -18h172q145 0 248 -101.5t103 -243.5q0 -136 -95.5 -235.5t-235.5 -110.5q-20 0 -20 -19v-146q0 -18 20 -18q213 7 361.5 161t148.5 368q0 144 -71 265.5t-193 192t-266 70.5h-33q-53 212 -226 347.5t-393 135.5 q-226 0 -401 -140t-224 -358q-177 -41 -292 -184.5t-115 -328.5zM871 -509q0 -63 63 -82q3 0 12 -1.5t14 -1.5q27 0 52.5 15.5t34.5 51.5l240 927q10 38 -7 69t-53 42q-27 3 -28 3q-26 0 -50.5 -16t-33.5 -48l-241 -930q-3 -13 -3 -29zM1314 -182q0 -19 12 -40 q22 -35 53 -47q17 -5 32 -5q16 0 35 8q32 14 44 61l155 605q4 18 4 29q0 24 -17 48t-49 34q-27 3 -28 3q-27 0 -50 -15.5t-32 -48.5l-155 -608q0 -4 -2 -12t-2 -12z" />
|
||||
<glyph unicode="" horiz-adv-x="2211" d="M2 678q0 36 25 60t61 24h1289q35 0 58 -23.5t23 -60.5q0 -35 -23 -58t-58 -23h-1289q-36 0 -61 23t-25 58zM261 987q0 35 25 59q24 24 59 24h1290q34 0 58 -24t24 -59t-24 -59.5t-58 -24.5h-1290q-35 0 -59.5 24.5t-24.5 59.5zM432 375q0 34 26 58q23 23 59 23h1290 q36 0 60 -23t24 -58t-24.5 -60t-59.5 -25h-1290q-35 0 -60 25t-25 60zM1561 678q0 37 24 60.5t61 23.5h481q37 0 60.5 -24t23.5 -60q0 -35 -24 -58t-60 -23h-481q-37 0 -61 23t-24 58z" />
|
||||
<glyph unicode="" d="M-10 89q0 -38 28 -64q27 -27 65 -27h613q42 0 73.5 -30.5t31.5 -72.5q0 -43 -31.5 -74t-73.5 -31q-41 0 -73 32q-29 26 -65 26q-38 0 -64.5 -26.5t-26.5 -63.5q0 -35 27 -64q84 -84 202 -84t201.5 83t83.5 202q0 118 -83.5 202t-201.5 84h-613q-38 0 -65.5 -27.5 t-27.5 -64.5zM-10 411q0 -36 28 -62t65 -26h1158q118 0 201.5 83t83.5 201t-83.5 200.5t-201.5 82.5q-122 0 -202 -81q-24 -25 -24 -65t24.5 -64t62.5 -24t66 24q30 30 73 30q41 0 72 -30t31 -73q0 -42 -31 -73t-72 -31h-1158q-38 0 -65.5 -27.5t-27.5 -64.5zM258 662 q0 -14 17 -14h154q12 0 22 16q36 87 111.5 143.5t168.5 63.5l56 8q21 0 21 18l8 55q17 173 146.5 289.5t304.5 116.5t305 -116t148 -290l7 -62q0 -19 19 -19h174q145 0 247 -101t102 -243t-102.5 -244t-246.5 -102h-737q-18 0 -18 -19v-146q0 -18 18 -18h737q143 0 264.5 71 t192.5 193t71 265q0 141 -73 264q117 113 161 275l16 72q2 2 2 8q0 7 -15 17l-64 22q-91 28 -155 89.5t-91.5 131t-27.5 139.5q0 43 9 84l15 66q4 15 -15 24l-84 26q-70 17 -133 17q-59 0 -120 -14t-126 -43.5t-126.5 -85t-106.5 -130.5q-113 48 -247 48q-226 0 -399 -140 t-220 -357q-137 -32 -242 -131t-145 -235v-3q-3 -5 -3 -9zM1670 1397q50 88 138 134.5t182 46.5q22 0 33 -1q-2 -14 -2 -38q0 -151 84 -289t232 -207q-25 -60 -80 -110q-142 122 -337 122h-35q-43 201 -215 342z" />
|
||||
<glyph unicode="" d="M-146 86q0 -38 26.5 -63t66.5 -25h1004q38 0 63 25t25 63q0 40 -25 65.5t-63 25.5h-1004q-40 0 -66.5 -25.5t-26.5 -65.5zM135 411q0 -39 27 -64q25 -27 64 -27h1006q37 0 62.5 26.5t25.5 64.5q0 37 -25 62.5t-63 25.5h-1006q-38 0 -64.5 -25.5t-26.5 -62.5zM246 661 q0 -15 19 -15h152q14 0 24 15q36 87 111 144t168 64l58 8q20 0 20 18l7 55q17 174 147 290.5t306 116.5q175 0 303.5 -115t150.5 -289l8 -62q0 -18 18 -18h173q145 0 246.5 -102t101.5 -246q0 -141 -103 -244.5t-245 -103.5h-737q-19 0 -19 -18v-143q0 -18 19 -18h737 q144 0 266 70t193 191.5t71 265.5q0 142 -70 264q117 104 162 275l19 73q1 2 1 7q0 13 -16 17l-66 23q-70 21 -124 61t-85.5 89.5t-47 100.5t-15.5 101q0 43 11 93l15 65q7 15 -15 24l-88 26q-70 17 -132 17q-60 0 -121 -14t-126 -43.5t-125.5 -85t-105.5 -130.5 q-124 51 -254 51q-226 0 -400 -140.5t-221 -360.5q-139 -32 -243.5 -130.5t-145.5 -235.5v-3q-2 -2 -2 -8zM322 -245q0 -38 28 -64q26 -27 64 -27h1005q38 0 65.5 27t27.5 64t-27.5 62.5t-65.5 25.5h-1005q-38 0 -65 -25.5t-27 -62.5zM1665 1394q55 88 143.5 136.5 t181.5 46.5q18 0 27 -1v-26q0 -156 84.5 -297.5t231.5 -211.5q-27 -62 -82 -114q-141 126 -340 126h-34q-40 193 -212 341z" />
|
||||
<glyph unicode="" horiz-adv-x="2331" d="M-1 527q0 -213 148.5 -366t362.5 -163q19 0 19 18v143q0 18 -19 18q-137 7 -233 109t-96 241q0 133 90 231t224 114l56 3q20 0 20 19l8 58q17 173 147 290t305 117q173 0 303.5 -117t148.5 -290l8 -62q0 -18 19 -18h172q142 0 246 -102.5t104 -242.5q0 -139 -96 -241 t-233 -109q-21 0 -21 -18v-143q0 -18 21 -18q213 7 360.5 161t147.5 368q0 138 -68 257q125 117 164 279l15 73q3 2 3 7q0 12 -18 17l-59 18q-91 27 -155.5 90.5t-91.5 133t-27 140.5q-1 39 8 84l15 62q5 14 -15 23l-85 27q-67 20 -140 20q-56 0 -116 -13.5t-124.5 -43 t-126.5 -85t-107 -130.5q-120 52 -252 52q-226 0 -401 -140t-224 -358q-177 -41 -292 -184.5t-115 -328.5zM583 -223q14 -34 49 -48q31 -16 66 -2.5t48 47.5q16 35 2.5 69t-47.5 47q-32 16 -66 2t-50 -49q-16 -25 -2 -66zM650 79q0 -24 16.5 -48t48.5 -34q41 -16 72 0.5 t43 60.5l93 342q11 40 -8 71.5t-57 39.5q-35 11 -67.5 -7t-43.5 -54l-93 -343q0 -3 -2 -13t-2 -15zM849 -518q0 -20 6 -31q14 -35 48 -48q15 -8 35 -8q10 0 32 6q36 13 51.5 48t-0.5 70t-48 49t-66 0q-31 -13 -44.5 -38t-13.5 -48zM928 -235q0 -61 67 -83q15 -5 26 -5 q17 0 37 9q35 13 49 61l176 653q10 38 -7 69t-53 42q-37 11 -69.5 -7t-42.5 -54l-180 -658q-3 -14 -3 -27zM1295 -191q0 -20 7 -33q13 -32 47 -47q19 -8 37 -8q10 0 32 6q35 14 48 47q13 35 0.5 67.5t-44.5 48.5q-35 16 -69.5 2.5t-50.5 -49.5q-7 -13 -7 -34zM1371 82 q0 -25 17 -48.5t49 -33.5q27 -3 29 -3q64 0 81 65l94 338q10 38 -8 69t-54 42q-37 11 -68 -6.5t-42 -54.5l-94 -339q-4 -18 -4 -29zM1441 1389q50 92 138.5 140t187.5 47q18 0 27 -2v-33q0 -156 83.5 -293.5t231.5 -207.5q-31 -70 -80 -114q-141 125 -347 125h-34 q-41 200 -207 338z" />
|
||||
<glyph unicode="" horiz-adv-x="2331" d="M-1 527q0 -213 148.5 -366t362.5 -163q18 0 18 18v146q0 19 -18 19q-139 11 -234 110.5t-95 235.5q0 134 90.5 233.5t223.5 110.5l56 8q20 0 20 18l8 55q17 173 146.5 289.5t304.5 116.5q174 0 304 -116t148 -290l8 -62q0 -19 19 -19h172q145 0 247.5 -101t102.5 -243 q0 -136 -95 -235.5t-234 -110.5q-21 0 -21 -19v-146q0 -18 21 -18q213 7 360.5 161t147.5 368q0 143 -73 264q128 116 165 275l16 77q1 1 1 7q0 12 -17 17l-62 18q-91 26 -155 88t-91 131.5t-27 140.5q0 39 10 84l15 66q4 15 -15 24l-84 26q-70 17 -133 17q-58 0 -119 -14 t-127 -43.5t-128 -85t-107 -130.5q-113 48 -249 48q-226 0 -400.5 -139.5t-224.5 -357.5q-176 -42 -291 -185.5t-115 -327.5zM817 31q-8 -23 15 -23h230l-140 -442h32l445 596q6 7 2.5 14.5t-14.5 7.5h-234l246 449q11 23 -15 23h-314q-13 0 -23 -15zM1435 1391 q53 91 141.5 139t184.5 48q20 0 29 -2v-33q0 -154 83 -291t231 -209q-25 -59 -80 -114q-147 126 -343 126h-34q-45 199 -212 336z" />
|
||||
<glyph unicode="" horiz-adv-x="2331" d="M-5 525q0 -212 149 -366t363 -161q20 0 20 18v143q0 18 -20 18q-136 7 -233 110t-97 238q0 136 91 235.5t225 110.5l57 7q20 0 20 19l7 54q17 176 147 293.5t306 117.5q174 0 305.5 -117t149.5 -290l8 -61q0 -20 18 -20h172q145 0 248.5 -103t103.5 -246 q0 -88 -44 -166.5t-120.5 -127.5t-166.5 -54q-21 0 -21 -18v-143q0 -18 21 -18q105 3 200 46.5t163 114t108 166.5t40 200q0 141 -73 268q133 119 169 280l15 73q2 2 2 8q0 9 -17 17l-60 19q-70 21 -124.5 62.5t-86.5 92.5t-48.5 103.5t-16.5 102.5q-3 38 9 88l15 62 q7 16 -15 25l-85 26q-65 18 -138 18q-58 0 -119.5 -14t-127 -44t-127.5 -86.5t-107 -131.5q-130 49 -250 49q-226 0 -402 -140.5t-226 -359.5q-180 -46 -293.5 -189t-113.5 -329zM573 -227q0 -27 17 -54t50 -37q37 -11 68.5 4t42.5 60l15 65q8 36 -10 67.5t-55 42.5 q-36 11 -68.5 -8t-42.5 -57l-15 -63q-2 -6 -2 -20zM654 82q0 -35 26 -61q25 -27 61 -27q38 0 64 25.5t26 62.5t-26 62.5t-64 25.5q-37 0 -62 -25t-25 -63zM719 325q-2 -25 14 -48.5t51 -34.5q33 -10 66.5 7.5t44.5 54.5l30 96q12 39 -7.5 69.5t-58.5 41.5q-35 11 -67 -7 t-43 -53l-27 -98q-3 -27 -3 -28zM842 -560q0 -27 17 -52.5t52 -35.5q14 -3 26 -3q69 0 85 65l15 63q10 40 -9 72.5t-57 39.5q-34 11 -66.5 -7.5t-43.5 -54.5l-15 -63q-4 -18 -4 -24zM926 -250q0 -36 26 -62t62 -26q38 0 63 25t25 63q0 37 -25 62t-63 25t-63 -25t-25 -62z M992 -8q-2 -24 14.5 -50t48.5 -32q37 -10 68 6t44 60l28 96q11 35 -7.5 67t-56.5 43q-35 11 -68 -8.5t-44 -56.5l-24 -96q-3 -14 -3 -29zM1287 -237q0 -27 16 -51.5t49 -34.5q5 0 15 -2t15 -2q65 0 81 70l15 64q11 34 -7.5 67t-54.5 44q-39 10 -71.5 -8.5t-43.5 -56.5 l-11 -63q0 -2 -1.5 -12t-1.5 -15zM1366 78q0 -37 26 -61q24 -26 62 -26t63 25t25 62q0 38 -25 63t-63 25t-63 -25t-25 -63zM1431 325q0 -27 17.5 -51.5t52.5 -34.5q3 0 12.5 -2t14.5 -2q17 0 39 10q33 17 43 56l27 96q10 36 -8 67.5t-54 42.5q-37 11 -68.5 -6t-42.5 -54 l-30 -97q0 -3 -1.5 -11.5t-1.5 -13.5zM1438 1397q51 93 139.5 143t183.5 49q15 0 32 -3v-34q-2 -157 82.5 -294.5t236.5 -206.5q-32 -64 -85 -118q-148 122 -344 122h-33q-50 209 -212 342z" />
|
||||
<glyph unicode="" horiz-adv-x="2331" d="M-1 525q0 -179 106 -320.5t274 -188.5q14 -3 27 8l120 150q-140 0 -242.5 104t-102.5 247q0 134 90 231.5t225 109.5l53 7q20 0 20 20l8 57q20 174 150 291t305 117q174 0 305 -117.5t150 -290.5l7 -62q0 -18 19 -18h173q142 0 244 -102t102 -243q0 -136 -90 -236.5 t-221 -111.5q-72 -9 -92 -30l-250 -310q-24 -27 -19 -67q4 -38 33.5 -63t60.5 -25q36 0 68 40l217 276q99 10 187.5 56t152 115.5t100.5 162.5t37 193q0 139 -73 260q130 118 169 280v10l21 82l-82 28q-92 27 -156 89t-91.5 131t-27.5 140q0 41 9 85l20 83l-90 25q-1 1 -3 2 t-3 2q-2 1 -4.5 2t-3.5 1q-70 17 -133 17q-61 -1 -122.5 -15t-128 -44t-128.5 -86t-107 -132q-120 52 -250 52q-226 0 -401 -140t-225 -358q-181 -44 -294 -186.5t-113 -327.5zM394 -327q0 -21 7 -33q14 -35 46 -49q35 -16 71 -3t49 47q16 35 1.5 71.5t-48.5 48.5 q-35 16 -70.5 2.5t-48.5 -48.5q-7 -15 -7 -36zM591 -66v-12q3 -36 35 -59q21 -24 58 -23t70 34l248 311q22 28 17.5 66.5t-32.5 61.5t-67 19t-61 -36l-248 -305q-20 -23 -20 -57zM651 -622q14 -34 50 -48q17 -8 35 -8q10 0 32 6q34 14 47 47q13 35 1 69t-47 51 q-35 13 -67 -0.5t-48 -46.5q-18 -31 -3 -70zM822 -338v-9q4 -36 33.5 -61.5t59.5 -25.5q35 0 64 36l461 572q23 27 20 66t-31 62q-32 23 -70.5 19.5t-60.5 -31.5l-456 -572q-20 -25 -20 -56zM1195 -456q0 -18 8 -35q13 -36 51 -50q36 -14 65 -2q35 14 49 49q16 35 3 68 t-48 50q-35 13 -69.5 0t-50.5 -45q-8 -19 -8 -35zM1440 1391q50 92 138.5 140.5t187.5 46.5q19 0 28 -1v-32q0 -155 85.5 -293.5t233.5 -209.5q-40 -75 -84 -119q-142 126 -343 126h-35q-49 206 -211 342z" />
|
||||
<glyph unicode="" horiz-adv-x="2331" d="M-1 528q0 -213 149 -366.5t363 -163.5q19 0 19 18v143q0 18 -19 18q-137 8 -233.5 110t-96.5 241q0 133 90.5 231.5t224.5 114.5l57 4q21 0 21 18l7 58q17 173 147 290.5t305 117.5q174 0 305 -117.5t149 -290.5l8 -61q0 -19 18 -19h173q143 0 247.5 -102.5t104.5 -243.5 q0 -138 -97 -240.5t-234 -110.5q-21 0 -21 -18v-143q0 -18 21 -18q213 7 361.5 161.5t148.5 368.5q0 141 -73 262q129 117 169 279l15 77q2 1 2 7q0 12 -17 17l-60 19q-91 26 -156 88.5t-92.5 131.5t-27.5 137q-3 36 9 88l15 65q7 16 -15 26l-84 26q-72 16 -135 16 q-60 0 -122 -14t-128 -43.5t-128 -85t-107 -130.5q-132 48 -249 48q-226 0 -401.5 -140.5t-225.5 -359.5q-177 -41 -292 -185t-115 -329zM592 -201q0 -27 17.5 -52.5t52.5 -35.5q37 -11 68.5 4.5t42.5 59.5l153 626q10 40 -9 72t-56 39q-35 11 -67.5 -7t-43.5 -54l-155 -629 q-3 -21 -3 -23zM870 -529q0 -25 16.5 -49t47.5 -33q18 -4 26 -4q70 0 87 63l240 953q10 39 -7.5 69.5t-53.5 41.5q-37 11 -70.5 -7.5t-41.5 -53.5l-241 -952q0 -2 -1.5 -12.5t-1.5 -15.5zM1314 -210q0 -57 67 -79q22 -6 32 -6q18 0 37 8q32 13 41 62l154 626q10 38 -8 69 t-54 42q-37 11 -70 -7t-41 -54l-153 -629q-5 -27 -5 -32zM1440 1395q51 93 139.5 141.5t187.5 46.5q18 0 28 -1v-32q-2 -155 82.5 -293t236.5 -210q-36 -73 -84 -114q-147 121 -344 121h-33q-44 203 -213 341z" />
|
||||
<glyph unicode="" horiz-adv-x="2331" d="M-1 527q0 -213 148.5 -366t362.5 -163q18 0 18 18v146q0 19 -18 19q-139 11 -234 110.5t-95 235.5q0 132 90 230t224 114l56 8q20 0 20 18l8 55q17 173 146.5 289.5t304.5 116.5q174 0 304 -116t148 -290l8 -62q0 -19 19 -19h172q144 0 247 -101t103 -243 q0 -136 -95 -235.5t-234 -110.5q-21 0 -21 -19v-146q0 -18 21 -18q213 7 360.5 161t147.5 368q0 139 -69 259q130 117 169 279v14l20 84l-81 22q-130 39 -202 144.5t-72 216.5q0 42 9 83l21 85l-91 27l-15 4q-75 15 -132 15q-60 0 -122 -14t-128.5 -44t-128 -85.5 t-106.5 -131.5q-119 52 -252 52q-226 0 -399.5 -140t-220.5 -358q-180 -41 -295.5 -184t-115.5 -328zM575 -195q0 -24 16 -47.5t48 -35.5q35 -12 68 5.5t44 54.5l30 113q11 33 -7.5 66t-54.5 44q-37 10 -69.5 -9t-44.5 -57l-26 -106q-4 -20 -4 -28zM713 310q0 -20 10 -38 q12 -27 58 -41q39 -10 70 6.5t43 55.5l29 110q11 37 -7 68.5t-54 41.5q-39 10 -71.5 -7.5t-43.5 -55.5l-28 -108q-6 -22 -6 -32zM847 -528q0 -27 16.5 -52.5t48.5 -35.5q18 -5 24 -5q14 0 40 11q32 13 46 59l32 112q11 34 -7.5 67t-54.5 44q-39 10 -71.5 -8.5t-43.5 -56.5 l-27 -108q-3 -13 -3 -27zM989 -19q0 -24 16 -47.5t49 -35.5q36 -11 68 6t43 56l29 111q11 34 -6.5 66.5t-52.5 43.5q-39 10 -72.5 -9t-44.5 -57l-26 -107q0 -3 -1 -8.5t-1.5 -10t-0.5 -8.5zM1288 -207q0 -61 67 -80q13 -3 27 -3q24 0 39 7q32 13 46 61l30 109 q10 40 -8.5 71.5t-56.5 39.5q-37 11 -68.5 -7.5t-42.5 -54.5l-30 -111q0 -4 -1 -10.5t-1.5 -12t-0.5 -9.5zM1435 307q0 -25 17 -49t50 -34q23 -5 25 -5q14 0 40 11q33 13 47 59l29 111q10 35 -9 67t-56 43q-35 11 -67.5 -7.5t-43.5 -55.5l-27 -113q-5 -23 -5 -27zM1440 1391 q51 90 141.5 140t193.5 50h22q-2 -28 -2 -40q0 -152 84 -289t232 -209q-34 -67 -84 -117q-149 129 -346 129h-34q-40 189 -207 336z" />
|
||||
<glyph unicode="" horiz-adv-x="2331" d="M-1 525q0 -212 149 -366t363 -161q19 0 19 18v143q0 18 -19 18q-136 7 -233 109.5t-97 238.5t90.5 235t224.5 110l57 8q21 0 21 18l7 55q17 175 146.5 293t305.5 118q174 0 305 -117.5t149 -290.5l8 -61q0 -19 18 -19h173q145 0 248.5 -103t103.5 -246 q0 -136 -97.5 -238.5t-233.5 -109.5q-21 0 -21 -18v-143q0 -18 21 -18q140 4 257.5 76t185 192t67.5 259q0 144 -70 265q119 107 163 275l18 77q2 2 2 8q0 12 -17 17l-65 18q-70 21 -124.5 61t-86 89.5t-47 101t-15.5 101.5q0 43 11 92l12 66q4 15 -15 24l-85 26 q-70 17 -133 17q-59 0 -120 -14t-126 -44t-126 -85.5t-106 -130.5q-129 53 -254 53q-226 0 -401.5 -140.5t-225.5 -359.5q-180 -45 -293.5 -188t-113.5 -329zM680 89q0 -38 25.5 -64.5t62.5 -26.5q38 0 63.5 26.5t25.5 64.5q0 36 -25.5 62t-63.5 26q-37 0 -62.5 -26 t-25.5 -62zM680 -299q0 -36 26 -62t62 -26q38 0 63.5 25t25.5 63t-25.5 63t-63.5 25t-63 -25t-25 -63zM1022 -118q0 -39 26 -63q26 -26 62 -26q38 0 65 26t27 63q0 38 -27 65.5t-65 27.5q-35 0 -61.5 -27.5t-26.5 -65.5zM1022 270q0 -38 26 -62q26 -26 62 -26q39 0 65.5 25 t26.5 63q0 37 -27 63.5t-65 26.5q-36 0 -62 -26.5t-26 -63.5zM1022 -510q0 -35 26 -61q27 -27 62 -27q38 0 65 25.5t27 62.5q0 38 -27 65.5t-65 27.5q-35 0 -61.5 -27.5t-26.5 -65.5zM1367 89q0 -38 26.5 -64.5t63.5 -26.5t62.5 26.5t25.5 64.5q0 36 -25.5 62t-62.5 26 t-63.5 -26t-26.5 -62zM1367 -299q0 -36 26 -62t64 -26q37 0 62.5 25.5t25.5 62.5q0 38 -25 63t-63 25t-64 -25t-26 -63zM1440 1395q50 87 138.5 134.5t182.5 49.5q23 0 34 -2v-33q0 -155 83.5 -292.5t232.5 -209.5q-25 -58 -81 -114q-142 126 -344 126h-33q-44 197 -213 341 z" />
|
||||
<glyph unicode="" horiz-adv-x="2331" d="M2 529q0 -213 148.5 -366t362.5 -164q20 0 20 19v142q0 19 -20 19q-137 7 -233 109t-96 241q0 133 90 231t224 114l56 4q21 0 21 19l8 58q17 173 146.5 289.5t304.5 116.5q174 0 305 -117t149 -289l7 -61q0 -20 19 -20h173q142 0 246 -102.5t104 -242.5 q0 -139 -96.5 -241t-233.5 -109q-20 0 -20 -19v-142q0 -19 20 -19q140 5 257.5 77t185 192.5t67.5 260.5q0 139 -73 261q126 120 165 275l14 78q3 1 3 7q0 12 -17 17l-62 18q-91 26 -156 87.5t-92.5 129.5t-27.5 137q0 46 12 91l15 65q4 16 -15 25l-85 26q-72 16 -135 16 q-58 0 -119 -14t-126.5 -43t-127.5 -84t-107 -128q-130 49 -249 49q-226 0 -401 -140.5t-225 -358.5q-177 -42 -291.5 -185.5t-114.5 -328.5zM635 435q0 -61 45 -104t109 -43t106.5 42.5t42.5 104.5q0 41 -37 109t-69 105q-25 28 -43 46l-38 -44q-43 -47 -79.5 -110.5 t-36.5 -105.5zM954 10q0 -105 73 -177t176 -72q105 0 178 73t73 176q0 87 -86 213q-71 93 -137 159q-13 9 -28 24l-25 -24q-63 -58 -136 -158q-38 -53 -63 -111t-25 -103zM1111 721q0 -41 30.5 -70t74.5 -29q41 0 70 29t29 70q0 68 -99 173l-26 -29q-30 -32 -54.5 -74.5 t-24.5 -69.5zM1438 1395q56 91 142.5 137.5t182.5 46.5q19 0 29 -2v-32q0 -154 84.5 -291.5t233.5 -210.5q-25 -60 -80 -110q-148 122 -343 122h-34q-49 210 -215 340z" />
|
||||
<glyph unicode="" horiz-adv-x="2331" d="M1 529q0 -177 106 -316.5t275 -190.5l-69 -181q-7 -22 15 -22h226l-138 -446h30l451 600q6 7 2.5 14.5t-14.5 7.5h-232l264 495q11 23 -15 23h-316q-14 0 -24 -16l-114 -307q-114 29 -189.5 124.5t-75.5 214.5q0 133 90.5 231t224.5 114l56 8q21 0 21 19l8 54 q17 173 146.5 290t304.5 117q174 0 305 -117.5t149 -289.5l8 -61q0 -20 18 -20h173q145 0 248 -101.5t103 -243.5q0 -136 -96 -236t-235 -110q-20 0 -20 -20v-145q0 -19 20 -19q213 7 361.5 161.5t148.5 368.5q0 139 -70 261q124 119 162 280l19 73q1 1 1 7q0 13 -16 17 l-66 18q-92 27 -156 89.5t-90.5 130t-25.5 134.5q0 44 11 91l12 65q4 16 -15 25l-85 26q-75 15 -132 15q-59 1 -120.5 -12.5t-126.5 -43t-125.5 -84t-105.5 -128.5q-136 49 -254 49q-226 0 -401 -140.5t-225 -358.5q-177 -42 -292 -185.5t-115 -328.5zM873 -523 q0 -25 16.5 -50t48.5 -35q25 -5 29 -5q15 0 39 11q33 16 45 56l30 112q10 35 -8.5 67.5t-56.5 43.5q-35 10 -67.5 -9t-43.5 -57l-28 -107q0 -3 -2 -12.5t-2 -14.5zM1016 -16q0 -69 65 -80q23 -5 29 -5q60 0 82 67l32 109q10 35 -9 67.5t-57 43.5q-36 11 -68.5 -7.5 t-43.5 -56.5l-27 -108q-3 -14 -3 -30zM1315 -194q2 -27 19 -51.5t51 -36.5q26 -6 36 -6q54 0 75 72l26 109q11 37 -6.5 70t-53.5 44q-40 10 -72.5 -9t-39.5 -57l-31 -110q0 -4 -2 -12.5t-2 -12.5zM1441 1395q53 89 143 137.5t189 48.5h22v-34q0 -102 36 -198t108.5 -177 t171.5 -129q-27 -60 -81 -110q-146 125 -343 125h-34q-45 200 -212 337zM1462 318q0 -69 69 -88q22 -4 32 -4q58 0 79 69l30 111q1 8 1 23q2 27 -15 52.5t-52 35.5q-2 0 -10.5 1.5t-13.5 1.5q-26 0 -51 -16.5t-35 -50.5l-31 -109q0 -1 -1.5 -11t-1.5 -15z" />
|
||||
<glyph unicode="" horiz-adv-x="2331" d="M-3 531q0 -178 105.5 -317.5t274.5 -190.5l-70 -180q-4 -22 16 -22h227l-104 -374h31l417 527q6 8 1.5 15.5t-15.5 7.5h-232l264 494q12 23 -15 23h-315q-14 0 -24 -15l-115 -307q-115 29 -190 124.5t-75 214.5q0 133 91 231.5t225 114.5l56 8q20 0 20 18l8 55 q17 173 147 290t305 117q173 0 304.5 -117.5t150.5 -289.5l6 -63q0 -18 20 -18h172q145 0 248 -101.5t103 -244.5q0 -136 -95.5 -236t-234.5 -111q-21 0 -21 -18v-147q0 -19 21 -19q140 5 257.5 77t185 192.5t67.5 261.5q0 139 -70 261q127 121 166 280l15 77q2 2 2 8 q0 11 -17 16l-61 19q-134 38 -204.5 142.5t-70.5 217.5q0 42 9 85l15 65q7 16 -15 25q-34 14 -97 28t-123 14q-58 0 -119 -14t-126.5 -43.5t-127.5 -85t-107 -130.5q-132 48 -253 48q-226 0 -401.5 -140t-225.5 -358q-177 -41 -291.5 -185.5t-114.5 -329.5zM861 -517 q0 -26 15 -49.5t47 -32.5q17 -3 32 -3q66 0 82 67l246 938q10 38 -7 69.5t-53 41.5q-37 11 -69.5 -6.5t-42.5 -52.5l-247 -943q-3 -14 -3 -29zM1305 -195q0 -26 15.5 -49t49.5 -33q5 0 17 -1.5t18 -1.5q60 0 76 67l160 616q10 39 -8 70t-54 41q-37 11 -68.5 -6t-41.5 -53 l-160 -621q-4 -22 -4 -29zM1438 1398q53 91 141.5 139t187.5 48q19 0 28 -1v-34q0 -154 84 -291t232 -210q-29 -65 -85 -114q-147 126 -343 126h-34q-45 200 -211 337z" />
|
||||
<glyph unicode="" horiz-adv-x="1600" d="M15 786q0 153 60 293t161 241.5t241.5 162t293.5 60.5h123q25 -7 25 -30l4 -94q6 -203 146.5 -346t340.5 -151l88 -7q25 0 25 -25v-104q1 -205 -99.5 -379.5t-274 -276t-378.5 -101.5q-156 0 -296 60t-241 161.5t-160 241.5t-59 294zM210 786q0 -162 82 -294.5 t210 -203.5t269 -71q93 0 187.5 36.5t176 102t140 167.5t75.5 223q-261 54 -418.5 223.5t-181.5 398.5q-151 -8 -276.5 -93t-194.5 -215.5t-69 -273.5z" />
|
||||
<glyph unicode="" d="M-3 93q0 -39 28 -64q29 -29 65 -29h628q42 0 73.5 -30t31.5 -73q0 -41 -32 -72.5t-73 -31.5q-40 0 -72 32q-30 28 -67 28t-63 -27t-26 -65q0 -37 27 -64q83 -83 201 -83t201 82.5t83 200.5q0 119 -83 203t-201 84h-628q-38 0 -65.5 -27t-27.5 -64zM-3 414q0 -37 28 -62 q24 -26 65 -26h1173q118 0 201 83t83 201t-83 200.5t-201 82.5q-122 0 -202 -80q-24 -27 -24 -67q0 -38 24.5 -62.5t63.5 -24.5q40 0 65 24q30 30 73 30q41 0 72 -30t31 -73t-31 -74t-72 -31h-1173q-38 0 -65.5 -27t-27.5 -64zM280 664q0 -13 17 -13h153q13 0 23 15 q36 87 111.5 144t169.5 64l56 7q21 0 21 20l7 53q17 173 146.5 289.5t304.5 116.5t304.5 -115.5t148.5 -290.5l7 -61q0 -19 19 -19h174q144 0 245.5 -101t101.5 -243q0 -143 -101.5 -244.5t-245.5 -101.5h-737q-19 0 -19 -19v-146q0 -19 19 -19h737q143 0 264.5 71t192 193 t70.5 266q0 118 -45 216q121 159 121 355q0 114 -44.5 217.5t-119.5 178t-178.5 118.5t-216.5 44q-246 0 -413 -185q-125 65 -284 65q-226 0 -399 -139t-220 -358q-136 -32 -241.5 -131t-146.5 -233v-4q-2 -5 -2 -10zM1722 1371q115 109 264 109q156 0 268 -112t112 -267 q0 -97 -54 -197q-153 153 -370 153h-36q-37 171 -184 314z" />
|
||||
<glyph unicode="" d="M-133 91q0 -40 29 -64q27 -28 65 -28h1018q37 0 62.5 27t25.5 65t-25.5 65t-62.5 27h-1018q-38 0 -66 -27.5t-28 -64.5zM149 414q0 -37 28 -62q26 -26 64 -26h1020q38 0 63 25t25 63t-25.5 64.5t-62.5 26.5h-1020q-38 0 -65 -26.5t-27 -64.5zM271 663q0 -12 20 -12h152 q14 0 24 15q36 87 112.5 144t169.5 64l58 8q18 0 18 19l8 54q17 173 147.5 290t306.5 117t306 -116t148 -291l8 -61q0 -20 18 -20h172q146 0 248.5 -101t102.5 -244q0 -142 -102.5 -244t-248.5 -102h-737q-18 0 -18 -18v-147q0 -19 18 -19h737q145 0 267.5 71t194 193 t71.5 266q0 113 -47 214q124 162 124 351q0 152 -75.5 281.5t-205 204.5t-282.5 75q-244 0 -410 -178q-136 65 -289 65q-227 0 -402 -140t-221 -359q-140 -32 -244.5 -128.5t-145.5 -231.5zM336 -239q0 -36 29 -62q26 -26 64 -26h1019q38 0 64.5 25t26.5 63t-27 65t-64 27 h-1019q-38 0 -65.5 -27.5t-27.5 -64.5zM1723 1369q108 104 263 104q158 0 269.5 -111t111.5 -268q0 -100 -53 -189q-157 153 -375 153h-34q-39 175 -182 311z" />
|
||||
<glyph unicode="" horiz-adv-x="2285" d="M3 529q0 -143 70 -264.5t191 -192.5t264 -71h1153q143 0 264.5 71t192.5 192.5t71 264.5q0 104 -44 213q121 148 121 352q0 152 -74.5 280.5t-203 203.5t-280.5 75q-239 0 -413 -186q-122 69 -286 69q-225 0 -398 -139t-222 -357q-179 -40 -292.5 -183t-113.5 -328z M185 529q0 133 89 230t224 113l73 3l7 77q22 173 150.5 289.5t300.5 116.5q115 0 214.5 -53t162.5 -146.5t74 -206.5l11 -80h190q141 0 243 -101.5t102 -241.5q0 -144 -101.5 -247t-243.5 -103h-1153q-139 0 -241 104t-102 246zM1469 1365q110 106 259 106 q158 0 268.5 -109.5t110.5 -267.5q0 -100 -54 -196q-153 153 -372 153h-33q-44 179 -179 314z" />
|
||||
<glyph unicode="" horiz-adv-x="2297" d="M-1 529q0 -213 148.5 -366t362.5 -164q19 0 19 19v142q0 19 -19 19q-137 7 -233 109t-96 241q0 133 90 231t224 114l56 8q20 0 20 19l8 54q17 173 147 289.5t305 116.5q174 0 305 -117t149 -289l6 -62q0 -19 20 -19h172q145 0 247.5 -101t102.5 -244q0 -139 -96 -241 t-233 -109q-21 0 -21 -19v-142q0 -19 21 -19q140 5 257.5 77t185 192.5t67.5 260.5q0 118 -46 214q126 153 126 354q0 151 -75.5 280t-204.5 204.5t-280 75.5q-249 0 -414 -185q-131 70 -289 70q-226 0 -401 -140.5t-225 -358.5q-176 -42 -291 -185.5t-115 -328.5zM571 -215 q14 -35 50 -49q32 -16 66.5 -2.5t47.5 47.5q14 35 1 69.5t-47 50.5q-32 13 -65.5 -1t-49.5 -46q-17 -28 -3 -69zM639 87q0 -25 16.5 -49t49.5 -33q36 -11 68.5 6t45.5 59l106 332q12 39 -8 71t-59 40q-35 11 -66.5 -6.5t-42.5 -53.5l-107 -339q0 -3 -1 -8.5t-1.5 -10 t-0.5 -8.5zM838 -511q0 -14 5 -31q14 -35 50 -48q13 -8 35 -8q10 0 32 6q35 13 50.5 48.5t-0.5 70.5t-48 49t-66 1q-31 -13 -44.5 -38.5t-13.5 -49.5zM917 -226q0 -28 16 -51t51 -30q3 0 12 -2t14 -2q69 5 86 66l187 647q11 39 -6 69.5t-53 41.5q-37 11 -69 -6.5t-43 -53.5 l-192 -647q0 -4 -1 -10.5t-1.5 -12t-0.5 -9.5zM1283 -183q0 -12 6 -34q13 -34 49 -47q15 -7 38 -7q19 0 31 6q36 14 49 46q13 35 0 69t-45 51q-35 13 -69.5 -1t-50.5 -46q-8 -17 -8 -37zM1360 88q0 -57 66 -80q22 -6 32 -6q20 0 36 9q28 14 43 59l105 332q12 37 -6.5 68.5 t-54.5 42.5q-37 11 -68 -6.5t-43 -53.5l-105 -334q-5 -27 -5 -31zM1470 1369q110 106 264 106q156 0 266.5 -111t110.5 -267q0 -99 -55 -196q-157 157 -373 157h-34q-49 185 -179 311z" />
|
||||
<glyph unicode="" horiz-adv-x="2285" d="M-6 525q0 -105 40 -200.5t108.5 -165t162.5 -112.5t199 -48q19 0 19 18v143q0 18 -19 18q-139 10 -234 110t-95 237q0 134 90.5 233t223.5 111l56 6q21 0 21 19l8 55q11 114 73.5 207t162 145.5t214.5 52.5q174 0 304 -115t149 -287l6 -62q0 -18 20 -18h171 q144 0 247 -102.5t103 -244.5q0 -137 -95 -237t-234 -110q-21 0 -21 -18v-143q0 -18 21 -18q214 10 361 161.5t147 364.5q0 102 -50 220q127 159 127 349q0 152 -75.5 281t-204 204t-279.5 75q-253 0 -412 -185q-130 68 -285 68q-225 0 -400 -140.5t-224 -359.5 q-179 -41 -292.5 -184t-113.5 -328zM810 32q-4 -22 15 -22h231l-141 -445h31l446 595q6 7 3 14.5t-14 7.5h-234l245 452q12 23 -15 23h-314q-12 0 -22 -15zM1457 1366q107 106 264 106t266.5 -110t109.5 -268q0 -95 -55 -192q-155 149 -368 149h-33q-47 190 -184 315z" />
|
||||
<glyph unicode="" horiz-adv-x="2308" d="M3 527q0 -139 68.5 -259t186.5 -192.5t259 -76.5q19 0 19 18v143q0 19 -19 19q-137 7 -234.5 109.5t-97.5 238.5q0 135 91.5 235t225.5 111l56 8q20 0 20 18l8 55q17 176 147.5 294.5t306.5 118.5q174 0 305.5 -118t149.5 -291l8 -62q0 -18 18 -18h174 q144 0 247.5 -103.5t103.5 -247.5q0 -136 -97.5 -238.5t-233.5 -109.5q-20 0 -20 -19v-143q0 -18 20 -18q141 4 258.5 76.5t185 192.5t67.5 259q0 118 -44 215q129 159 129 359q0 153 -75.5 282.5t-205 205t-281.5 75.5q-119 0 -229.5 -49.5t-190.5 -136.5q-141 69 -289 69 q-227 0 -403 -140.5t-226 -359.5q-180 -46 -294 -190t-114 -330zM573 -227q0 -27 17 -54t50 -37q37 -11 68.5 4t42.5 60l15 65q8 36 -10 67.5t-55 42.5q-36 11 -68.5 -8t-42.5 -57l-15 -63q-2 -6 -2 -20zM654 82q0 -35 26 -61q25 -27 61 -27q38 0 64 25.5t26 62.5t-26 62.5 t-64 25.5q-37 0 -62 -25t-25 -63zM719 325q-2 -25 14 -48.5t51 -34.5q33 -10 66.5 7.5t44.5 54.5l30 96q12 39 -7.5 69.5t-58.5 41.5q-35 11 -67 -7t-43 -53l-27 -98q-3 -27 -3 -28zM842 -560q0 -27 17 -52.5t52 -35.5q14 -3 26 -3q69 0 85 65l15 63q10 40 -9 72.5t-57 39.5 q-34 11 -66.5 -7.5t-43.5 -54.5l-15 -63q-4 -18 -4 -24zM926 -250q0 -36 26 -62t62 -26q38 0 63 25t25 63q0 37 -25 62t-63 25t-63 -25t-25 -62zM992 -8q-2 -24 14.5 -50t48.5 -32q37 -10 68 6t44 60l28 96q11 35 -7.5 67t-56.5 43q-35 11 -68 -8.5t-44 -56.5l-24 -96 q-3 -14 -3 -29zM1287 -237q0 -27 16 -51.5t49 -34.5q5 0 15 -2t15 -2q65 0 81 70l15 64q11 34 -7.5 67t-54.5 44q-39 10 -71.5 -8.5t-43.5 -56.5l-11 -63q0 -2 -1.5 -12t-1.5 -15zM1366 78q0 -37 26 -61q24 -26 62 -26t63 25t25 62q0 38 -25 63t-63 25t-63 -25t-25 -63z M1431 325q0 -27 17.5 -51.5t52.5 -34.5q3 0 12.5 -2t14.5 -2q17 0 39 10q33 17 43 56l27 96q10 36 -8 67.5t-54 42.5q-37 11 -68.5 -6t-42.5 -54l-30 -97q0 -3 -1.5 -11.5t-1.5 -13.5zM1480 1370q111 111 269 111q157 0 267.5 -111.5t110.5 -268.5q0 -105 -55 -197 q-154 154 -377 154h-34q-44 186 -181 312z" />
|
||||
<glyph unicode="" horiz-adv-x="2320" d="M-5 515q0 -181 107 -321t280 -192q15 -2 26 8l121 156q-143 0 -246.5 102.5t-103.5 246.5q0 138 91.5 239t228.5 112l56 8q18 0 18 15l8 60q19 176 150.5 294t307.5 118q178 0 310 -117t149 -292l8 -63q4 -18 22 -18h175q144 0 248 -105t104 -251q0 -135 -91.5 -235.5 t-224.5 -113.5q-76 0 -97 -30l-233 -256q-25 -29 -20 -66.5t34 -67.5q21 -28 62.5 -24.5t64.5 40.5l209 222q132 13 242.5 87.5t175 192t64.5 251.5q0 101 -46 222q124 158 124 354q0 156 -76 286.5t-207 206t-285 75.5q-251 0 -416 -182q-139 69 -296 69q-229 0 -405 -142 t-226 -366q-183 -41 -298 -186.5t-115 -336.5zM400 -285q0 -21 6 -34q13 -34 47 -47q36 -16 72.5 -2.5t48.5 48.5q16 35 2 71.5t-49 49.5q-36 13 -70 -0.5t-50 -47.5q-7 -15 -7 -38zM597 -24q0 -37 34 -71q68 -58 134 16l234 260q24 28 19 65.5t-34 60.5q-29 22 -66.5 17 t-62.5 -31l-236 -254q-22 -32 -22 -63zM665 -550q0 -19 8 -34q14 -36 45 -49q19 -8 39 -8q12 0 34 6q34 15 47 48q16 32 2 68t-48 49q-36 17 -69.5 3t-51.5 -51q-6 -18 -6 -32zM843 -295v-14q3 -38 35 -64q23 -29 63.5 -25.5t63.5 40.5l450 524q25 29 21 68t-37 61 q-28 23 -65 20t-62 -32l-449 -525q-20 -21 -20 -53zM1231 -384q-11 -36 0 -66q14 -35 48 -52q17 -8 38 -8q10 0 32 6q36 14 49 49q16 36 2.5 70t-47.5 47q-36 17 -70 3.5t-52 -49.5zM1487 1368q109 109 264 109q161 0 273 -112.5t112 -273.5q0 -100 -55 -194 q-156 156 -378 156h-34q-40 182 -182 315z" />
|
||||
<glyph unicode="" horiz-adv-x="2297" d="M1 525q0 -105 40 -200.5t108.5 -165t163 -112.5t199.5 -48q18 0 18 18v143q0 18 -18 18q-139 10 -234 110t-95 237q0 134 90.5 233.5t223.5 110.5l56 6q21 0 21 20l7 54q17 174 146 289.5t305 115.5q175 0 305 -114.5t148 -286.5l6 -62q0 -19 20 -19h171 q144 0 247 -102.5t103 -244.5q0 -137 -95 -237t-234 -110q-21 0 -21 -18v-143q0 -18 21 -18q214 10 362 161.5t148 364.5q0 112 -46 212q126 153 126 358q0 150 -75.5 279t-204.5 204.5t-279 75.5q-249 0 -414 -185q-141 69 -288 69q-226 0 -400.5 -140.5t-224.5 -360.5 q-176 -41 -291 -184.5t-115 -327.5zM586 -187q0 -64 69 -83q33 -11 67 6.5t44 53.5l160 611q10 35 -9.5 67t-57.5 43q-35 11 -67 -7.5t-42 -55.5l-160 -608q-4 -18 -4 -27zM863 -513q0 -19 9 -39q16 -33 54 -46q4 0 10.5 -1t12 -1.5t9.5 -0.5q69 0 81 70l246 932 q11 35 -7 67t-54 43q-39 11 -72 -7.5t-40 -55.5l-246 -933q-3 -15 -3 -28zM1306 -186q0 -25 17 -50t48 -34q11 -3 24 -3q27 0 52 14.5t34 48.5l160 611q12 33 -7 66t-55 44q-39 11 -70.5 -7.5t-38.5 -55.5l-160 -608q-4 -18 -4 -26zM1470 1362q107 107 264 107 q156 0 266 -109t110 -265q0 -100 -55 -197q-153 153 -373 153h-33q-45 187 -179 311z" />
|
||||
<glyph unicode="" horiz-adv-x="2297" d="M-1 526q0 -105 40 -200.5t108.5 -165.5t163.5 -113t200 -48q19 0 19 18v143q0 18 -19 18q-136 7 -232.5 109.5t-96.5 238.5t90 235t224 110l57 7q16 0 16 16l8 57q18 175 149 291.5t307 116.5q174 0 304.5 -115.5t149.5 -288.5l7 -61q0 -20 19 -20h173q142 0 244 -102 t102 -246q0 -136 -95 -238.5t-231 -109.5q-20 0 -20 -18v-143q0 -18 20 -18q214 10 362 162t148 365q0 100 -45 213q122 160 122 351q0 151 -75.5 280.5t-204.5 205t-280 75.5q-245 0 -412 -178q-139 69 -288 69q-227 0 -402 -141t-224 -361q-181 -42 -294.5 -183.5 t-113.5 -330.5zM574 -194q0 -27 15.5 -51.5t51.5 -36.5q18 -9 39.5 -7.5t43 21t29.5 51.5l30 108q10 40 -8.5 72t-56.5 39q-34 11 -67 -7.5t-44 -53.5l-30 -111q-3 -14 -3 -24zM714 315q1 -31 18 -55.5t51 -29.5q27 -5 32 -5q60 0 81 70l30 107q10 37 -8.5 70.5t-56.5 44.5 q-36 10 -68 -8.5t-43 -55.5l-32 -112q-4 -20 -4 -26zM846 -528q0 -28 16.5 -53t51.5 -35q22 -5 30 -5q14 0 40 11q27 13 41 59l32 108q10 40 -9 72.5t-56 39.5q-35 11 -68.5 -7.5t-44.5 -54.5l-30 -111q-3 -14 -3 -24zM987 -16q0 -27 17.5 -52.5t52.5 -35.5q36 -11 67.5 5 t43.5 60l30 108q11 40 -7.5 71t-56.5 38q-35 12 -68 -6.5t-44 -54.5l-30 -111q-5 -18 -5 -22zM1291 -202q-2 -25 14.5 -49.5t49.5 -35.5l28 -3q18 -1 40 8q33 13 44 58l31 112q11 37 -8 68.5t-55 42.5q-40 10 -71 -9t-38 -57l-31 -107q0 -3 -2 -12.5t-2 -15.5zM1435 310 q0 -28 17.5 -53t52.5 -32q1 0 11 -1.5t15 -1.5q70 0 87 66l30 110q10 40 -9 72t-57 39q-37 11 -68.5 -7.5t-41.5 -56.5l-32 -109q-5 -22 -5 -26zM1472 1366q107 103 261 103q156 0 266 -111.5t110 -267.5q0 -102 -51 -189q-154 154 -372 154h-35q-40 176 -179 311z" />
|
||||
<glyph unicode="" horiz-adv-x="2297" d="M-5 529q0 -213 149 -366.5t363 -163.5q20 0 20 18v143q0 18 -20 18q-137 8 -233.5 110t-96.5 241q0 133 91 231t225 114l56 4q20 0 20 19l8 58q17 173 147 290t305 117q174 0 304.5 -117t148.5 -290l8 -61q0 -20 19 -20h172q145 0 248 -101.5t103 -243.5 q0 -139 -96.5 -241t-233.5 -110q-21 0 -21 -18v-143q0 -18 21 -18q214 7 362.5 161.5t148.5 368.5q0 114 -46 214q126 154 126 354q0 152 -75.5 281.5t-204.5 204.5t-280 75q-248 0 -415 -185q-129 69 -289 69q-226 0 -401.5 -140.5t-225.5 -358.5q-177 -42 -292 -185.5 t-115 -328.5zM677 90q0 -35 26 -61q27 -27 62 -27q37 0 62.5 25t25.5 63t-25 63t-63 25t-63 -25t-25 -63zM677 -297q0 -38 26 -62q26 -26 62 -26q38 0 63 25t25 63t-25.5 64.5t-62.5 26.5t-62.5 -26.5t-25.5 -64.5zM1017 -112q0 -37 27 -65.5t62 -28.5q37 0 64.5 28t27.5 66 q0 35 -27.5 61.5t-64.5 26.5q-35 0 -62 -26.5t-27 -61.5zM1017 271q0 -36 26 -62t63 -26q38 0 65 25.5t27 62.5t-27 63.5t-65 26.5q-36 0 -62.5 -26.5t-26.5 -63.5zM1017 -504q0 -37 26.5 -64t62.5 -27q38 0 65 27t27 64q0 35 -27.5 61.5t-64.5 26.5q-35 0 -62 -26.5 t-27 -61.5zM1362 90q0 -32 27 -61q27 -27 64 -27t62.5 25t25.5 63t-25 63t-63 25t-64.5 -25t-26.5 -63zM1362 -297q0 -35 27 -62q26 -26 64 -26t63 25t25 63t-25.5 64.5t-62.5 26.5t-64 -27t-27 -64zM1467 1369q108 108 266 108q157 0 266.5 -111t109.5 -269q0 -94 -54 -196 q-154 154 -374 154h-33q-47 186 -181 314z" />
|
||||
<glyph unicode="" horiz-adv-x="2297" d="M-2 529q0 -213 148.5 -366.5t363.5 -163.5q18 0 18 18v146q0 20 -18 20q-140 10 -235.5 110t-95.5 236q0 135 91 234.5t225 110.5l56 7q20 0 20 20l7 53q17 173 147 290t306 117q175 0 305 -116.5t149 -290.5l6 -61q0 -19 20 -19h171q145 0 248 -101.5t103 -243.5 q0 -136 -95 -236t-234 -110q-22 0 -22 -20v-146q0 -18 22 -18q213 7 361.5 161.5t148.5 368.5q0 118 -46 214q126 153 126 358q0 151 -75.5 280.5t-204.5 205t-280 75.5q-244 0 -419 -187q-132 67 -284 67q-226 0 -401.5 -140t-224.5 -359q-177 -42 -292 -185.5t-115 -328.5 zM631 435q0 -60 45.5 -104t108.5 -44t106.5 43t43.5 105q0 41 -37.5 109t-70.5 107q-3 4 -19.5 21.5t-22.5 25.5l-38 -43q-44 -48 -80 -112.5t-36 -107.5zM950 10q0 -101 73.5 -175t175.5 -74q103 0 177 74t74 175q0 43 -25 103t-60 111q-74 95 -138 159q-9 7 -28 26 l-62 -62q-71 -68 -129 -166.5t-58 -170.5zM1107 725q0 -43 30 -71.5t76 -28.5q41 0 70 28.5t29 71.5q0 65 -99 172l-27 -28q-30 -32 -54.5 -74.5t-24.5 -69.5zM1469 1369q107 106 265 106q156 0 266 -109t110 -265q0 -101 -54 -197q-160 154 -374 154h-33q-45 188 -180 311z " />
|
||||
<glyph unicode="" horiz-adv-x="2297" d="M-1 533q0 -176 106 -317.5t274 -191.5l-68 -177q-7 -22 15 -22h226l-122 -436h31l435 586q6 7 1.5 15t-15.5 8h-232l264 498q12 23 -15 23h-315q-16 0 -21 -15l-117 -312q-114 29 -189.5 125t-75.5 216q0 134 90.5 233.5t224.5 111.5l56 7q21 0 21 18l8 55 q17 174 146.5 290.5t305.5 116.5q175 0 305 -116.5t149 -290.5l7 -62q0 -18 19 -18h173q145 0 248 -101.5t103 -243.5q0 -137 -96 -237.5t-235 -110.5q-20 0 -20 -19v-147q0 -18 20 -18q213 7 361.5 162t148.5 370q0 114 -45 213q123 161 123 351q0 114 -45 217.5 t-120 178.5t-179 119.5t-217 44.5q-118 0 -226 -48t-186 -134q-131 70 -288 70q-227 0 -402 -139.5t-225 -359.5q-177 -41 -292 -185t-115 -328zM871 -520q0 -28 17 -54.5t49 -32.5q1 0 11 -1.5t15 -1.5q69 5 86 65l31 113q10 35 -9.5 67.5t-56.5 43.5q-35 10 -67.5 -9 t-43.5 -57l-28 -107q-4 -20 -4 -26zM1014 -11q0 -24 16.5 -48t49.5 -36q35 -12 67 5.5t43 56.5l32 111q10 40 -8.5 72t-56.5 39q-36 11 -69 -7.5t-45 -56.5l-26 -109q0 -3 -1 -8.5t-1.5 -10t-0.5 -8.5zM1315 -184q0 -30 17.5 -55.5t50.5 -34.5q25 -5 28 -5q64 0 83 64 l26 113q11 35 -6 67t-53 44q-40 10 -73.5 -9.5t-40.5 -56.5l-29 -106q-3 -12 -3 -21zM1461 322q0 -26 17 -50.5t52 -36.5q27 -3 28 -3q19 0 40 9q31 16 42 56l31 111q10 35 -9.5 67.5t-56.5 43.5q-36 11 -68 -7.5t-43 -56.5l-30 -109q-3 -14 -3 -24zM1472 1373 q106 102 261 102q157 0 267 -110.5t110 -267.5q0 -101 -52 -190q-158 154 -372 154h-35q-49 186 -179 312z" />
|
||||
<glyph unicode="" horiz-adv-x="2297" d="M2 529q0 -178 106 -318t274 -187l-69 -179q-8 -23 15 -23h226l-111 -447h30l423 600q6 7 1.5 15t-15.5 8h-232l264 492q11 23 -15 23h-314q-14 0 -24 -15l-114 -306q-114 28 -188.5 122t-74.5 215q0 133 89.5 230.5t223.5 113.5l57 4q20 0 20 19l8 57q17 173 146 289.5 t304 116.5q173 0 303 -117t148 -289l8 -60q0 -20 19 -20h172q142 0 245.5 -102t103.5 -242q0 -138 -96 -239.5t-232 -108.5q-21 0 -21 -19v-143q0 -18 21 -18q139 4 256 76.5t184 192.5t67 259q0 119 -44 214q124 146 124 353q0 150 -75 279t-203.5 204t-278.5 75 q-248 0 -413 -185q-129 69 -288 69q-225 0 -400 -139.5t-224 -357.5q-176 -41 -291 -184.5t-115 -327.5zM848 -525q0 -26 15.5 -49t48.5 -33q3 0 8.5 -1t10 -1.5t8.5 -0.5q67 0 85 64l259 948q10 37 -7 68.5t-52 42.5q-37 11 -69.5 -6.5t-42.5 -53.5l-261 -949 q-3 -27 -3 -29zM1290 -201q0 -26 16 -50t51 -35q3 0 8.5 -1t10 -1.5t8.5 -0.5q19 0 38 8q31 13 44 59l175 624q10 37 -8 68.5t-54 42.5q-37 11 -68.5 -6.5t-41.5 -53.5l-174 -628q-5 -23 -5 -26zM1469 1367q108 102 264 102q155 0 264.5 -109t109.5 -264q0 -103 -53 -197 q-154 154 -372 154h-34q-45 184 -179 314z" />
|
||||
<glyph unicode="" horiz-adv-x="1131" d="M1 1160q0 80 57 137t137 57q79 0 135.5 -57t56.5 -137t-56.5 -137t-135.5 -57t-136.5 57t-57.5 137zM95 1160q0 -41 30 -71t70 -30q41 0 71 30t30 71t-30 71t-71 30q-42 0 -71 -29.5t-29 -71.5zM510 688q0 -123 68 -219q35 -50 98.5 -79.5t144.5 -29.5q233 0 292 178 q6 23 -6.5 43.5t-35.5 24.5q-23 6 -43 -7t-25 -36q0 -1 -1.5 -5.5l-2.5 -7.5q-18 -30 -48 -48q-50 -30 -130 -30q-51 0 -90 17q-64 27 -91 95q-18 44 -18 104v344q0 24 3 48q6 61 48 111q47 56 148 56q82 0 130 -29q32 -19 48 -48q1 -3 2.5 -8.5t1.5 -6.5q6 -22 25 -32.5 t43 -5.5q23 5 35.5 24.5t6.5 42.5v1l-9 25q-8 18 -29.5 46t-48.5 47q-77 56 -205 56q-81 0 -144 -28.5t-98 -77.5q-69 -95 -69 -221v-344z" />
|
||||
<glyph unicode="" horiz-adv-x="2217" d="M3 531q0 -214 149 -367t363 -163q19 0 19 18v143q0 19 -19 19q-137 7 -233.5 109t-96.5 241q0 133 91 231.5t225 114.5l56 3q20 0 20 18l8 59q16 173 146 289.5t306 116.5q174 0 304.5 -116.5t148.5 -289.5l8 -62q0 -18 19 -18h172q142 0 246.5 -103t104.5 -243 q0 -139 -96.5 -241t-233.5 -109q-21 0 -21 -19v-143q0 -18 21 -18q213 7 361 161t148 369q0 144 -71 265t-193 190.5t-266 69.5h-34q-52 214 -224.5 351t-393.5 137q-226 0 -401.5 -140t-225.5 -359q-176 -41 -291.5 -185.5t-115.5 -328.5zM746 368q0 -37 27 -64l275 -278 q20 -25 62 -25q45 0 65 25l276 278q27 29 27 64q0 37 -26.5 62t-64.5 25t-66 -25l-120 -118v425q0 38 -26.5 63t-64.5 25t-63 -25t-25 -63v-425l-118 118q-28 25 -66 25q-40 0 -66 -24.5t-26 -62.5z" />
|
||||
<glyph unicode="" horiz-adv-x="2217" d="M6 527q0 -105 40 -200.5t108.5 -165t163 -112.5t199.5 -48q18 0 18 18v142q0 20 -18 20q-139 10 -234.5 110t-95.5 236q0 134 91 233.5t224 110.5l56 8q20 0 20 18l8 55q11 114 74 207.5t162.5 146t214.5 52.5q175 0 305 -115t148 -288l7 -61q0 -19 20 -19h172 q144 0 247 -103t103 -245q0 -136 -95 -236t-234 -110q-21 0 -21 -20v-142q0 -18 21 -18q214 10 361 161.5t147 364.5q0 143 -71 264.5t-193 192.5t-265 71h-34q-53 213 -225.5 350t-392.5 137q-225 0 -400.5 -141.5t-225.5 -360.5q-176 -41 -290.5 -185t-114.5 -328z M670 392q0 -119 59.5 -220.5t161 -161.5t219.5 -60q184 0 314.5 130.5t130.5 311.5q0 38 -26.5 64.5t-64.5 26.5t-66 -27t-28 -64q0 -108 -76 -184.5t-184 -76.5q-107 0 -183.5 77t-76.5 184q0 96 57.5 171t140.5 82l-45 -42q-24 -24 -24 -61q0 -40 24 -70q22 -24 57 -25 t75 25l194 199q26 26 26 62q0 41 -26 65l-194 195q-30 28 -66 28q-37 0 -63.5 -27t-26.5 -65t24 -62l38 -38q-160 -27 -265.5 -149t-105.5 -288z" />
|
||||
<glyph unicode="" horiz-adv-x="2217" d="M0 530q0 -213 148.5 -366t362.5 -163q18 0 18 18v142q0 20 -18 20q-137 7 -233 108.5t-96 240.5q0 132 90 230t224 114h56q21 0 21 19l6 61q17 173 147 290t305 117q173 0 303.5 -117t148.5 -290l8 -61q0 -19 19 -19h172q142 0 246 -102.5t104 -241.5q0 -138 -96.5 -240 t-232.5 -109q-21 0 -21 -20v-142q0 -18 21 -18q139 4 256 76t184.5 192.5t67.5 260.5q0 107 -42 204t-113 167t-169 111.5t-205 41.5h-34q-53 214 -225 350.5t-392 136.5q-226 0 -401 -140.5t-224 -358.5q-177 -41 -291.5 -184.5t-114.5 -327.5zM741 463q0 -40 26 -68 q24 -24 65 -24q39 0 66 24l118 122v-424q0 -38 25 -65t63 -27q37 0 64 27t27 65v420l120 -118q64 -49 130 0q26 24 26 66q0 38 -26 66l-276 274q-26 26 -64 26q-39 0 -63 -26l-275 -274q-26 -26 -26 -64z" />
|
||||
<glyph unicode="" horiz-adv-x="2217" d="M3 531q0 -214 149 -367t363 -163q19 0 19 18v143q0 19 -19 19q-137 7 -233.5 109t-96.5 241q0 133 91 231.5t225 114.5h56q20 0 20 18l8 62q16 173 146 289.5t306 116.5q174 0 304.5 -116.5t148.5 -289.5l8 -62q0 -18 19 -18h172q142 0 246.5 -103t104.5 -243 q0 -139 -96.5 -241t-233.5 -109q-21 0 -21 -19v-143q0 -18 21 -18q213 7 361 161t148 369q0 144 -71 265t-193 190.5t-266 69.5h-34q-52 214 -224.5 351t-393.5 137q-226 0 -401.5 -140t-225.5 -359q-176 -41 -291.5 -185.5t-115.5 -328.5z" />
|
||||
<glyph unicode="" horiz-adv-x="377" d="M-3 1349q0 80 57 137t137 57q79 0 135.5 -57t56.5 -137t-56.5 -137.5t-135.5 -57.5t-136.5 57.5t-57.5 137.5zM90 1349q0 -41 30 -71t71 -30t70.5 30t29.5 71t-29.5 70.5t-70.5 29.5q-42 0 -71.5 -29t-29.5 -71z" />
|
||||
<glyph unicode="" horiz-adv-x="685" d="M6 542q0 -31 20.5 -51.5t50.5 -20.5h306q30 0 50.5 20.5t20.5 51.5q0 30 -20.5 49.5t-50.5 19.5h-133l415 414q18 21 18 53q0 30 -18 49q-20 19 -54 19q-31 0 -50 -19l-412 -413v133q0 31 -20.5 51.5t-51.5 20.5q-30 0 -50.5 -21t-20.5 -51v-305z" />
|
||||
<glyph unicode="" horiz-adv-x="685" d="M-6 574q0 -34 28 -59l256 -261q25 -25 61 -25q38 0 63 25l256 261q27 24 27 59q0 36 -24.5 60.5t-60.5 24.5t-61 -25l-113 -112v703q0 36 -25.5 60t-61.5 24t-61 -24t-25 -60v-703l-114 112q-25 25 -59 25q-36 0 -61 -24.5t-25 -60.5z" />
|
||||
<glyph unicode="" horiz-adv-x="1131" d="M3 1157q0 79 58 137q57 57 137 57q79 0 135.5 -57t56.5 -137t-56.5 -137.5t-135.5 -57.5q-80 0 -137.5 58t-57.5 137zM97 1157q0 -41 30 -71t71 -30t70.5 30t29.5 71t-29.5 70.5t-70.5 29.5q-42 0 -71.5 -29t-29.5 -71zM568 422q0 -23 16.5 -39.5t39.5 -16.5t39.5 16.5 t16.5 39.5v404h305q23 0 39.5 17t16.5 40q0 24 -16 40t-40 16h-305v291h408q23 0 38.5 16.5t15.5 39.5t-15.5 39.5t-38.5 16.5h-509q-11 0 -11 -12v-908z" />
|
||||
<glyph unicode="" horiz-adv-x="2285" d="M-13 635q0 42 29 66q23 24 66 24h222q39 0 64.5 -25.5t25.5 -64.5q0 -38 -26 -65t-64 -27h-222q-39 0 -67 27t-28 65zM87 93q0 38 27 65q26 25 65 25h1942q39 0 66.5 -26t27.5 -64t-28 -65.5t-66 -27.5h-1942q-38 0 -65 27.5t-27 65.5zM304 1391q0 41 25 63q27 29 66 29 t66 -29l154 -156q27 -26 27 -64q0 -40 -26 -65.5t-64 -25.5q-35 0 -64 26l-159 157q-25 22 -25 65zM582 635q0 -139 57 -249q5 -15 25 -15h178q12 0 14.5 6.5t-7.5 17.5q-84 103 -84 240q0 159 114 271t272 112q159 0 271.5 -112t112.5 -271q0 -136 -86 -240q-6 -10 -6 -13 q-1 -5 3 -8t11 -3h182q14 0 22 15q59 111 59 249q0 153 -76.5 284.5t-208 208.5t-284.5 77t-284.5 -77t-208 -208.5t-76.5 -284.5zM1058 1481v223q0 38 27.5 66t65.5 28t66 -28t28 -66v-223q0 -38 -28 -66t-66 -28t-65.5 28t-27.5 66zM1658 1234q0 38 27 64l154 156 q27 29 65 29q40 0 67 -26.5t27 -65.5q0 -41 -24 -65l-161 -157q-26 -24 -63 -24q-40 -1 -66 24t-26 65zM1906 635q0 42 27 66q23 24 62 24h223q39 0 66.5 -25.5t27.5 -64.5q0 -38 -28 -65t-66 -27h-223q-37 0 -63 27t-26 65z" />
|
||||
<glyph unicode="" horiz-adv-x="2285" d="M73 94q0 42 30 66q22 25 67 25h1956q40 0 68 -26t28 -65q0 -38 -28 -66.5t-68 -28.5h-1956q-40 0 -68.5 28.5t-28.5 66.5zM283 978q0 41 26 67q27 28 68 28q37 0 64 -28l160 -160q29 -29 29 -67q0 -37 -29 -66q-24 -29 -64 -28.5t-68 28.5l-160 160q-26 25 -26 66z M597 382q-7 -25 16 -25h158q11 0 24 18q48 102 142 162t209 60q116 0 212 -60t144 -162q13 -18 24 -18h159q20 0 16 25q-54 180 -208 293.5t-347 113.5q-192 0 -343.5 -113t-205.5 -294zM1055 1071v228q0 40 26 68t65 28q40 0 67 -27.5t27 -68.5v-228q0 -41 -27 -68t-67 -27 q-39 0 -65 27.5t-26 67.5zM1665 818q0 37 28 67l163 160q25 28 66 28q39 0 64 -27t25 -68q0 -42 -25 -66l-157 -160q-27 -27 -61 -30q-36 -4 -69.5 26t-33.5 70z" />
|
||||
<glyph unicode="" horiz-adv-x="1085" d="M6 734q0 -38 25 -63l262 -256q23 -26 58 -26q36 0 61 24t25 60q0 35 -26 61l-112 112h703q36 0 60 25.5t24 61.5t-24 61.5t-60 26.5h-703l112 113q26 26 26 59q0 36 -25 61t-61 25q-33 0 -58 -28l-262 -256q-25 -25 -25 -61z" />
|
||||
<glyph unicode="" horiz-adv-x="2285" d="M-288 99q0 -36 27 -61t66 -25h1985q38 0 64.5 25t26.5 61q0 39 -26 65.5t-65 26.5h-1985q-38 0 -65.5 -27t-27.5 -65zM-10 431q0 -38 28 -63q22 -29 63 -29h1985q37 0 62 27t25 65q0 37 -25 62t-62 25h-1985q-38 0 -64.5 -25t-26.5 -62zM6 689v5q-7 -24 13 -24h154 q9 0 20 16q38 83 113.5 136t165.5 60l57 8q20 0 20 19l6 53q17 172 145.5 287.5t302.5 115.5q172 0 301 -114t147 -285l7 -61q0 -19 22 -19h169q103 0 187 -55t125 -145q11 -16 23 -16h151q19 0 16 24q-35 89 -49 113q116 104 160 274l18 70q4 10 -1.5 17t-12.5 7l-66 23 q-143 41 -217.5 169t-39.5 273l14 62q8 15 -14 25l-90 25q-175 43 -343 -28t-263 -226q-127 49 -244 49q-222 0 -395.5 -140t-223.5 -357q-135 -32 -236.5 -129t-141.5 -232zM175 -226q0 -38 28 -63q27 -29 63 -29h1989q37 0 63 27t26 65q0 37 -26 62t-63 25h-1989 q-38 0 -64.5 -25t-26.5 -62zM1402 1403q56 92 150.5 142t199.5 40q-11 -167 75 -312.5t237 -215.5q-24 -56 -80 -114q-147 121 -335 121h-34q-51 210 -213 339z" />
|
||||
<glyph unicode="" horiz-adv-x="685" d="M-2 741q0 -192 134 -326.5t326 -134.5q30 0 51.5 23t21.5 54t-21 51.5t-52 20.5q-130 0 -220.5 91t-90.5 221q0 124 85.5 216t200.5 92l-35 -34q-24 -23 -22 -52q0 -29 21 -51.5t52 -22.5q32 0 54 22l161 160q21 17 21 54q0 35 -21 50l-161 163q-21 22 -53 22 q-31 0 -53 -22t-22 -53q0 -33 23 -54l32 -30q-184 -13 -308 -144.5t-124 -315.5z" />
|
||||
<glyph unicode="" horiz-adv-x="1108" d="M-7 674q0 -151 75.5 -279.5t204 -203t278.5 -74.5q153 0 281.5 74t202.5 202t74 281q0 38 -25.5 65t-62.5 27t-64.5 -27t-27.5 -65q0 -157 -110.5 -267.5t-267.5 -110.5q-155 0 -264 110.5t-109 267.5q0 136 84.5 246.5t196.5 117.5l-41 -39q-26 -28 -26 -62 q0 -36 26 -64q58 -56 129 0l196 194q26 20 26 66q0 41 -26 61l-196 198q-28 26 -61 26q-40 0 -67 -26.5t-27 -64.5q0 -39 26 -65l41 -38q-200 -35 -333 -191.5t-133 -358.5z" />
|
||||
<glyph unicode="" horiz-adv-x="1085" d="M0 799q0 -36 24 -61t59 -21h703l-111 -115q-25 -25 -25 -59q0 -36 25.5 -61t61.5 -25q34 1 57 29l261 257q25 25 25 62q-2 37 -26 62l-263 255q-24 26 -58 25q-36 0 -61 -24t-25 -60t25 -61l113 -115h-703q-36 -1 -59.5 -26.5t-22.5 -61.5z" />
|
||||
<glyph unicode="" horiz-adv-x="857" d="M2 1027q0 41 40.5 108t77.5 111q10 12 25 29.5t17 19.5l39 -46q44 -49 81 -114t37 -108q0 -66 -45.5 -113t-111.5 -47t-113 47t-47 113zM335 584q0 46 26 107.5t64 113.5q31 41 75 91.5t66 71.5q4 2 27 26l28 -26q59 -55 140 -162q41 -55 67 -115t26 -107 q0 -109 -75.5 -184t-185.5 -75q-108 0 -183 76t-75 183zM497 1320q0 68 108 181l26 -30q30 -35 54.5 -78.5t24.5 -72.5q0 -44 -30.5 -73.5t-74.5 -29.5q-46 0 -77 29.5t-31 73.5z" />
|
||||
<glyph unicode="" d="M6 521q0 -40 29 -66q26 -30 68 -30h1567q46 0 77.5 -32t31.5 -79q0 -46 -31 -76.5t-78 -30.5t-78 31q-26 28 -64 28q-40 0 -68.5 -27.5t-28.5 -65.5q0 -40 30 -67q90 -88 209 -88q125 0 214 86t89 210t-89.5 213.5t-213.5 89.5h-1567q-40 0 -68.5 -28t-28.5 -68zM6 871 q0 -38 29 -65q28 -28 68 -28h2138q125 0 214.5 87t89.5 210q0 124 -89.5 212t-214.5 88q-123 0 -208 -85q-30 -27 -30 -71q0 -41 27.5 -67t67.5 -26t68 26q30 33 75 33q46 0 77.5 -32t31.5 -78t-31.5 -77t-77.5 -31h-2138q-40 0 -68.5 -28t-28.5 -68z" />
|
||||
<glyph unicode="" d="M-40 693q0 40 32 74q36 31 75 31h248q43 0 72 -31t29 -74q0 -46 -29 -77t-72 -31h-248q-43 0 -75 32.5t-32 75.5zM315 1544q0 43 28 73q36 31 75 31q42 0 73 -31l175 -176q30 -35 30 -74q0 -45 -29 -75t-70 -30q-39 0 -75 31l-179 173q-28 34 -28 78zM528 104q0 45 31 73 q28 28 73 28h281l332 312q16 13 36 0l337 -312h295q43 0 73.5 -29.5t30.5 -71.5q0 -43 -30.5 -74t-73.5 -31h-361q-16 0 -31 8l-257 242l-255 -242q-13 -8 -30 -8h-347q-43 0 -73.5 31t-30.5 74zM627 693q0 -155 66 -282q4 -19 27 -19h201q12 0 16 9t-2 20 q-102 123 -102 272q0 180 128 306t308 126q179 0 305.5 -126t126.5 -306q0 -150 -101 -272q-7 -11 -3.5 -20t16.5 -9h203q21 0 25 19q66 123 66 282q0 130 -51 248t-136.5 203.5t-203.5 136t-247 50.5q-130 0 -248.5 -50.5t-205 -136t-137.5 -203.5t-51 -248zM1163 1648v245 q0 46 30 75.5t76 29.5q45 0 74.5 -30t29.5 -75v-245q0 -46 -29.5 -75.5t-74.5 -29.5q-46 0 -76 29.5t-30 75.5zM1840 1367q0 40 29 74l173 176q31 31 75 31t74 -30.5t30 -73.5q0 -46 -28 -78l-180 -173q-34 -31 -75 -31q-43 0 -70.5 29.5t-27.5 75.5zM2119 693q0 43 28.5 74 t70.5 31h251q43 0 74 -31t31 -74q0 -44 -31 -76t-74 -32h-251q-43 0 -71 31t-28 77z" />
|
||||
<glyph unicode="" d="M-22 681q0 44 32 72q28 28 73 28h246q43 0 71.5 -28.5t28.5 -71.5q0 -44 -28.5 -75t-71.5 -31h-246q-43 0 -74 31.5t-31 74.5zM329 1519q0 44 28 72q32 32 74 32q46 0 72 -32l174 -174q74 -70 0 -144q-30 -30 -70 -30q-35 0 -72 30l-178 174q-28 30 -28 72zM539 103 q0 42 32 74q29 29 72 29h343q17 0 30 -8l251 -237l255 237q11 8 31 8h352q43 0 73.5 -30t30.5 -73t-30.5 -73.5t-73.5 -30.5h-286l-334 -303q-20 -14 -36 0l-329 303h-277q-43 0 -73.5 30.5t-30.5 73.5zM639 681q0 -157 63 -280q8 -17 28 -17h199q13 0 16 8t-8 19 q-95 119 -95 270q0 176 126 299.5t304 123.5q176 0 301.5 -124t125.5 -299q0 -152 -94 -270q-11 -11 -8 -19t16 -8h201q20 0 25 17q66 125 66 280q0 127 -50.5 244.5t-135.5 202.5t-202 135.5t-245 50.5t-245 -50.5t-202 -135.5t-135.5 -202.5t-50.5 -244.5zM1168 1621v248 q0 43 30.5 73.5t73.5 30.5t73.5 -30.5t30.5 -73.5v-248q0 -43 -30.5 -73.5t-73.5 -30.5t-73.5 30.5t-30.5 73.5zM1838 1345q0 46 27 72l174 174q26 32 72 32q44 0 73.5 -30.5t29.5 -73.5q0 -44 -28 -72l-177 -174q-37 -30 -74 -30q-42 0 -69.5 29.5t-27.5 72.5zM2113 681 q0 43 28.5 71.5t71.5 28.5h246q43 0 73.5 -29t30.5 -71q0 -43 -30.5 -74.5t-73.5 -31.5h-246q-43 0 -71.5 31t-28.5 75z" />
|
||||
<glyph unicode="" horiz-adv-x="685" d="M-1 393q0 86 39.5 160.5t111.5 124.5v668q0 81 55.5 137t136.5 56t137 -56t56 -137v-668q71 -50 110.5 -124.5t39.5 -160.5q0 -143 -100.5 -243t-242.5 -100t-242.5 100t-100.5 243zM117 393q0 -94 66 -160.5t159 -66.5q94 0 161.5 67.5t67.5 159.5q0 63 -32.5 116.5 t-88.5 83.5l-19 9q-10 4 -10 20v724q0 32 -22.5 54t-56.5 22q-33 0 -56 -22t-23 -54v-724q0 -16 -9 -20l-20 -9q-55 -30 -86 -83t-31 -117z" />
|
||||
<glyph unicode="" horiz-adv-x="685" d="M153 401q0 -81 56 -137.5t134 -56.5q79 0 136 56.5t57 137.5q0 71 -49 126t-120 66v558q0 7 -7 14.5t-17 7.5q-22 0 -22 -22v-558q-70 -11 -119 -66t-49 -126z" />
|
||||
<glyph unicode="" horiz-adv-x="685" d="M-10 377q0 87 40.5 163t113.5 126v681q0 82 56 139.5t138 57.5q83 0 140 -57.5t57 -139.5v-681q72 -51 112.5 -126.5t40.5 -162.5q0 -145 -102.5 -247.5t-247.5 -102.5t-246.5 102.5t-101.5 247.5zM110 377q0 -96 67 -164t161 -68q97 0 166 68.5t69 163.5q0 64 -33 118 t-91 84l-19 11q-11 5 -11 19v738q0 32 -23.5 54.5t-57.5 22.5q-32 0 -55.5 -22.5t-23.5 -54.5v-738q0 -15 -10 -19l-19 -11q-57 -30 -88.5 -83.5t-31.5 -118.5zM170 381q0 -72 49 -122.5t119 -50.5t121 50.5t51 122.5q0 64 -44 113t-106 57v495q0 6 -7 12.5t-15 6.5 q-19 0 -19 -19v-495q-62 -8 -105.5 -57t-43.5 -113z" />
|
||||
<glyph unicode="" horiz-adv-x="1485" d="M5 718q0 -71 74 -121q135 -95 408 -95q122 0 227 21q111 24 181.5 75.5t70.5 119.5q0 21 -8 42q142 35 223 94.5t81 136.5q0 19 -6 41q233 83 233 224q0 90 -102 160q-200 133 -586 133q-181 0 -329 -32q-159 -33 -258 -102.5t-99 -158.5q0 -51 34 -97 q-122 -69 -122 -168q0 -76 76 -135q-98 -57 -98 -138zM46 411q0 -75 92 -117.5t232 -42.5q142 0 235 42.5t93 117.5q0 28 -18 47t-46 19q-23 0 -41 -16t-23 -39q-20 -16 -75 -29.5t-125 -13.5q-113 0 -183 32q16 16 17.5 41t-10.5 42q-16 22 -41.5 27t-47.5 -9 q-59 -40 -59 -101zM133 718q1 2 12 12q9 9 38.5 23t65.5 24l8 4q166 -55 388 -55q97 0 176 11l18 -16q-9 -16 -44 -34q-41 -22 -126.5 -39t-181.5 -17t-182.5 17t-128.5 39q-34 15 -43 31zM141 143q0 -65 73.5 -100t182.5 -35q111 0 185.5 35t74.5 100q0 26 -19 46t-45 20 q-48 0 -63 -49q-40 -25 -133 -25q-83 0 -132 25q-15 49 -63 49q-26 0 -43.5 -19t-17.5 -47zM154 991q0 6 11 19q25 31 97 60q196 -105 539 -105q175 0 329 32v-6q0 -16 -18 -31q-42 -37 -157 -70q-124 -37 -310 -37q-188 0 -312 37q-119 33 -159 70q-20 16 -20 31zM243 1256 q0 18 24 40q46 43 183 83q146 43 351 43q206 0 354 -43q138 -40 183 -83q23 -23 23 -40t-23 -38q-45 -43 -183 -84q-148 -43 -354 -43q-205 0 -351 43q-138 40 -183 84q-24 21 -24 38z" />
|
||||
<glyph unicode="" horiz-adv-x="685" d="M5 557q0 -31 18 -50q20 -19 54 -19q31 0 50 19l411 413v-133q0 -31 20.5 -51.5t51.5 -20.5q30 0 50.5 21t20.5 51v306q0 31 -20.5 51.5t-50.5 20.5h-306q-30 0 -50.5 -20.5t-20.5 -51.5q0 -30 20.5 -50t50.5 -20h133l-414 -414q-18 -21 -18 -52z" />
|
||||
<glyph unicode="" horiz-adv-x="685" d="M-2 1013q-1 -36 23 -61.5t60 -24.5q36 -2 61 25l113 112v-703q-1 -36 24.5 -60t62.5 -23q35 -2 61 22t27 60v703l112 -112q24 -25 59 -25q37 0 62 24.5t25 60.5q-2 37 -27 58l-255 262q-25 24 -63 27q-38 -2 -63 -28l-256 -260q-26 -22 -26 -57z" />
|
||||
<glyph unicode="" horiz-adv-x="1542" d="M10 769q0 156 60.5 298.5t163 244.5t244 163t297.5 61q157 0 299 -61t245 -163.5t164 -244.5t61 -297q0 -156 -61 -298.5t-164 -245.5t-245 -163.5t-298 -60.5q-155 0 -297 60.5t-244.5 163t-163.5 244.5t-61 299zM178 769q0 -162 80.5 -299.5t218.5 -217t299 -79.5 q121 0 232 47.5t191.5 127.5t128 190.5t47.5 231.5q0 245 -177 422q-178 176 -423 176q-121 0 -231.5 -48t-190.5 -128t-127.5 -191t-47.5 -232zM502 521l89 239q5 10 0 21l-89 235q-5 10 0.5 16t16.5 1l599 -252q10 -1 10 -11q0 -9 -10 -10l-599 -257q-11 -4 -16.5 1.5 t-0.5 16.5z" />
|
||||
<glyph unicode="" horiz-adv-x="1542" d="M26 968q22 82 64 161t108.5 154t149 131.5t193 90.5t233.5 34q319 0 544 -226q148 -148 201.5 -346t0.5 -397q-22 -82 -63.5 -161t-107.5 -154t-148 -131.5t-193 -91t-234 -34.5q-320 0 -545 226q-148 147 -202 346t-1 398zM173 769q0 -249 176 -425t425 -176 q122 0 233 47.5t191.5 128t128 191.5t47.5 234q0 163 -80.5 301t-218.5 218.5t-301 80.5q-123 0 -234 -47.5t-191.5 -128t-128 -191.5t-47.5 -233zM392 761q0 7 11 10l605 250q11 2 16 -3.5t1 -16.5l-248 -604q-2 -11 -10 -11q-11 0 -16 11l-103 230q-9 14 -15 15l-230 104 q-11 4 -11 15z" />
|
||||
<glyph unicode="" horiz-adv-x="1542" d="M26 568.5q-53 198.5 0 396.5q22 82 63.5 160.5t107.5 153t148 131t192.5 90.5t233.5 34q317 0 542 -225q148 -147 201 -345t0 -396q-22 -82 -63 -160.5t-107 -153.5t-148 -131.5t-192 -90.5t-233 -34q-319 0 -544 225q-148 147 -201 345.5zM171 767q0 -249 176 -424 q175 -176 424 -176q162 0 299.5 80.5t218 218.5t80.5 301q0 162 -80.5 299.5t-218 218t-299.5 80.5q-163 0 -301 -80.5t-218.5 -218t-80.5 -299.5zM519 998q-2 11 3.5 17t15.5 2l603 -249q11 -2 11 -10q0 -9 -11 -14l-230 -104q-11 -4 -15 -15l-104 -230q-5 -11 -14 -11 q-8 0 -10 11z" />
|
||||
<glyph unicode="" horiz-adv-x="1542" d="M2 765q0 209 103.5 386.5t280.5 280.5t385 103q156 0 298 -61t245 -164t164 -245.5t61 -299.5q0 -156 -61 -298.5t-164 -245t-245 -163t-298 -60.5q-157 0 -299.5 61t-245.5 164t-163.5 244.5t-60.5 297.5zM171 765q0 -242 178 -422q177 -177 422 -177q162 0 300 80.5 t219 218.5t81 300t-81 300.5t-219 219t-300 80.5t-300 -80.5t-219 -219t-81 -300.5zM505 507l256 600q2 11 10 11q9 0 11 -11l255 -600q4 -11 -2 -17t-17 0l-236 89q-10 5 -21 0l-239 -89q-10 -6 -16 0t-1 17z" />
|
||||
<glyph unicode="" horiz-adv-x="1542" d="M8.5 572q-53.5 200 0.5 397q22 82 63.5 161t108 154.5t149.5 132t194 91t234 34.5q318 0 544 -228q148 -148 201.5 -346t0.5 -397q-22 -82 -63.5 -161t-108 -154.5t-148.5 -132t-192.5 -91t-233.5 -34.5q-322 0 -547 226q-149 148 -202.5 348zM155 770q0 -250 176 -426 t427 -176q162 0 300.5 80.5t219 219t80.5 302.5q0 122 -47.5 233t-128 191.5t-191.5 128t-233 47.5q-164 0 -303 -80.5t-219.5 -219t-80.5 -300.5zM375 777q0 10 10 14l231 104q10 5 15 15l103 229q9 12 16 12q8 0 11 -12l248 -605q2 -11 -2 -15t-16 -2l-606 248 q-10 3 -10 12z" />
|
||||
<glyph unicode="" horiz-adv-x="1542" d="M30.5 573q-53.5 200 0.5 397q22 82 63.5 161t107.5 154t148.5 131.5t193.5 91t234 34.5q318 0 543 -228q149 -147 202.5 -345.5t-0.5 -395.5q-22 -82 -63 -161.5t-107.5 -154.5t-148.5 -132t-192.5 -91.5t-233.5 -34.5q-322 0 -545 227q-149 147 -202.5 347zM176 771 q0 -249 176 -426q175 -175 426 -175q162 0 300 80t218.5 218.5t80.5 302.5q0 162 -80.5 300.5t-218.5 219t-300 80.5q-164 0 -302.5 -80.5t-219 -219t-80.5 -300.5zM525 536l250 605q1 10 10 10q10 0 14 -10l104 -231q2 -9 16 -14l230 -104q11 -4 11 -14q0 -9 -11 -12 l-605 -248q-10 -4 -15 1t-4 17z" />
|
||||
<glyph unicode="" horiz-adv-x="1542" d="M6 766q0 156 61.5 298.5t164.5 245.5t245 163.5t298 60.5t298.5 -60.5t245.5 -163.5t164 -245t61 -299t-61 -299t-164 -244.5t-245.5 -163t-298.5 -60.5t-298.5 61t-245.5 164t-164 245t-61 297zM176 766q0 -245 177 -422q176 -176 422 -176q163 0 301.5 80.5t219 218 t80.5 299.5q0 121 -47.5 232t-128.5 191.5t-192 128t-233 47.5q-121 0 -231.5 -47.5t-191 -128t-128.5 -191.5t-48 -232zM510 1025q-5 11 1 16.5t16 0.5l238 -89q10 -4 22 0l236 89q10 5 16 -0.5t2 -16.5l-254 -599q-3 -10 -12 -10q-7 0 -10 10z" />
|
||||
<glyph unicode="" horiz-adv-x="1542" d="M6 769q0 157 61.5 300t165 246.5t246 164.5t298.5 61t298.5 -61t246 -164.5t164.5 -246.5t61 -300t-61 -300t-164.5 -245.5t-246 -163.5t-298.5 -61q-157 0 -300 61t-246 164.5t-164 246t-61 298.5zM176 769q0 -246 178 -424q177 -177 423 -177q163 0 301.5 81 t219.5 219.5t81 300.5t-81 300.5t-219.5 219.5t-301.5 81q-162 0 -300.5 -81t-219.5 -219.5t-81 -300.5zM424 769q0 9 11 10l602 258q11 5 16.5 -1.5t0.5 -16.5l-89 -240q-7 -10 0 -20l89 -238q5 -10 -0.5 -16t-16.5 -1l-602 255q-11 1 -11 10z" />
|
||||
</font>
|
||||
</defs></svg>
|
Before Width: | Height: | Size: 94 KiB |
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"
|
||||
}
|
||||
}
|
95
fonts/roboto.css
Normal file
95
fonts/roboto.css
Normal file
@@ -0,0 +1,95 @@
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src:
|
||||
local("Roboto Thin"),
|
||||
local("Roboto-Thin"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff") format("woff"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto Condensed";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src:
|
||||
local("Roboto Condensed Light"),
|
||||
local("RobotoCondensed-Light"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.woff") format("woff"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto Condensed";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src:
|
||||
local("Roboto Condensed"),
|
||||
local("RobotoCondensed-Regular"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.woff") format("woff"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Roboto Condensed";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src:
|
||||
local("Roboto Condensed Bold"),
|
||||
local("RobotoCondensed-Bold"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.woff") format("woff"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src:
|
||||
local("Roboto"),
|
||||
local("Roboto-Regular"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff") format("woff"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src:
|
||||
local("Roboto Medium"),
|
||||
local("Roboto-Medium"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff") format("woff"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src:
|
||||
local("Roboto Bold"),
|
||||
local("Roboto-Bold"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff") format("woff"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.ttf") format("truetype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Roboto;
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src:
|
||||
local("Roboto Light"),
|
||||
local("Roboto-Light"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2") format("woff2"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff") format("woff"),
|
||||
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.ttf") format("truetype");
|
||||
}
|
55
index.html
Normal file
55
index.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>MagicMirror²</title>
|
||||
<meta name="google" content="notranslate" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
|
||||
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
|
||||
<link rel="stylesheet" type="text/css" href="css/main.css">
|
||||
<link rel="stylesheet" type="text/css" href="fonts/roboto.css">
|
||||
<!-- custom.css is loaded by the loader.js to make sure it's loaded after the module css files. -->
|
||||
|
||||
<script type="text/javascript">
|
||||
var version = "#VERSION#";
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="region fullscreen below"><div class="container"></div></div>
|
||||
<div class="region top bar">
|
||||
<div class="container"></div>
|
||||
<div class="region top left"><div class="container"></div></div>
|
||||
<div class="region top center"><div class="container"></div></div>
|
||||
<div class="region top right"><div class="container"></div></div>
|
||||
</div>
|
||||
<div class="region upper third"><div class="container"></div></div>
|
||||
<div class="region middle center"><div class="container"></div></div>
|
||||
<div class="region lower third"><div class="container"><br/></div></div>
|
||||
<div class="region bottom bar">
|
||||
<div class="container"></div>
|
||||
<div class="region bottom left"><div class="container"></div></div>
|
||||
<div class="region bottom center"><div class="container"></div></div>
|
||||
<div class="region bottom right"><div class="container"></div></div>
|
||||
</div>
|
||||
<div class="region fullscreen above"><div class="container"></div></div>
|
||||
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
|
||||
<script type="text/javascript" src="vendor/node_modules/nunjucks/browser/nunjucks.min.js"></script>
|
||||
<script type="text/javascript" src="js/defaults.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>
|
||||
<script type="text/javascript" src="translations/translations.js"></script>
|
||||
<script type="text/javascript" src="js/translator.js"></script>
|
||||
<script type="text/javascript" src="js/class.js"></script>
|
||||
<script type="text/javascript" src="js/module.js"></script>
|
||||
<script type="text/javascript" src="js/loader.js"></script>
|
||||
<script type="text/javascript" src="js/socketclient.js"></script>
|
||||
<script type="text/javascript" src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
39
index.php
39
index.php
@@ -1,39 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Magic Mirror</title>
|
||||
<style type="text/css">
|
||||
<?php include('css/main.css') ?>
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="css/weather-icons.css">
|
||||
<script type="text/javascript">
|
||||
var gitHash = '<?php echo trim(`git rev-parse HEAD`) ?>';
|
||||
</script>
|
||||
<meta name="google" value="notranslate" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top left"><div class="date small dimmed"></div><div class="time"></div><div class="calendar xxsmall"></div></div>
|
||||
<div class="top right"><div class="windsun small dimmed"></div><div class="temp"></div><div class="forecast small dimmed"></div></div>
|
||||
<div class="center-ver center-hor"><!-- <div class="dishwasher light">Vaatwasser is klaar!</div> --></div>
|
||||
<div class="lower-third center-hor"><div class="compliment light"></div></div>
|
||||
<div class="bottom center-hor"><div class="news medium"></div></div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="js/jquery.js"></script>
|
||||
<script src="js/jquery.feedToJSON.js"></script>
|
||||
<script src="js/ical_parser.js"></script>
|
||||
<script src="js/moment-with-locales.min.js"></script>
|
||||
<script src="js/config.js"></script>
|
||||
<script src="js/rrule.js"></script>
|
||||
<script src="js/version/version.js"></script>
|
||||
<script src="js/calendar/calendar.js"></script>
|
||||
<script src="js/compliments/compliments.js"></script>
|
||||
<script src="js/weather/weather.js"></script>
|
||||
<script src="js/time/time.js"></script>
|
||||
<script src="js/news/news.js"></script>
|
||||
<script src="js/main.js?nocache=<?php echo md5(microtime()) ?>"></script>
|
||||
<!-- <script src="js/socket.io.min.js"></script> -->
|
||||
</body>
|
||||
</html>
|
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
|
163
installers/raspberry.sh
Normal file
163
installers/raspberry.sh
Normal file
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This is an installer script for MagicMirror2. It works well enough
|
||||
# that it can detect if you have Node installed, run a binary script
|
||||
# and then download and run MagicMirror2.
|
||||
|
||||
echo -e "\e[0m"
|
||||
echo '$$\ $$\ $$\ $$\ $$\ $$\ $$$$$$\'
|
||||
echo '$$$\ $$$ | \__| $$$\ $$$ |\__| $$ __$$\'
|
||||
echo '$$$$\ $$$$ | $$$$$$\ $$$$$$\ $$\ $$$$$$$\ $$$$\ $$$$ |$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ \__/ $$ |'
|
||||
echo '$$\$$\$$ $$ | \____$$\ $$ __$$\ $$ |$$ _____|$$\$$\$$ $$ |$$ |$$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ $$$$$$ |'
|
||||
echo '$$ \$$$ $$ | $$$$$$$ |$$ / $$ |$$ |$$ / $$ \$$$ $$ |$$ |$$ | \__|$$ | \__|$$ / $$ |$$ | \__|$$ ____/'
|
||||
echo '$$ |\$ /$$ |$$ __$$ |$$ | $$ |$$ |$$ | $$ |\$ /$$ |$$ |$$ | $$ | $$ | $$ |$$ | $$ |'
|
||||
echo '$$ | \_/ $$ |\$$$$$$$ |\$$$$$$$ |$$ |\$$$$$$$\ $$ | \_/ $$ |$$ |$$ | $$ | \$$$$$$ |$$ | $$$$$$$$\'
|
||||
echo '\__| \__| \_______| \____$$ |\__| \_______|\__| \__|\__|\__| \__| \______/ \__| \________|'
|
||||
echo ' $$\ $$ |'
|
||||
echo ' \$$$$$$ |'
|
||||
echo ' \______/'
|
||||
echo -e "\e[0m"
|
||||
|
||||
# Define the tested version of Node.js.
|
||||
NODE_TESTED="v5.1.0"
|
||||
|
||||
# Determine which Pi is running.
|
||||
ARM=$(uname -m)
|
||||
|
||||
# Check the Raspberry Pi version.
|
||||
if [ "$ARM" != "armv7l" ]; then
|
||||
echo -e "\e[91mSorry, your Raspberry Pi is not supported."
|
||||
echo -e "\e[91mPlease run MagicMirror on a Raspberry Pi 2 or 3."
|
||||
echo -e "\e[91mIf this is a Pi Zero, you are in the same boat as the original Raspberry Pi. You must run in server only mode."
|
||||
exit;
|
||||
fi
|
||||
|
||||
# Define helper methods.
|
||||
function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; }
|
||||
function command_exists () { type "$1" &> /dev/null ;}
|
||||
|
||||
# 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
|
||||
|
||||
# Check if we need to install or upgrade Node.js.
|
||||
echo -e "\e[96mCheck current Node installation ...\e[0m"
|
||||
NODE_INSTALL=false
|
||||
if command_exists node; then
|
||||
echo -e "\e[0mNode currently installed. Checking version number.";
|
||||
NODE_CURRENT=$(node -v)
|
||||
echo -e "\e[0mMinimum Node version: \e[1m$NODE_TESTED\e[0m"
|
||||
echo -e "\e[0mInstalled Node version: \e[1m$NODE_CURRENT\e[0m"
|
||||
if version_gt $NODE_TESTED $NODE_CURRENT; then
|
||||
echo -e "\e[96mNode should be upgraded.\e[0m"
|
||||
NODE_INSTALL=true
|
||||
|
||||
# Check if a node process is currenlty running.
|
||||
# If so abort installation.
|
||||
if pgrep "node" > /dev/null; then
|
||||
echo -e "\e[91mA Node process is currently running. Can't upgrade."
|
||||
echo "Please quit all Node processes and restart the installer."
|
||||
exit;
|
||||
fi
|
||||
|
||||
else
|
||||
echo -e "\e[92mNo Node.js upgrade necessary.\e[0m"
|
||||
fi
|
||||
|
||||
else
|
||||
echo -e "\e[93mNode.js is not installed.\e[0m";
|
||||
NODE_INSTALL=true
|
||||
fi
|
||||
|
||||
# Install or upgrade node if necessary.
|
||||
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.
|
||||
|
||||
NODE_STABLE_BRANCH="6.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 MagicMirror
|
||||
cd ~
|
||||
if [ -d "$HOME/MagicMirror" ] ; then
|
||||
echo -e "\e[93mIt seems like MagicMirror is already installed."
|
||||
echo -e "To prevent overwriting, the installer will be aborted."
|
||||
echo -e "Please rename the \e[1m~/MagicMirror\e[0m\e[93m folder and try again.\e[0m"
|
||||
echo ""
|
||||
echo -e "If you want to upgrade your installation run \e[1m\e[97mgit pull\e[0m from the ~/MagicMirror directory."
|
||||
echo ""
|
||||
exit;
|
||||
fi
|
||||
|
||||
echo -e "\e[96mCloning MagicMirror ...\e[90m"
|
||||
if git clone https://github.com/MichMich/MagicMirror.git; then
|
||||
echo -e "\e[92mCloning MagicMirror Done!\e[0m"
|
||||
else
|
||||
echo -e "\e[91mUnable to clone MagicMirror."
|
||||
exit;
|
||||
fi
|
||||
|
||||
cd ~/MagicMirror || exit
|
||||
echo -e "\e[96mInstalling dependencies ...\e[90m"
|
||||
if npm install; then
|
||||
echo -e "\e[92mDependencies installation Done!\e[0m"
|
||||
else
|
||||
echo -e "\e[91mUnable to install dependencies!"
|
||||
exit;
|
||||
fi
|
||||
|
||||
# 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
|
||||
THEME_DIR="/usr/share/plymouth/themes"
|
||||
echo -e "\e[90mSplashscreen: Checking themes directory.\e[0m"
|
||||
if [ -d $THEME_DIR ]; then
|
||||
echo -e "\e[90mSplashscreen: Create theme directory if not exists.\e[0m"
|
||||
if [ ! -d $THEME_DIR/MagicMirror ]; then
|
||||
sudo mkdir $THEME_DIR/MagicMirror
|
||||
fi
|
||||
|
||||
if sudo cp ~/MagicMirror/splashscreen/splash.png $THEME_DIR/MagicMirror/splash.png && sudo cp ~/MagicMirror/splashscreen/MagicMirror.plymouth $THEME_DIR/MagicMirror/MagicMirror.plymouth && sudo cp ~/MagicMirror/splashscreen/MagicMirror.script $THEME_DIR/MagicMirror/MagicMirror.script; then
|
||||
echo -e "\e[90mSplashscreen: Theme copied successfully.\e[0m"
|
||||
if sudo plymouth-set-default-theme -R MagicMirror; then
|
||||
echo -e "\e[92mSplashscreen: Changed theme to MagicMirror successfully.\e[0m"
|
||||
else
|
||||
echo -e "\e[91mSplashscreen: Couldn't change theme to MagicMirror!\e[0m"
|
||||
fi
|
||||
else
|
||||
echo -e "\e[91mSplashscreen: Copying theme failed!\e[0m"
|
||||
fi
|
||||
else
|
||||
echo -e "\e[91mSplashscreen: Themes folder doesn't exist!\e[0m"
|
||||
fi
|
||||
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 " "
|
||||
echo " "
|
268
js/app.js
Normal file
268
js/app.js
Normal file
@@ -0,0 +1,268 @@
|
||||
/* Magic Mirror
|
||||
* The Core App (Server)
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
// Get version number.
|
||||
global.version = JSON.parse(fs.readFileSync("package.json", "utf8")).version;
|
||||
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) {
|
||||
console.log("Whoops! There was an uncaught exception...");
|
||||
console.error(err);
|
||||
console.log("MagicMirror will not quit, but it might be a good idea to check why this happened. Maybe no internet connection?");
|
||||
console.log("If you think this really is an issue, please open an issue on GitHub: https://github.com/MichMich/MagicMirror/issues");
|
||||
});
|
||||
|
||||
/* App - The core app.
|
||||
*/
|
||||
var App = function() {
|
||||
var nodeHelpers = [];
|
||||
|
||||
/* loadConfig(callback)
|
||||
* Loads the config file. combines it with the defaults,
|
||||
* and runs the callback with the found config as argument.
|
||||
*
|
||||
* argument callback function - The callback 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(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(Utils.colors.error("WARNING! Could not validate config file. Please correct syntax errors. Starting with default configuration."));
|
||||
} else {
|
||||
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, callback) {
|
||||
|
||||
var elements = module.split("/");
|
||||
var moduleName = elements[elements.length - 1];
|
||||
var moduleFolder = __dirname + "/../modules/" + module;
|
||||
|
||||
if (defaultModules.indexOf(moduleName) !== -1) {
|
||||
moduleFolder = __dirname + "/../modules/default/" + module;
|
||||
}
|
||||
|
||||
var helperPath = moduleFolder + "/node_helper.js";
|
||||
|
||||
var loadModule = true;
|
||||
try {
|
||||
fs.accessSync(helperPath, fs.R_OK);
|
||||
} catch (e) {
|
||||
loadModule = false;
|
||||
console.log("No helper found for module: " + moduleName + ".");
|
||||
}
|
||||
|
||||
if (loadModule) {
|
||||
var Module = require(helperPath);
|
||||
var m = new Module();
|
||||
|
||||
if (m.requiresVersion) {
|
||||
console.log("Check MagicMirror version for node helper '" + moduleName + "' - Minimum version: " + m.requiresVersion + " - Current version: " + global.version);
|
||||
if (cmpVersions(global.version, m.requiresVersion) >= 0) {
|
||||
console.log("Version is ok!");
|
||||
} else {
|
||||
console.log("Version is incorrect. Skip module: '" + moduleName + "'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m.setName(moduleName);
|
||||
m.setPath(path.resolve(moduleFolder));
|
||||
nodeHelpers.push(m);
|
||||
|
||||
m.loaded(callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
/* loadModules(modules)
|
||||
* Loads all modules.
|
||||
*
|
||||
* argument module string - The name of the module (including subpath).
|
||||
*/
|
||||
var loadModules = function(modules, callback) {
|
||||
console.log("Loading module helpers ...");
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
loadNextModule();
|
||||
};
|
||||
|
||||
/* cmpVersions(a,b)
|
||||
* Compare two symantic version numbers and return the difference.
|
||||
*
|
||||
* argument a string - Version number a.
|
||||
* argument a string - Version number b.
|
||||
*/
|
||||
function cmpVersions(a, b) {
|
||||
var i, diff;
|
||||
var regExStrip0 = /(\.0+)+$/;
|
||||
var segmentsA = a.replace(regExStrip0, "").split(".");
|
||||
var segmentsB = b.replace(regExStrip0, "").split(".");
|
||||
var l = Math.min(segmentsA.length, segmentsB.length);
|
||||
|
||||
for (i = 0; i < l; i++) {
|
||||
diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
|
||||
if (diff) {
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
return segmentsA.length - segmentsB.length;
|
||||
}
|
||||
|
||||
/* start(callback)
|
||||
* This methods starts the core app.
|
||||
* It loads the config, then it loads all modules.
|
||||
* When it"s done it executs the callback with the config as argument.
|
||||
*
|
||||
* argument callback function - The callback function.
|
||||
*/
|
||||
this.start = function(callback) {
|
||||
|
||||
loadConfig(function(c) {
|
||||
config = c;
|
||||
|
||||
var modules = [];
|
||||
|
||||
for (var m in config.modules) {
|
||||
var module = config.modules[m];
|
||||
if (modules.indexOf(module.module) === -1 && !module.disabled) {
|
||||
modules.push(module.module);
|
||||
}
|
||||
}
|
||||
|
||||
loadModules(modules, function() {
|
||||
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();
|
||||
}
|
||||
|
||||
console.log("Sockets connected & modules started ...");
|
||||
|
||||
if (typeof callback === "function") {
|
||||
callback(config);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/* stop()
|
||||
* This methods stops the core app.
|
||||
* This calls each node_helper's STOP() function, if it exists.
|
||||
* Added to fix #1056
|
||||
*/
|
||||
this.stop = function() {
|
||||
for (var h in nodeHelpers) {
|
||||
var nodeHelper = nodeHelpers[h];
|
||||
if (typeof nodeHelper.stop === "function") {
|
||||
nodeHelper.stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Listen for SIGINT signal and call stop() function.
|
||||
*
|
||||
* Added to fix #1056
|
||||
* Note: this is only used if running `server-only`. Otherwise
|
||||
* this.stop() is called by app.on("before-quit"... in `electron.js`
|
||||
*/
|
||||
process.on("SIGINT", () => {
|
||||
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();
|
@@ -1,139 +0,0 @@
|
||||
var calendar = {
|
||||
eventList: [],
|
||||
calendarLocation: '.calendar',
|
||||
updateInterval: 1000,
|
||||
updateDataInterval: 60000,
|
||||
fadeInterval: 1000,
|
||||
intervalId: null,
|
||||
dataIntervalId: null,
|
||||
maximumEntries: config.calendar.maximumEntries || 10
|
||||
}
|
||||
|
||||
calendar.updateData = function (callback) {
|
||||
|
||||
new ical_parser("controllers/calendar.php" + "?url="+encodeURIComponent(config.calendar.url), function(cal) {
|
||||
var events = cal.getEvents();
|
||||
this.eventList = [];
|
||||
|
||||
for (var i in events) {
|
||||
|
||||
var e = events[i];
|
||||
for (var key in e) {
|
||||
var value = e[key];
|
||||
var seperator = key.search(';');
|
||||
if (seperator >= 0) {
|
||||
var mainKey = key.substring(0,seperator);
|
||||
var subKey = key.substring(seperator+1);
|
||||
|
||||
var dt;
|
||||
if (subKey == 'VALUE=DATE') {
|
||||
//date
|
||||
dt = new Date(value.substring(0,4), value.substring(4,6) - 1, value.substring(6,8));
|
||||
} else {
|
||||
//time
|
||||
dt = new Date(value.substring(0,4), value.substring(4,6) - 1, value.substring(6,8), value.substring(9,11), value.substring(11,13), value.substring(13,15));
|
||||
}
|
||||
|
||||
if (mainKey == 'DTSTART') e.startDate = dt;
|
||||
if (mainKey == 'DTEND') e.endDate = dt;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.startDate == undefined){
|
||||
//some old events in Gmail Calendar is "start_date"
|
||||
//FIXME: problems with Gmail's TimeZone
|
||||
var days = moment(e.DTSTART).diff(moment(), 'days');
|
||||
var seconds = moment(e.DTSTART).diff(moment(), 'seconds');
|
||||
var startDate = moment(e.DTSTART);
|
||||
} else {
|
||||
var days = moment(e.startDate).diff(moment(), 'days');
|
||||
var seconds = moment(e.startDate).diff(moment(), 'seconds');
|
||||
var startDate = moment(e.startDate);
|
||||
}
|
||||
|
||||
//only add fututre events, days doesn't work, we need to check seconds
|
||||
if (seconds >= 0) {
|
||||
if (seconds <= 60*60*5 || seconds >= 60*60*24*2) {
|
||||
var time_string = moment(startDate).fromNow();
|
||||
}else {
|
||||
var time_string = moment(startDate).calendar()
|
||||
}
|
||||
if (!e.RRULE) {
|
||||
this.eventList.push({'description':e.SUMMARY,'seconds':seconds,'days':time_string});
|
||||
}
|
||||
e.seconds = seconds;
|
||||
}
|
||||
|
||||
// Special handling for rrule events
|
||||
if (e.RRULE) {
|
||||
var options = new RRule.parseString(e.RRULE);
|
||||
options.dtstart = e.startDate;
|
||||
var rule = new RRule(options);
|
||||
|
||||
var oneYear = new Date();
|
||||
oneYear.setFullYear(oneYear.getFullYear() + 1);
|
||||
|
||||
var dates = rule.between(new Date(), oneYear, true, function (date, i){return i < 10});
|
||||
for (date in dates) {
|
||||
var dt = new Date(dates[date]);
|
||||
var days = moment(dt).diff(moment(), 'days');
|
||||
var seconds = moment(dt).diff(moment(), 'seconds');
|
||||
var startDate = moment(dt);
|
||||
if (seconds >= 0) {
|
||||
if (seconds <= 60*60*5 || seconds >= 60*60*24*2) {
|
||||
var time_string = moment(dt).fromNow();
|
||||
} else {
|
||||
var time_string = moment(dt).calendar()
|
||||
}
|
||||
this.eventList.push({'description':e.SUMMARY,'seconds':seconds,'days':time_string});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.eventList = this.eventList.sort(function(a,b){return a.seconds-b.seconds});
|
||||
|
||||
// Limit the number of entries.
|
||||
this.eventList = this.eventList.slice(0, calendar.maximumEntries);
|
||||
|
||||
if (callback !== undefined && Object.prototype.toString.call(callback) === '[object Function]') {
|
||||
callback(this.eventList);
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
|
||||
}
|
||||
|
||||
calendar.updateCalendar = function (eventList) {
|
||||
|
||||
table = $('<table/>').addClass('xsmall').addClass('calendar-table');
|
||||
opacity = 1;
|
||||
|
||||
for (var i in eventList) {
|
||||
var e = eventList[i];
|
||||
|
||||
var row = $('<tr/>').css('opacity',opacity);
|
||||
row.append($('<td/>').html(e.description).addClass('description'));
|
||||
row.append($('<td/>').html(e.days).addClass('days dimmed'));
|
||||
table.append(row);
|
||||
|
||||
opacity -= 1 / eventList.length;
|
||||
}
|
||||
|
||||
$(this.calendarLocation).updateWithText(table, this.fadeInterval);
|
||||
|
||||
}
|
||||
|
||||
calendar.init = function () {
|
||||
|
||||
this.updateData(this.updateCalendar.bind(this));
|
||||
|
||||
this.intervalId = setInterval(function () {
|
||||
this.updateCalendar(this.eventList)
|
||||
}.bind(this), this.updateInterval);
|
||||
|
||||
this.dataIntervalId = setInterval(function () {
|
||||
this.updateData(this.updateCalendar.bind(this));
|
||||
}.bind(this), this.updateDataInterval);
|
||||
|
||||
}
|
98
js/class.js
Normal file
98
js/class.js
Normal file
@@ -0,0 +1,98 @@
|
||||
/* Simple JavaScript Inheritance
|
||||
* By John Resig http://ejohn.org/
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
// Inspired by base2 and Prototype
|
||||
(function () {
|
||||
var initializing = false;
|
||||
var fnTest = /xyz/.test(function () { xyz; }) ? /\b_super\b/ : /.*/;
|
||||
|
||||
// The base Class implementation (does nothing)
|
||||
this.Class = function () { };
|
||||
|
||||
// Create a new Class that inherits from this class
|
||||
Class.extend = function (prop) {
|
||||
var _super = this.prototype;
|
||||
|
||||
// Instantiate a base class (but only create the instance,
|
||||
// don't run the init constructor)
|
||||
initializing = true;
|
||||
var prototype = new this();
|
||||
initializing = false;
|
||||
|
||||
// Make a copy of all prototype properies, to prevent reference issues.
|
||||
for (var name in prototype) {
|
||||
prototype[name] = cloneObject(prototype[name]);
|
||||
}
|
||||
|
||||
// Copy the properties over onto the new prototype
|
||||
for (var name in prop) {
|
||||
// Check if we're overwriting an existing function
|
||||
prototype[name] = typeof prop[name] == "function" &&
|
||||
typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function (name, fn) {
|
||||
return function () {
|
||||
var tmp = this._super;
|
||||
|
||||
// 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;
|
||||
|
||||
|
||||
return ret;
|
||||
};
|
||||
})(name, prop[name]) : prop[name];
|
||||
}
|
||||
|
||||
// The dummy class constructor
|
||||
function Class() {
|
||||
// All construction is actually done in the init method
|
||||
if (!initializing && this.init) {
|
||||
this.init.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
// Populate our constructed prototype object
|
||||
Class.prototype = prototype;
|
||||
|
||||
// Enforce the constructor to be what we expect
|
||||
Class.prototype.constructor = Class;
|
||||
|
||||
// And make this class extendable
|
||||
Class.extend = arguments.callee;
|
||||
|
||||
return Class;
|
||||
};
|
||||
})();
|
||||
|
||||
//Define the clone method for later use.
|
||||
//Helper Method
|
||||
function cloneObject(obj) {
|
||||
if (obj === null || typeof obj !== "object") {
|
||||
return obj;
|
||||
}
|
||||
|
||||
var temp = obj.constructor(); // give temp the original obj's constructor
|
||||
for (var key in obj) {
|
||||
temp[key] = cloneObject(obj[key]);
|
||||
|
||||
if (key === "lockStrings") {
|
||||
Log.log(key);
|
||||
}
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = Class;
|
||||
module.exports._test = {
|
||||
cloneObject: cloneObject
|
||||
}
|
||||
}
|
@@ -1,70 +0,0 @@
|
||||
var compliments = {
|
||||
complimentLocation: '.compliment',
|
||||
currentCompliment: '',
|
||||
complimentList: {
|
||||
'morning': config.compliments.morning,
|
||||
'afternoon': config.compliments.afternoon,
|
||||
'evening': config.compliments.evening
|
||||
},
|
||||
updateInterval: config.compliments.interval || 30000,
|
||||
fadeInterval: config.compliments.fadeInterval || 4000,
|
||||
intervalId: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Changes the compliment visible on the screen
|
||||
*/
|
||||
compliments.updateCompliment = function () {
|
||||
|
||||
|
||||
|
||||
var _list = [];
|
||||
|
||||
var hour = moment().hour();
|
||||
|
||||
// In the followign if statement we use .slice() on the
|
||||
// compliments array to make a copy by value.
|
||||
// This way the original array of compliments stays in tact.
|
||||
|
||||
if (hour >= 3 && hour < 12) {
|
||||
// Morning compliments
|
||||
_list = compliments.complimentList['morning'].slice();
|
||||
} else if (hour >= 12 && hour < 17) {
|
||||
// Afternoon compliments
|
||||
_list = compliments.complimentList['afternoon'].slice();
|
||||
} else if (hour >= 17 || hour < 3) {
|
||||
// Evening compliments
|
||||
_list = compliments.complimentList['evening'].slice();
|
||||
} else {
|
||||
// Edge case in case something weird happens
|
||||
// This will select a compliment from all times of day
|
||||
Object.keys(compliments.complimentList).forEach(function (_curr) {
|
||||
_list = _list.concat(compliments.complimentList[_curr]).slice();
|
||||
});
|
||||
}
|
||||
|
||||
// Search for the location of the current compliment in the list
|
||||
var _spliceIndex = _list.indexOf(compliments.currentCompliment);
|
||||
|
||||
// If it exists, remove it so we don't see it again
|
||||
if (_spliceIndex !== -1) {
|
||||
_list.splice(_spliceIndex, 1);
|
||||
}
|
||||
|
||||
// Randomly select a location
|
||||
var _randomIndex = Math.floor(Math.random() * _list.length);
|
||||
compliments.currentCompliment = _list[_randomIndex];
|
||||
|
||||
$('.compliment').updateWithText(compliments.currentCompliment, compliments.fadeInterval);
|
||||
|
||||
}
|
||||
|
||||
compliments.init = function () {
|
||||
|
||||
this.updateCompliment();
|
||||
|
||||
this.intervalId = setInterval(function () {
|
||||
this.updateCompliment();
|
||||
}.bind(this), this.updateInterval)
|
||||
|
||||
}
|
43
js/config.js
43
js/config.js
@@ -1,43 +0,0 @@
|
||||
var config = {
|
||||
lang: 'nl',
|
||||
time: {
|
||||
timeFormat: 12
|
||||
},
|
||||
weather: {
|
||||
//change weather params here:
|
||||
//units: metric or imperial
|
||||
params: {
|
||||
q: 'Baarn,Netherlands',
|
||||
units: 'metric',
|
||||
// if you want a different lang for the weather that what is set above, change it here
|
||||
lang: 'nl',
|
||||
APPID: 'YOUR_FREE_OPENWEATHER_API_KEY'
|
||||
}
|
||||
},
|
||||
compliments: {
|
||||
interval: 30000,
|
||||
fadeInterval: 4000,
|
||||
morning: [
|
||||
'Good morning, handsome!',
|
||||
'Enjoy your day!',
|
||||
'How was your sleep?'
|
||||
],
|
||||
afternoon: [
|
||||
'Hello, beauty!',
|
||||
'You look sexy!',
|
||||
'Looking good today!'
|
||||
],
|
||||
evening: [
|
||||
'Wow, you look hot!',
|
||||
'You look nice!',
|
||||
'Hi, sexy!'
|
||||
]
|
||||
},
|
||||
calendar: {
|
||||
maximumEntries: 10,
|
||||
url: "https://p01-calendarws.icloud.com/ca/subscribe/1/n6x7Farxpt7m9S8bHg1TGArSj7J6kanm_2KEoJPL5YIAk3y70FpRo4GyWwO-6QfHSY5mXtHcRGVxYZUf7U3HPDOTG5x0qYnno1Zr_VuKH2M"
|
||||
},
|
||||
news: {
|
||||
feed: 'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml'
|
||||
}
|
||||
}
|
81
js/defaults.js
Normal file
81
js/defaults.js
Normal file
@@ -0,0 +1,81 @@
|
||||
/* exported defaults */
|
||||
|
||||
/* Magic Mirror
|
||||
* Config Defauls
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var port = 8080;
|
||||
var address = "localhost";
|
||||
if (typeof(mmPort) !== "undefined") {
|
||||
port = mmPort;
|
||||
}
|
||||
var defaults = {
|
||||
address: address,
|
||||
port: port,
|
||||
kioskmode: false,
|
||||
electronOptions: {},
|
||||
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
|
||||
|
||||
language: "en",
|
||||
timeFormat: 24,
|
||||
units: "metric",
|
||||
zoom: 1,
|
||||
customCss: "css/custom.css",
|
||||
|
||||
modules: [
|
||||
{
|
||||
module: "updatenotification",
|
||||
position: "top_center"
|
||||
},
|
||||
{
|
||||
module: "helloworld",
|
||||
position: "upper_third",
|
||||
classes: "large thin",
|
||||
config: {
|
||||
text: "Magic Mirror<sup>2</sup>"
|
||||
}
|
||||
},
|
||||
{
|
||||
module: "helloworld",
|
||||
position: "middle_center",
|
||||
config: {
|
||||
text: "Please create a config file."
|
||||
}
|
||||
},
|
||||
{
|
||||
module: "helloworld",
|
||||
position: "middle_center",
|
||||
classes: "small dimmed",
|
||||
config: {
|
||||
text: "See README for more information."
|
||||
}
|
||||
},
|
||||
{
|
||||
module: "helloworld",
|
||||
position: "middle_center",
|
||||
classes: "xsmall",
|
||||
config: {
|
||||
text: "If you get this message while your config file is already<br>created, your config file probably contains an error.<br>Use a JavaScript linter to validate your file."
|
||||
}
|
||||
},
|
||||
{
|
||||
module: "helloworld",
|
||||
position: "bottom_bar",
|
||||
classes: "xsmall dimmed",
|
||||
config: {
|
||||
text: "www.michaelteeuw.nl"
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
paths: {
|
||||
modules: "modules",
|
||||
vendor: "vendor"
|
||||
},
|
||||
};
|
||||
|
||||
/*************** DO NOT EDIT THE LINE BELOW ***************/
|
||||
if (typeof module !== "undefined") {module.exports = defaults;}
|
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;}
|
119
js/electron.js
Normal file
119
js/electron.js
Normal file
@@ -0,0 +1,119 @@
|
||||
/* jshint esversion: 6 */
|
||||
|
||||
"use strict";
|
||||
|
||||
const electron = require("electron");
|
||||
const core = require(__dirname + "/app.js");
|
||||
|
||||
// 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.
|
||||
const BrowserWindow = electron.BrowserWindow;
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let mainWindow;
|
||||
|
||||
function createWindow() {
|
||||
var electronOptionsDefaults = {
|
||||
width: 800,
|
||||
height: 600,
|
||||
x: 0,
|
||||
y: 0,
|
||||
darkTheme: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
zoomFactor: config.zoom
|
||||
},
|
||||
backgroundColor: "#000000"
|
||||
};
|
||||
|
||||
// DEPRECATED: "kioskmode" backwards compatibility, to be removed
|
||||
// settings these options directly instead provides cleaner interface
|
||||
if (config.kioskmode) {
|
||||
electronOptionsDefaults.kiosk = true;
|
||||
} else {
|
||||
electronOptionsDefaults.fullscreen = true;
|
||||
electronOptionsDefaults.autoHideMenuBar = true;
|
||||
}
|
||||
|
||||
var electronOptions = Object.assign({}, electronOptionsDefaults, config.electronOptions);
|
||||
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow(electronOptions);
|
||||
|
||||
// and load the index.html of the app.
|
||||
// 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.includes("dev")) {
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
// Set responders for window events.
|
||||
mainWindow.on("closed", function() {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
if (config.kioskmode) {
|
||||
mainWindow.on("blur", function() {
|
||||
mainWindow.focus();
|
||||
});
|
||||
|
||||
mainWindow.on("leave-full-screen", function() {
|
||||
mainWindow.setFullScreen(true);
|
||||
});
|
||||
|
||||
mainWindow.on("resize", function() {
|
||||
setTimeout(function() {
|
||||
mainWindow.reload();
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
app.on("ready", function() {
|
||||
console.log("Launching application.");
|
||||
createWindow();
|
||||
});
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on("window-all-closed", function() {
|
||||
createWindow();
|
||||
});
|
||||
|
||||
app.on("activate", function() {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (mainWindow === null) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
/* 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;
|
||||
});
|
||||
}
|
@@ -1,224 +0,0 @@
|
||||
/**
|
||||
* Javascript ical Parser
|
||||
* Proof of concept method of reading icalendar (.ics) files with javascript.
|
||||
*
|
||||
* @author: Carl Saggs
|
||||
* @source: https://github.com/thybag/
|
||||
* @version: 0.2
|
||||
*/
|
||||
function ical_parser(feed_url, callback){
|
||||
//store of unproccesed data.
|
||||
this.raw_data = null;
|
||||
//Store of proccessed data.
|
||||
this.events = [];
|
||||
|
||||
/**
|
||||
* loadFile
|
||||
* Using AJAX to load the requested .ics file, passing it to the callback when completed.
|
||||
* @param url URL of .ics file
|
||||
* @param callback Function to call on completion.
|
||||
*/
|
||||
this.loadFile = function(url, callback){
|
||||
//Create request object
|
||||
try {xmlhttp = window.XMLHttpRequest?new XMLHttpRequest(): new ActiveXObject("Microsoft.XMLHTTP");} catch (e) { }
|
||||
//Grab file
|
||||
xmlhttp.onreadystatechange = function(){
|
||||
if ((xmlhttp.readyState == 4) && (xmlhttp.status == 200)) {
|
||||
//On success, run callback.
|
||||
callback(xmlhttp.responseText);
|
||||
}
|
||||
}
|
||||
xmlhttp.open("GET", url, true);
|
||||
xmlhttp.send(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* makeDate
|
||||
* Convert the dateformat used by ICalendar in to one more suitable for javascript.
|
||||
* @param String ical_date
|
||||
* @return dt object, includes javascript Date + day name, hour/minutes/day/month/year etc.
|
||||
*/
|
||||
this.makeDate = function(ical_date){
|
||||
//break date apart
|
||||
var dtutc = {
|
||||
year: ical_date.substr(0,4),
|
||||
month: ical_date.substr(4,2),
|
||||
day: ical_date.substr(6,2),
|
||||
hour: ical_date.substr(9,2),
|
||||
minute: ical_date.substr(11,2)
|
||||
}
|
||||
//Create JS date (months start at 0 in JS - don't ask)
|
||||
var utcdatems = Date.UTC(dtutc.year, (dtutc.month-1), dtutc.day, dtutc.hour, dtutc.minute);
|
||||
var dt = {};
|
||||
dt.date = new Date(utcdatems);
|
||||
|
||||
dt.year = dt.date.getFullYear();
|
||||
dt.month = ('0' + (dt.date.getMonth()+1)).slice(-2);
|
||||
dt.day = ('0' + dt.date.getDate()).slice(-2);
|
||||
dt.hour = ('0' + dt.date.getHours()).slice(-2);
|
||||
dt.minute = ('0' + dt.date.getMinutes()).slice(-2);
|
||||
|
||||
//Get the full name of the given day
|
||||
dt.dayname =["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"][dt.date.getDay()];
|
||||
dt.monthname = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ][dt.date.getMonth()] ;
|
||||
|
||||
return dt;
|
||||
}
|
||||
|
||||
/**
|
||||
* parseICAL
|
||||
* Convert the ICAL format in to a number of javascript objects (Each representing a date)
|
||||
*
|
||||
* @param data Raw ICAL data
|
||||
*/
|
||||
this.parseICAL = function(data){
|
||||
//Ensure cal is empty
|
||||
this.events = [];
|
||||
|
||||
//Clean string and split the file so we can handle it (line by line)
|
||||
cal_array = data.replace(new RegExp( "\\r", "g" ), "").replace(/\n /g,"").split("\n");
|
||||
|
||||
//Keep track of when we are activly parsing an event
|
||||
var in_event = false;
|
||||
//Use as a holder for the current event being proccessed.
|
||||
var cur_event = null;
|
||||
for(var i=0;i<cal_array.length;i++){
|
||||
ln = cal_array[i];
|
||||
//If we encounted a new Event, create a blank event object + set in event options.
|
||||
if(!in_event && ln == 'BEGIN:VEVENT'){
|
||||
in_event = true;
|
||||
cur_event = {};
|
||||
}
|
||||
//If we encounter end event, complete the object and add it to our events array then clear it for reuse.
|
||||
if(in_event && ln == 'END:VEVENT'){
|
||||
in_event = false;
|
||||
this.events.push(cur_event);
|
||||
cur_event = null;
|
||||
}
|
||||
//If we are in an event
|
||||
else if(in_event){
|
||||
//var lntrim = ln.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
|
||||
//var lnsplit = lntrim.split(':');
|
||||
//type = lnsplit[0];
|
||||
//val = lnsplit[1];
|
||||
|
||||
//Split the item based on the first ":"
|
||||
idx = ln.indexOf(':');
|
||||
//Apply trimming to values to reduce risks of badly formatted ical files.
|
||||
type = ln.substr(0,idx).replace(/^\s\s*/, '').replace(/\s\s*$/, '');//Trim
|
||||
val = ln.substr(idx+1).replace(/^\s\s*/, '').replace(/\s\s*$/, '');
|
||||
|
||||
//If the type is a start date, proccess it and store details
|
||||
if(type =='DTSTART'){
|
||||
dt = this.makeDate(val);
|
||||
val = dt.date;
|
||||
//These are helpful for display
|
||||
cur_event.start_time = dt.hour+':'+dt.minute;
|
||||
cur_event.start_date = dt.day+'/'+dt.month+'/'+dt.year;
|
||||
cur_event.day = dt.dayname;
|
||||
cur_event.start_date_long = dt.day+'. '+dt.monthname+' '+dt.year ;
|
||||
}
|
||||
//If the type is an end date, do the same as above
|
||||
else if(type =='DTEND'){
|
||||
dt = this.makeDate(val);
|
||||
val = dt.date;
|
||||
//These are helpful for display
|
||||
cur_event.end_time = dt.hour+':'+dt.minute;
|
||||
cur_event.end_date = dt.day+'/'+dt.month+'/'+dt.year;
|
||||
cur_event.day = dt.dayname;
|
||||
}
|
||||
//Convert timestamp
|
||||
else if(type =='DTSTAMP'){
|
||||
val = this.makeDate(val).date;
|
||||
}
|
||||
else {
|
||||
val = val
|
||||
.replace(/\\r\\n/g,'<br />')
|
||||
.replace(/\\n/g,'<br />')
|
||||
.replace(/\\,/g,',');
|
||||
}
|
||||
|
||||
//Add the value to our event object.
|
||||
cur_event[type] = val;
|
||||
}
|
||||
}
|
||||
//Run this to finish proccessing our Events.
|
||||
this.complete();
|
||||
}
|
||||
/**
|
||||
* complete
|
||||
* Sort all events in to a sensible order and run the original callback
|
||||
*/
|
||||
this.complete = function(){
|
||||
//Sort the data so its in date order.
|
||||
this.events.sort(function(a,b){
|
||||
return a.DTSTART-b.DTSTART;
|
||||
});
|
||||
//Run callback method, if was defined. (return self)
|
||||
if(typeof callback == 'function') callback(this);
|
||||
}
|
||||
/**
|
||||
* getEvents
|
||||
* return all events found in the ical file.
|
||||
*
|
||||
* @return list of events objects
|
||||
*/
|
||||
this.getEvents = function(){
|
||||
return this.events;
|
||||
}
|
||||
|
||||
/**
|
||||
* getFutureEvents
|
||||
* return all events sheduled to take place after the current date.
|
||||
*
|
||||
* @return list of events objects
|
||||
*/
|
||||
this.getFutureEvents = function(){
|
||||
var future_events = [], current_date = new Date();
|
||||
|
||||
this.events.forEach(function(itm){
|
||||
//If the event ends after the current time, add it to the array to return.
|
||||
if(itm.DTEND > current_date) future_events.push(itm);
|
||||
});
|
||||
return future_events;
|
||||
}
|
||||
|
||||
/**
|
||||
* getPastEvents
|
||||
* return all events sheduled to take place before the current date.
|
||||
*
|
||||
* @return list of events objects
|
||||
*/
|
||||
this.getPastEvents = function(){
|
||||
var past_events = [], current_date = new Date();
|
||||
|
||||
this.events.forEach(function(itm){
|
||||
//If the event ended before the current time, add it to the array to return.
|
||||
if(itm.DTEND <= current_date) past_events.push(itm);
|
||||
});
|
||||
return past_events.reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* load
|
||||
* load a new ICAL file.
|
||||
*
|
||||
* @param ical file url
|
||||
*/
|
||||
this.load = function(ical_file){
|
||||
var tmp_this = this;
|
||||
this.raw_data = null;
|
||||
this.loadFile(ical_file, function(data){
|
||||
//if the file loads, store the data and invoke the parser
|
||||
tmp_this.raw_data = data;
|
||||
tmp_this.parseICAL(data);
|
||||
});
|
||||
}
|
||||
|
||||
//Store this so we can use it in the callback from the load function.
|
||||
var tmp_this = this;
|
||||
//Store the feed url
|
||||
this.feed_url = feed_url;
|
||||
//Load the file
|
||||
this.load(this.feed_url);
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
//jQuery extension to fetch an rss feed and return it as json via YQL
|
||||
//created by dboz@airshp.com
|
||||
(function($) {
|
||||
|
||||
$.extend({
|
||||
feedToJson: function(options, callback) {
|
||||
if ($.isFunction(options)) {
|
||||
callback = options;
|
||||
options = null;
|
||||
}
|
||||
options = $.extend($.feedToJson.defaults,options);
|
||||
var url = options.yqlURL + options.yqlQS + "'" + encodeURIComponent(options.feed) + "'" + "&_nocache=" + options.cacheBuster;
|
||||
return $.getJSON(url, function(data){
|
||||
//console.log(data.query.results);
|
||||
data = data.query.results;
|
||||
$.isFunction(callback) && callback(data); //allows the callback function to be the only option
|
||||
$.isFunction(options.success) && options.success(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//defaults
|
||||
$.feedToJson.defaults = {
|
||||
yqlURL : 'https://query.yahooapis.com/v1/public/yql', //yql
|
||||
yqlQS : '?format=json&callback=?&q=select%20*%20from%20rss%20where%20url%3D', //yql query string
|
||||
feed:'http://instagr.am/tags/tacos/feed/recent.rss', //instagram recent posts tagged 'tacos'
|
||||
cachebuster: Math.floor((new Date().getTime()) / 1200 / 1000), //yql caches feeds, so we change the feed url every 20min
|
||||
success:null //success callback
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
// eo feedToJson
|
4
js/jquery.js
vendored
4
js/jquery.js
vendored
File diff suppressed because one or more lines are too long
265
js/loader.js
Normal file
265
js/loader.js
Normal file
@@ -0,0 +1,265 @@
|
||||
/* global config, vendor, MM, Log, Module */
|
||||
/* Magic Mirror
|
||||
* Module and File loaders.
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var Loader = (function() {
|
||||
|
||||
/* Create helper valiables */
|
||||
|
||||
var loadedModuleFiles = [];
|
||||
var loadedFiles = [];
|
||||
var moduleObjects = [];
|
||||
|
||||
/* Private Methods */
|
||||
|
||||
/* loadModules()
|
||||
* Loops thru all modules and requests load for every module.
|
||||
*/
|
||||
var loadModules = function() {
|
||||
|
||||
var moduleData = getModuleData();
|
||||
|
||||
var loadNextModule = function() {
|
||||
if (moduleData.length > 0) {
|
||||
var nextModule = moduleData[0];
|
||||
loadModule(nextModule, function() {
|
||||
moduleData = moduleData.slice(1);
|
||||
loadNextModule();
|
||||
});
|
||||
} else {
|
||||
// All modules loaded. Load custom.css
|
||||
// This is done after all the modules so we can
|
||||
// overwrite all the defined styles.
|
||||
|
||||
loadFile(config.customCss, function() {
|
||||
// custom.css loaded. Start all modules.
|
||||
startModules();
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
loadNextModule();
|
||||
};
|
||||
|
||||
/* startModules()
|
||||
* Loops thru all modules and requests start for every module.
|
||||
*/
|
||||
var startModules = function() {
|
||||
for (var m in moduleObjects) {
|
||||
var module = moduleObjects[m];
|
||||
module.start();
|
||||
}
|
||||
|
||||
// Notifiy core of loded modules.
|
||||
MM.modulesStarted(moduleObjects);
|
||||
};
|
||||
|
||||
/* getAllModules()
|
||||
* Retrieve list of all modules.
|
||||
*
|
||||
* return array - module data as configured in config
|
||||
*/
|
||||
var getAllModules = function() {
|
||||
return config.modules;
|
||||
};
|
||||
|
||||
/* getModuleData()
|
||||
* Generate array with module information including module paths.
|
||||
*
|
||||
* return array - Module information.
|
||||
*/
|
||||
var getModuleData = function() {
|
||||
var modules = getAllModules();
|
||||
var moduleFiles = [];
|
||||
|
||||
for (var m in modules) {
|
||||
var moduleData = modules[m];
|
||||
var module = moduleData.module;
|
||||
|
||||
var elements = module.split("/");
|
||||
var moduleName = elements[elements.length - 1];
|
||||
var moduleFolder = config.paths.modules + "/" + module;
|
||||
|
||||
if (defaultModules.indexOf(moduleName) !== -1) {
|
||||
moduleFolder = config.paths.modules + "/default/" + module;
|
||||
}
|
||||
|
||||
if (moduleData.disabled === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
moduleFiles.push({
|
||||
index: m,
|
||||
identifier: "module_" + m + "_" + module,
|
||||
name: moduleName,
|
||||
path: moduleFolder + "/" ,
|
||||
file: moduleName + ".js",
|
||||
position: moduleData.position,
|
||||
header: moduleData.header,
|
||||
config: moduleData.config,
|
||||
classes: (typeof moduleData.classes !== "undefined") ? moduleData.classes + " " + module : module
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return moduleFiles;
|
||||
};
|
||||
|
||||
/* loadModule(module)
|
||||
* Load modules via ajax request and create module objects.
|
||||
*
|
||||
* argument callback function - Function called when done.
|
||||
* argument module object - Information about the module we want to load.
|
||||
*/
|
||||
var loadModule = function(module, callback) {
|
||||
var url = module.path + "/" + module.file;
|
||||
|
||||
var afterLoad = function() {
|
||||
var moduleObject = Module.create(module.name);
|
||||
if (moduleObject) {
|
||||
bootstrapModule(module, moduleObject, function() {
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
if (loadedModuleFiles.indexOf(url) !== -1) {
|
||||
afterLoad();
|
||||
} else {
|
||||
loadFile(url, function() {
|
||||
loadedModuleFiles.push(url);
|
||||
afterLoad();
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* bootstrapModule(module, mObj)
|
||||
* Bootstrap modules by setting the module data and loading the scripts & styles.
|
||||
*
|
||||
* argument module object - Information about the module we want to load.
|
||||
* argument mObj object - Modules instance.
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
var bootstrapModule = function(module, mObj, callback) {
|
||||
Log.info("Bootstrapping module: " + module.name);
|
||||
|
||||
mObj.setData(module);
|
||||
|
||||
mObj.loadScripts(function() {
|
||||
Log.log("Scripts loaded for: " + module.name);
|
||||
mObj.loadStyles(function() {
|
||||
Log.log("Styles loaded for: " + module.name);
|
||||
mObj.loadTranslations(function() {
|
||||
Log.log("Translations loaded for: " + module.name);
|
||||
moduleObjects.push(mObj);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/* loadFile(fileName)
|
||||
* Load a script or stylesheet by adding it to the dom.
|
||||
*
|
||||
* argument fileName string - Path of the file we want to load.
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
var loadFile = function(fileName, callback) {
|
||||
|
||||
var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
|
||||
|
||||
switch (extension.toLowerCase()) {
|
||||
case "js":
|
||||
Log.log("Load script: " + fileName);
|
||||
var script = document.createElement("script");
|
||||
script.type = "text/javascript";
|
||||
script.src = fileName;
|
||||
script.onload = function() {
|
||||
if (typeof callback === "function") {callback();}
|
||||
};
|
||||
script.onerror = function() {
|
||||
console.error("Error on loading script:", fileName);
|
||||
if (typeof callback === "function") {callback();}
|
||||
};
|
||||
|
||||
document.getElementsByTagName("body")[0].appendChild(script);
|
||||
break;
|
||||
case "css":
|
||||
Log.log("Load stylesheet: " + fileName);
|
||||
var stylesheet = document.createElement("link");
|
||||
stylesheet.rel = "stylesheet";
|
||||
stylesheet.type = "text/css";
|
||||
stylesheet.href = fileName;
|
||||
stylesheet.onload = function() {
|
||||
if (typeof callback === "function") {callback();}
|
||||
};
|
||||
stylesheet.onerror = function() {
|
||||
console.error("Error on loading stylesheet:", fileName);
|
||||
if (typeof callback === "function") {callback();}
|
||||
};
|
||||
|
||||
document.getElementsByTagName("head")[0].appendChild(stylesheet);
|
||||
break;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/* Public Methods */
|
||||
return {
|
||||
|
||||
/* loadModules()
|
||||
* Load all modules as defined in the config.
|
||||
*/
|
||||
loadModules: function() {
|
||||
loadModules();
|
||||
},
|
||||
|
||||
/* loadFile()
|
||||
* Load a file (script or stylesheet).
|
||||
* Prevent double loading and search for files in the vendor folder.
|
||||
*
|
||||
* argument fileName string - Path of the file we want to load.
|
||||
* argument module Module Object - the module that calls the loadFile function.
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
loadFile: function(fileName, module, callback) {
|
||||
|
||||
if (loadedFiles.indexOf(fileName.toLowerCase()) !== -1) {
|
||||
Log.log("File already loaded: " + fileName);
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileName.indexOf("http://") === 0 || fileName.indexOf("https://") === 0 || fileName.indexOf("/") !== -1) {
|
||||
// This is an absolute or relative path.
|
||||
// Load it and then return.
|
||||
loadedFiles.push(fileName.toLowerCase());
|
||||
loadFile(fileName, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
if (vendor[fileName] !== undefined) {
|
||||
// This file is available in the vendor folder.
|
||||
// Load it from this vendor folder.
|
||||
loadedFiles.push(fileName.toLowerCase());
|
||||
loadFile(config.paths.vendor + "/" + vendor[fileName], callback);
|
||||
return;
|
||||
}
|
||||
|
||||
// File not loaded yet.
|
||||
// Load it based on the module path.
|
||||
loadedFiles.push(fileName.toLowerCase());
|
||||
loadFile(module.file(fileName), callback);
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
27
js/logger.js
Normal file
27
js/logger.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/* global console */
|
||||
/* exported Log */
|
||||
|
||||
/* Magic Mirror
|
||||
* Logger
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
// This logger is very simple, but needs to be extended.
|
||||
// This system can eventually be used to push the log messages to an external target.
|
||||
|
||||
var Log = (function() {
|
||||
return {
|
||||
info: Function.prototype.bind.call(console.info, console),
|
||||
log: Function.prototype.bind.call(console.log, console),
|
||||
error: Function.prototype.bind.call(console.error, console),
|
||||
warn: Function.prototype.bind.call(console.warn, console),
|
||||
group: Function.prototype.bind.call(console.group, console),
|
||||
groupCollapsed: Function.prototype.bind.call(console.groupCollapsed, console),
|
||||
groupEnd: Function.prototype.bind.call(console.groupEnd, console),
|
||||
time: Function.prototype.bind.call(console.time, console),
|
||||
timeEnd: Function.prototype.bind.call(console.timeEnd, console),
|
||||
timeStamp: Function.prototype.bind.call(console.timeStamp, console)
|
||||
};
|
||||
})();
|
584
js/main.js
Executable file → Normal file
584
js/main.js
Executable file → Normal file
@@ -1,60 +1,534 @@
|
||||
jQuery.fn.updateWithText = function(text, speed)
|
||||
{
|
||||
var dummy = $('<div/>').html(text);
|
||||
/* global Log, Loader, Module, config, defaults */
|
||||
/* jshint -W020 */
|
||||
|
||||
if ($(this).html() != dummy.html())
|
||||
{
|
||||
$(this).fadeOut(speed/2, function() {
|
||||
$(this).html(text);
|
||||
$(this).fadeIn(speed/2, function() {
|
||||
//done
|
||||
/* Magic Mirror
|
||||
* Main System
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var MM = (function() {
|
||||
|
||||
var modules = [];
|
||||
|
||||
/* Private Methods */
|
||||
|
||||
/* createDomObjects()
|
||||
* Create dom objects for all modules that
|
||||
* are configured for a specific position.
|
||||
*/
|
||||
var createDomObjects = function() {
|
||||
for (var m in modules) {
|
||||
var module = modules[m];
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
updateWrapperStates();
|
||||
|
||||
sendNotification("DOM_OBJECTS_CREATED");
|
||||
};
|
||||
|
||||
/* selectWrapper(position)
|
||||
* Select the wrapper dom object for a specific position.
|
||||
*
|
||||
* argument position string - The name of the position.
|
||||
*/
|
||||
var selectWrapper = function(position) {
|
||||
var classes = position.replace("_"," ");
|
||||
var parentWrapper = document.getElementsByClassName(classes);
|
||||
if (parentWrapper.length > 0) {
|
||||
var wrapper = parentWrapper[0].getElementsByClassName("container");
|
||||
if (wrapper.length > 0) {
|
||||
return wrapper[0];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* sendNotification(notification, payload, sender)
|
||||
* Send a notification to all modules.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
var sendNotification = function(notification, payload, sender) {
|
||||
for (var m in modules) {
|
||||
var module = modules[m];
|
||||
if (module !== sender) {
|
||||
module.notificationReceived(notification, payload, sender);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* updateDom(module, speed)
|
||||
* Update the dom for a specific module.
|
||||
*
|
||||
* argument module Module - The module that needs an update.
|
||||
* argument speed Number - The number of microseconds for the animation. (optional)
|
||||
*/
|
||||
var updateDom = function(module, speed) {
|
||||
var newContent = module.getDom();
|
||||
var newHeader = module.getHeader();
|
||||
|
||||
if (!module.hidden) {
|
||||
|
||||
if (!moduleNeedsUpdate(module, newHeader, newContent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!speed) {
|
||||
updateModuleContent(module, newHeader, newContent);
|
||||
return;
|
||||
}
|
||||
|
||||
hideModule(module, speed / 2, function() {
|
||||
updateModuleContent(module, newHeader, newContent);
|
||||
if (!module.hidden) {
|
||||
showModule(module, speed / 2);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
updateModuleContent(module, newHeader, newContent);
|
||||
}
|
||||
};
|
||||
|
||||
/* moduleNeedsUpdate(module, newContent)
|
||||
* Check if the content has changed.
|
||||
*
|
||||
* argument module Module - The module to check.
|
||||
* argument newContent Domobject - The new content that is generated.
|
||||
*
|
||||
* return bool - Does the module need an update?
|
||||
*/
|
||||
var moduleNeedsUpdate = function(module, newHeader, newContent) {
|
||||
var moduleWrapper = document.getElementById(module.identifier);
|
||||
var contentWrapper = moduleWrapper.getElementsByClassName("module-content");
|
||||
var headerWrapper = moduleWrapper.getElementsByClassName("module-header");
|
||||
|
||||
var headerNeedsUpdate = false;
|
||||
var contentNeedsUpdate = false;
|
||||
|
||||
if (headerWrapper.length > 0) {
|
||||
headerNeedsUpdate = newHeader !== headerWrapper[0].innerHTML;
|
||||
}
|
||||
|
||||
var tempContentWrapper = document.createElement("div");
|
||||
tempContentWrapper.appendChild(newContent);
|
||||
contentNeedsUpdate = tempContentWrapper.innerHTML !== contentWrapper[0].innerHTML;
|
||||
|
||||
return headerNeedsUpdate || contentNeedsUpdate;
|
||||
};
|
||||
|
||||
/* moduleNeedsUpdate(module, newContent)
|
||||
* Update the content of a module on screen.
|
||||
*
|
||||
* argument module Module - The module to check.
|
||||
* argument newContent Domobject - The new content that is generated.
|
||||
*/
|
||||
var updateModuleContent = function(module, newHeader, newContent) {
|
||||
var moduleWrapper = document.getElementById(module.identifier);
|
||||
var headerWrapper = moduleWrapper.getElementsByClassName("module-header");
|
||||
var contentWrapper = moduleWrapper.getElementsByClassName("module-content");
|
||||
|
||||
contentWrapper[0].innerHTML = "";
|
||||
contentWrapper[0].appendChild(newContent);
|
||||
|
||||
if( headerWrapper.length > 0 && newHeader) {
|
||||
headerWrapper[0].innerHTML = newHeader;
|
||||
}
|
||||
};
|
||||
|
||||
/* hideModule(module, speed, callback)
|
||||
* Hide the module.
|
||||
*
|
||||
* argument module Module - The module to hide.
|
||||
* argument speed Number - The speed of the hide animation.
|
||||
* argument callback function - Called when the animation is done.
|
||||
*/
|
||||
var hideModule = function(module, speed, callback, options) {
|
||||
options = options || {};
|
||||
|
||||
// set lockString if set in options.
|
||||
if (options.lockString) {
|
||||
// Log.log("Has lockstring: " + options.lockString);
|
||||
if (module.lockStrings.indexOf(options.lockString) === -1) {
|
||||
module.lockStrings.push(options.lockString);
|
||||
}
|
||||
}
|
||||
|
||||
var moduleWrapper = document.getElementById(module.identifier);
|
||||
if (moduleWrapper !== null) {
|
||||
moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
|
||||
moduleWrapper.style.opacity = 0;
|
||||
|
||||
clearTimeout(module.showHideTimer);
|
||||
module.showHideTimer = setTimeout(function() {
|
||||
// To not take up any space, we just make the position absolute.
|
||||
// since it's fade out anyway, we can see it lay above or
|
||||
// below other modules. This works way better than adjusting
|
||||
// the .display property.
|
||||
moduleWrapper.style.position = "fixed";
|
||||
|
||||
updateWrapperStates();
|
||||
|
||||
if (typeof callback === "function") { callback(); }
|
||||
}, speed);
|
||||
}
|
||||
};
|
||||
|
||||
/* showModule(module, speed, callback)
|
||||
* Show the module.
|
||||
*
|
||||
* argument module Module - The module to show.
|
||||
* argument speed Number - The speed of the show animation.
|
||||
* argument callback function - Called when the animation is done.
|
||||
*/
|
||||
var showModule = function(module, speed, callback, options) {
|
||||
options = options || {};
|
||||
|
||||
// remove lockString if set in options.
|
||||
if (options.lockString) {
|
||||
var index = module.lockStrings.indexOf(options.lockString);
|
||||
if ( index !== -1) {
|
||||
module.lockStrings.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there are no more lockstrings set, or the force option is set.
|
||||
// Otherwise cancel show action.
|
||||
if (module.lockStrings.length !== 0 && options.force !== true) {
|
||||
Log.log("Will not show " + module.name + ". LockStrings active: " + module.lockStrings.join(","));
|
||||
return;
|
||||
}
|
||||
|
||||
module.hidden = false;
|
||||
|
||||
// If forced show, clean current lockstrings.
|
||||
if (module.lockStrings.length !== 0 && options.force === true) {
|
||||
Log.log("Force show of module: " + module.name);
|
||||
module.lockStrings = [];
|
||||
}
|
||||
|
||||
var moduleWrapper = document.getElementById(module.identifier);
|
||||
if (moduleWrapper !== null) {
|
||||
moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
|
||||
// Restore the postition. See hideModule() for more info.
|
||||
moduleWrapper.style.position = "static";
|
||||
|
||||
updateWrapperStates();
|
||||
|
||||
// Waiting for DOM-changes done in updateWrapperStates before we can start the animation.
|
||||
var dummy = moduleWrapper.parentElement.parentElement.offsetHeight;
|
||||
moduleWrapper.style.opacity = 1;
|
||||
|
||||
clearTimeout(module.showHideTimer);
|
||||
module.showHideTimer = setTimeout(function() {
|
||||
if (typeof callback === "function") { callback(); }
|
||||
}, speed);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/* updateWrapperStates()
|
||||
* Checks for all positions if it has visible content.
|
||||
* If not, if will hide the position to prevent unwanted margins.
|
||||
* This method schould be called by the show and hide methods.
|
||||
*
|
||||
* Example:
|
||||
* If the top_bar only contains the update notification. And no update is available,
|
||||
* the update notification is hidden. The top bar still occupies space making for
|
||||
* an ugly top margin. By using this function, the top bar will be hidden if the
|
||||
* update notification is not visible.
|
||||
*/
|
||||
|
||||
var updateWrapperStates = function() {
|
||||
var positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
|
||||
|
||||
positions.forEach(function(position) {
|
||||
var wrapper = selectWrapper(position);
|
||||
var moduleWrappers = wrapper.getElementsByClassName("module");
|
||||
|
||||
var showWrapper = false;
|
||||
Array.prototype.forEach.call(moduleWrappers, function(moduleWrapper) {
|
||||
if (moduleWrapper.style.position == "" || moduleWrapper.style.position == "static") {
|
||||
showWrapper = true;
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.style.display = showWrapper ? "block" : "none";
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/* loadConfig()
|
||||
* Loads the core config and combines it with de system defaults.
|
||||
*/
|
||||
var loadConfig = function() {
|
||||
if (typeof config === "undefined") {
|
||||
config = defaults;
|
||||
Log.error("Config file is missing! Please create a config file.");
|
||||
return;
|
||||
}
|
||||
|
||||
config = Object.assign({}, defaults, config);
|
||||
};
|
||||
|
||||
/* setSelectionMethodsForModules()
|
||||
* Adds special selectors on a collection of modules.
|
||||
*
|
||||
* argument modules array - Array of modules.
|
||||
*/
|
||||
var setSelectionMethodsForModules = function(modules) {
|
||||
|
||||
/* withClass(className)
|
||||
* 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) {
|
||||
return modulesByClass(className, true);
|
||||
};
|
||||
|
||||
/* exceptWithClass(className)
|
||||
* 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(" ");
|
||||
}
|
||||
|
||||
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 include;
|
||||
}
|
||||
}
|
||||
|
||||
return !include;
|
||||
});
|
||||
|
||||
setSelectionMethodsForModules(newModules);
|
||||
return newModules;
|
||||
};
|
||||
|
||||
/* exceptModule(module)
|
||||
* Removes a module instance from the collection.
|
||||
*
|
||||
* argument module Module object - The module instance to remove from the collection.
|
||||
*
|
||||
* return array - Filtered collection of modules.
|
||||
*/
|
||||
var exceptModule = function(module) {
|
||||
var newModules = modules.filter(function(mod) {
|
||||
return mod.identifier !== module.identifier;
|
||||
});
|
||||
|
||||
setSelectionMethodsForModules(newModules);
|
||||
return newModules;
|
||||
};
|
||||
|
||||
/* enumerate(callback)
|
||||
* Walks thru a collection of modules and executes the callback with the module as an argument.
|
||||
*
|
||||
* argument callback function - The function to execute with the module as an argument.
|
||||
*/
|
||||
var enumerate = function(callback) {
|
||||
modules.map(function(module) {
|
||||
callback(module);
|
||||
});
|
||||
};
|
||||
|
||||
if (typeof modules.withClass === "undefined") { Object.defineProperty(modules, "withClass", {value: withClass, enumerable: false}); }
|
||||
if (typeof modules.exceptWithClass === "undefined") { Object.defineProperty(modules, "exceptWithClass", {value: exceptWithClass, enumerable: false}); }
|
||||
if (typeof modules.exceptModule === "undefined") { Object.defineProperty(modules, "exceptModule", {value: exceptModule, enumerable: false}); }
|
||||
if (typeof modules.enumerate === "undefined") { Object.defineProperty(modules, "enumerate", {value: enumerate, enumerable: false}); }
|
||||
};
|
||||
|
||||
return {
|
||||
/* Public Methods */
|
||||
|
||||
/* init()
|
||||
* Main init method.
|
||||
*/
|
||||
init: function() {
|
||||
Log.info("Initializing MagicMirror.");
|
||||
loadConfig();
|
||||
Translator.loadCoreTranslations(config.language);
|
||||
Loader.loadModules();
|
||||
},
|
||||
|
||||
/* modulesStarted(moduleObjects)
|
||||
* Gets called when all modules are started.
|
||||
*
|
||||
* argument moduleObjects array<Module> - All module instances.
|
||||
*/
|
||||
modulesStarted: function(moduleObjects) {
|
||||
modules = [];
|
||||
for (var m in moduleObjects) {
|
||||
var module = moduleObjects[m];
|
||||
modules[module.data.index] = module;
|
||||
}
|
||||
|
||||
Log.info("All modules started!");
|
||||
sendNotification("ALL_MODULES_STARTED");
|
||||
|
||||
createDomObjects();
|
||||
},
|
||||
|
||||
/* sendNotification(notification, payload, sender)
|
||||
* Send a notification to all modules.
|
||||
*
|
||||
* argument notification string - The identifier of the noitication.
|
||||
* argument payload mixed - The payload of the notification.
|
||||
* argument sender Module - The module that sent the notification.
|
||||
*/
|
||||
sendNotification: function(notification, payload, sender) {
|
||||
if (arguments.length < 3) {
|
||||
Log.error("sendNotification: Missing arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof notification !== "string") {
|
||||
Log.error("sendNotification: Notification should be a string.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(sender instanceof Module)) {
|
||||
Log.error("sendNotification: Sender should be a module.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Further implementation is done in the private method.
|
||||
sendNotification(notification, payload, sender);
|
||||
},
|
||||
|
||||
/* updateDom(module, speed)
|
||||
* Update the dom for a specific module.
|
||||
*
|
||||
* argument module Module - The module that needs an update.
|
||||
* argument speed Number - The number of microseconds for the animation. (optional)
|
||||
*/
|
||||
updateDom: function(module, speed) {
|
||||
if (!(module instanceof Module)) {
|
||||
Log.error("updateDom: Sender should be a module.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Further implementation is done in the private method.
|
||||
updateDom(module, speed);
|
||||
},
|
||||
|
||||
/* getModules(module, speed)
|
||||
* Returns a collection of all modules currently active.
|
||||
*
|
||||
* return array - A collection of all modules currently active.
|
||||
*/
|
||||
getModules: function() {
|
||||
setSelectionMethodsForModules(modules);
|
||||
return modules;
|
||||
},
|
||||
|
||||
/* hideModule(module, speed, callback)
|
||||
* Hide the module.
|
||||
*
|
||||
* argument module Module - The module hide.
|
||||
* argument speed Number - The speed of the hide animation.
|
||||
* argument callback function - Called when the animation is done.
|
||||
* argument options object - Optional settings for the hide method.
|
||||
*/
|
||||
hideModule: function(module, speed, callback, options) {
|
||||
module.hidden = true;
|
||||
hideModule(module, speed, callback, options);
|
||||
},
|
||||
|
||||
/* showModule(module, speed, callback)
|
||||
* Show the module.
|
||||
*
|
||||
* argument module Module - The module show.
|
||||
* argument speed Number - The speed of the show animation.
|
||||
* argument callback function - Called when the animation is done.
|
||||
* argument options object - Optional settings for the hide method.
|
||||
*/
|
||||
showModule: function(module, speed, callback, options) {
|
||||
// do not change module.hidden yet, only if we really show it later
|
||||
showModule(module, speed, callback, options);
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
// Add polyfill for Object.assign.
|
||||
if (typeof Object.assign != "function") {
|
||||
(function() {
|
||||
Object.assign = function(target) {
|
||||
"use strict";
|
||||
if (target === undefined || target === null) {
|
||||
throw new TypeError("Cannot convert undefined or null to object");
|
||||
}
|
||||
var output = Object(target);
|
||||
for (var index = 1; index < arguments.length; index++) {
|
||||
var source = arguments[index];
|
||||
if (source !== undefined && source !== null) {
|
||||
for (var nextKey in source) {
|
||||
if (source.hasOwnProperty(nextKey)) {
|
||||
output[nextKey] = source[nextKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
})();
|
||||
}
|
||||
|
||||
jQuery.fn.outerHTML = function(s) {
|
||||
return s
|
||||
? this.before(s).remove()
|
||||
: jQuery("<p>").append(this.eq(0).clone()).html();
|
||||
};
|
||||
|
||||
function roundVal(temp)
|
||||
{
|
||||
return Math.round(temp * 10) / 10;
|
||||
}
|
||||
|
||||
jQuery(document).ready(function($) {
|
||||
|
||||
var eventList = [];
|
||||
|
||||
var lastCompliment;
|
||||
var compliment;
|
||||
|
||||
moment.locale(config.lang);
|
||||
|
||||
//connect do Xbee monitor
|
||||
// var socket = io.connect('http://rpi-alarm.local:8082');
|
||||
// socket.on('dishwasher', function (dishwasherReady) {
|
||||
// if (dishwasherReady) {
|
||||
// $('.dishwasher').fadeIn(2000);
|
||||
// $('.lower-third').fadeOut(2000);
|
||||
// } else {
|
||||
// $('.dishwasher').fadeOut(2000);
|
||||
// $('.lower-third').fadeIn(2000);
|
||||
// }
|
||||
// });
|
||||
|
||||
version.init();
|
||||
|
||||
time.init();
|
||||
|
||||
calendar.init();
|
||||
|
||||
compliments.init();
|
||||
|
||||
weather.init();
|
||||
|
||||
news.init();
|
||||
|
||||
});
|
||||
MM.init();
|
||||
|
487
js/module.js
Normal file
487
js/module.js
Normal file
@@ -0,0 +1,487 @@
|
||||
/* global Log, Class, Loader, Class , MM */
|
||||
/* exported Module */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module Blueprint.
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var Module = Class.extend({
|
||||
|
||||
/*********************************************************
|
||||
* All methods (and properties) below can be subclassed. *
|
||||
*********************************************************/
|
||||
|
||||
// Set the minimum MagicMirror module version for this module.
|
||||
requiresVersion: "2.0.0",
|
||||
|
||||
// Module config defaults.
|
||||
defaults: {},
|
||||
|
||||
// Timer reference used for showHide animation callbacks.
|
||||
showHideTimer: null,
|
||||
|
||||
// Array to store lockStrings. These strings are used to lock
|
||||
// visibility when hiding and showing module.
|
||||
lockStrings: [],
|
||||
|
||||
// Storage of the nunjuck Environment,
|
||||
// This should not be referenced directly.
|
||||
// Use the nunjucksEnvironment() to get it.
|
||||
_nunjucksEnvironment: null,
|
||||
|
||||
/* init()
|
||||
* Is called when the module is instantiated.
|
||||
*/
|
||||
init: function () {
|
||||
//Log.log(this.defaults);
|
||||
},
|
||||
|
||||
/* start()
|
||||
* Is called when the module is started.
|
||||
*/
|
||||
start: function () {
|
||||
Log.info("Starting module: " + this.name);
|
||||
},
|
||||
|
||||
/* getScripts()
|
||||
* Returns a list of scripts the module requires to be loaded.
|
||||
*
|
||||
* return Array<String> - An array with filenames.
|
||||
*/
|
||||
getScripts: function () {
|
||||
return [];
|
||||
},
|
||||
|
||||
/* getStyles()
|
||||
* Returns a list of stylesheets the module requires to be loaded.
|
||||
*
|
||||
* return Array<String> - An array with filenames.
|
||||
*/
|
||||
getStyles: function () {
|
||||
return [];
|
||||
},
|
||||
|
||||
/* getTranslations()
|
||||
* Returns a map of translation files the module requires to be loaded.
|
||||
*
|
||||
* return Map<String, String> - A map with langKeys and filenames.
|
||||
*/
|
||||
getTranslations: function () {
|
||||
return false;
|
||||
},
|
||||
|
||||
/* getDom()
|
||||
* This method generates the dom which needs to be displayed. This method is called by the Magic Mirror core.
|
||||
* This method 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.
|
||||
*/
|
||||
getDom: function () {
|
||||
var div = document.createElement("div");
|
||||
var template = this.getTemplate();
|
||||
var templateData = this.getTemplateData();
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// The inner content of the div will be set after the template is received.
|
||||
// This isn't the most optimal way, but since it's near instant
|
||||
// it probably won't be an issue.
|
||||
// If it gives problems, we can always add a way to pre fetch the templates.
|
||||
// Let's not over optimise this ... KISS! :)
|
||||
div.innerHTML = res;
|
||||
});
|
||||
} else {
|
||||
// the template is a template string.
|
||||
div.innerHTML = this.nunjucksEnvironment().renderString(template, templateData);
|
||||
}
|
||||
|
||||
return div;
|
||||
},
|
||||
|
||||
/* getHeader()
|
||||
* This method generates the header string which needs to be displayed if a user has a header configured for this module.
|
||||
* This method is called by the Magic Mirror core, but only if the user has configured a default header for the module.
|
||||
* This method needs to be subclassed if the module wants to display modified headers on the mirror.
|
||||
*
|
||||
* return string - The header to display above the header.
|
||||
*/
|
||||
getHeader: function () {
|
||||
return this.data.header;
|
||||
},
|
||||
|
||||
/* getTemplate()
|
||||
* This method returns the template for the module which is used by the default getDom implementation.
|
||||
* This method needs to be subclassed if the module wants to use a 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.
|
||||
*
|
||||
* argument notification string - The identifier of the notification.
|
||||
* argument payload mixed - The payload of the notification.
|
||||
* argument sender Module - The module that sent the notification.
|
||||
*/
|
||||
notificationReceived: function (notification, payload, sender) {
|
||||
if (sender) {
|
||||
Log.log(this.name + " received a module notification: " + notification + " from sender: " + sender.name);
|
||||
} else {
|
||||
Log.log(this.name + " received a system notification: " + notification);
|
||||
}
|
||||
},
|
||||
|
||||
/** nunjucksEnvironment()
|
||||
* Returns the nunjucks environment for the current module.
|
||||
* The environment is checked in the _nunjucksEnvironment instance variable.
|
||||
|
||||
* @returns Nunjucks Environment
|
||||
*/
|
||||
nunjucksEnvironment: function() {
|
||||
if (this._nunjucksEnvironment != null) {
|
||||
return this._nunjucksEnvironment;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
this._nunjucksEnvironment = new nunjucks.Environment(new nunjucks.WebLoader(this.file(""), {async: true}), {
|
||||
trimBlocks: true,
|
||||
lstripBlocks: true
|
||||
});
|
||||
this._nunjucksEnvironment.addFilter("translate", function(str) {
|
||||
return self.translate(str)
|
||||
});
|
||||
|
||||
return this._nunjucksEnvironment;
|
||||
},
|
||||
|
||||
/* socketNotificationReceived(notification, payload)
|
||||
* This method is called when a socket notification arrives.
|
||||
*
|
||||
* argument notification string - The identifier of the notification.
|
||||
* argument payload mixed - The payload of the notification.
|
||||
*/
|
||||
socketNotificationReceived: function (notification, payload) {
|
||||
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
|
||||
},
|
||||
|
||||
/* suspend()
|
||||
* This method is called when a module is hidden.
|
||||
*/
|
||||
suspend: function () {
|
||||
Log.log(this.name + " is suspended.");
|
||||
},
|
||||
|
||||
/* resume()
|
||||
* This method is called when a module is shown.
|
||||
*/
|
||||
resume: function () {
|
||||
Log.log(this.name + " is resumed.");
|
||||
},
|
||||
|
||||
/*********************************************
|
||||
* The methods below don"t need subclassing. *
|
||||
*********************************************/
|
||||
|
||||
/* setData(data)
|
||||
* Set the module data.
|
||||
*
|
||||
* argument data obejct - Module data.
|
||||
*/
|
||||
setData: function (data) {
|
||||
this.data = data;
|
||||
this.name = data.name;
|
||||
this.identifier = data.identifier;
|
||||
this.hidden = false;
|
||||
|
||||
this.setConfig(data.config);
|
||||
},
|
||||
|
||||
/* setConfig(config)
|
||||
* Set the module config and combine it with the module defaults.
|
||||
*
|
||||
* argument config obejct - Module config.
|
||||
*/
|
||||
setConfig: function (config) {
|
||||
this.config = Object.assign({}, this.defaults, config);
|
||||
},
|
||||
|
||||
/* socket()
|
||||
* Returns a socket object. If it doesn"t exist, it"s created.
|
||||
* It also registers the notification callback.
|
||||
*/
|
||||
socket: function () {
|
||||
if (typeof this._socket === "undefined") {
|
||||
this._socket = this._socket = new MMSocket(this.name);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
this._socket.setNotificationCallback(function (notification, payload) {
|
||||
self.socketNotificationReceived(notification, payload);
|
||||
});
|
||||
|
||||
return this._socket;
|
||||
},
|
||||
|
||||
/* file(file)
|
||||
* Retrieve the path to a module file.
|
||||
*
|
||||
* argument file string - Filename.
|
||||
*
|
||||
* return string - File path.
|
||||
*/
|
||||
file: function (file) {
|
||||
return (this.data.path + "/" + file).replace("//", "/");
|
||||
},
|
||||
|
||||
/* loadStyles()
|
||||
* Load all required stylesheets by requesting the MM object to load the files.
|
||||
*
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
loadStyles: function (callback) {
|
||||
this.loadDependencies("getStyles", callback);
|
||||
},
|
||||
|
||||
/* loadScripts()
|
||||
* Load all required scripts by requesting the MM object to load the files.
|
||||
*
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
loadScripts: function (callback) {
|
||||
this.loadDependencies("getScripts", callback);
|
||||
},
|
||||
|
||||
/* 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();
|
||||
}
|
||||
};
|
||||
|
||||
loadNextDependency();
|
||||
},
|
||||
|
||||
/* loadScripts()
|
||||
* Load all required scripts by requesting the MM object to load the files.
|
||||
*
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
loadTranslations: function (callback) {
|
||||
var self = this;
|
||||
var translations = this.getTranslations();
|
||||
var lang = config.language.toLowerCase();
|
||||
|
||||
// The variable `first` will contain the first
|
||||
// defined translation after the following line.
|
||||
for (var first in translations) { break; }
|
||||
|
||||
if (translations) {
|
||||
var translationFile = translations[lang] || undefined;
|
||||
var translationsFallbackFile = translations[first];
|
||||
|
||||
// If a translation file is set, load it and then also load the fallback translation file.
|
||||
// Otherwise only load the fallback translation file.
|
||||
if (translationFile !== undefined && translationFile !== translationsFallbackFile) {
|
||||
Translator.load(self, translationFile, false, function () {
|
||||
Translator.load(self, translationsFallbackFile, true, callback);
|
||||
});
|
||||
} else {
|
||||
Translator.load(self, translationsFallbackFile, true, callback);
|
||||
}
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
|
||||
/* translate(key, defaultValueOrVariables, defaultValue)
|
||||
* Request the translation for a given key with optional variables and default value.
|
||||
*
|
||||
* argument key string - The key of the string to translate
|
||||
* argument defaultValueOrVariables string/object - The default value or variables for translating. (Optional)
|
||||
* argument defaultValue string - The default value with variables. (Optional)
|
||||
*/
|
||||
translate: function (key, defaultValueOrVariables, defaultValue) {
|
||||
if(typeof defaultValueOrVariables === "object") {
|
||||
return Translator.translate(this, key, defaultValueOrVariables) || defaultValue || "";
|
||||
}
|
||||
return Translator.translate(this, key) || defaultValueOrVariables || "";
|
||||
},
|
||||
|
||||
/* updateDom(speed)
|
||||
* Request an (animated) update of the module.
|
||||
*
|
||||
* argument speed Number - The speed of the animation. (Optional)
|
||||
*/
|
||||
updateDom: function (speed) {
|
||||
MM.updateDom(this, speed);
|
||||
},
|
||||
|
||||
/* sendNotification(notification, payload)
|
||||
* Send a notification to all modules.
|
||||
*
|
||||
* argument notification string - The identifier of the notification.
|
||||
* argument payload mixed - The payload of the notification.
|
||||
*/
|
||||
sendNotification: function (notification, payload) {
|
||||
MM.sendNotification(notification, payload, this);
|
||||
},
|
||||
|
||||
/* sendSocketNotification(notification, payload)
|
||||
* Send a socket notification to the node helper.
|
||||
*
|
||||
* argument notification string - The identifier of the notification.
|
||||
* argument payload mixed - The payload of the notification.
|
||||
*/
|
||||
sendSocketNotification: function (notification, payload) {
|
||||
this.socket().sendNotification(notification, payload);
|
||||
},
|
||||
|
||||
/* hideModule(module, speed, callback)
|
||||
* Hide this module.
|
||||
*
|
||||
* argument speed Number - The speed of the hide animation.
|
||||
* argument callback function - Called when the animation is done.
|
||||
* argument options object - Optional settings for the hide method.
|
||||
*/
|
||||
hide: function (speed, callback, options) {
|
||||
if (typeof callback === "object") {
|
||||
options = callback;
|
||||
callback = function () { };
|
||||
}
|
||||
|
||||
callback = callback || function () { };
|
||||
options = options || {};
|
||||
|
||||
var self = this;
|
||||
MM.hideModule(self, speed, function () {
|
||||
self.suspend();
|
||||
callback();
|
||||
}, options);
|
||||
},
|
||||
|
||||
/* showModule(module, speed, callback)
|
||||
* Show this module.
|
||||
*
|
||||
* argument speed Number - The speed of the show animation.
|
||||
* argument callback function - Called when the animation is done.
|
||||
* argument options object - Optional settings for the hide method.
|
||||
*/
|
||||
show: function (speed, callback, options) {
|
||||
if (typeof callback === "object") {
|
||||
options = callback;
|
||||
callback = function () { };
|
||||
}
|
||||
|
||||
callback = callback || function () { };
|
||||
options = options || {};
|
||||
|
||||
this.resume();
|
||||
MM.showModule(this, speed, callback, options);
|
||||
}
|
||||
});
|
||||
|
||||
Module.definitions = {};
|
||||
|
||||
Module.create = function (name) {
|
||||
|
||||
// Make sure module definition is available.
|
||||
if (!Module.definitions[name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var moduleDefinition = Module.definitions[name];
|
||||
var clonedDefinition = cloneObject(moduleDefinition);
|
||||
|
||||
// Note that we clone the definition. Otherwise the objects are shared, which gives problems.
|
||||
var ModuleClass = Module.extend(clonedDefinition);
|
||||
|
||||
return new ModuleClass();
|
||||
|
||||
};
|
||||
|
||||
/* cmpVersions(a,b)
|
||||
* Compare two symantic version numbers and return the difference.
|
||||
*
|
||||
* argument a string - Version number a.
|
||||
* argument a string - Version number b.
|
||||
*/
|
||||
function cmpVersions(a, b) {
|
||||
var i, diff;
|
||||
var regExStrip0 = /(\.0+)+$/;
|
||||
var segmentsA = a.replace(regExStrip0, "").split(".");
|
||||
var segmentsB = b.replace(regExStrip0, "").split(".");
|
||||
var l = Math.min(segmentsA.length, segmentsB.length);
|
||||
|
||||
for (i = 0; i < l; i++) {
|
||||
diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
|
||||
if (diff) {
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
return segmentsA.length - segmentsB.length;
|
||||
}
|
||||
|
||||
Module.register = function (name, moduleDefinition) {
|
||||
|
||||
if (moduleDefinition.requiresVersion) {
|
||||
Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + version);
|
||||
if (cmpVersions(version, moduleDefinition.requiresVersion) >= 0) {
|
||||
Log.log("Version is ok!");
|
||||
} else {
|
||||
Log.log("Version is incorrect. Skip module: '" + name + "'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
Log.log("Module registered: " + name);
|
||||
Module.definitions[name] = moduleDefinition;
|
||||
};
|
||||
|
||||
if (typeof exports != "undefined") { // For testing purpose only
|
||||
// A good a idea move the function cmpversions a helper file.
|
||||
// It's used into other side.
|
||||
exports._test = {
|
||||
cmpVersions: cmpVersions
|
||||
}
|
||||
}
|
80
js/moment-with-locales.min.js
vendored
80
js/moment-with-locales.min.js
vendored
File diff suppressed because one or more lines are too long
152
js/news/news.js
152
js/news/news.js
@@ -1,152 +0,0 @@
|
||||
// A lot of this code is from the original feedToJson function that was included with this project
|
||||
// The new code allows for multiple feeds to be used but a bunch of variables and such have literally been copied and pasted into this code and some help from here: http://jsfiddle.net/BDK46/
|
||||
// The original version can be found here: http://airshp.com/2011/jquery-plugin-feed-to-json/
|
||||
var news = {
|
||||
feed: config.news.feed || null,
|
||||
newsLocation: '.news',
|
||||
newsItems: [],
|
||||
seenNewsItem: [],
|
||||
_yqURL: 'https://query.yahooapis.com/v1/public/yql',
|
||||
_yqlQS: '?format=json&q=select%20*%20from%20rss%20where%20url%3D',
|
||||
_cacheBuster: Math.floor((new Date().getTime()) / 1200 / 1000),
|
||||
_failedAttempts: 0,
|
||||
fetchInterval: config.news.fetchInterval || 60000,
|
||||
updateInterval: config.news.interval || 5500,
|
||||
fadeInterval: 2000,
|
||||
intervalId: null,
|
||||
fetchNewsIntervalId: null
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the query string that will be used to grab a converted RSS feed into a JSON object via Yahoo
|
||||
* @param {string} feed The original location of the RSS feed
|
||||
* @return {string} The new location of the RSS feed provided by Yahoo
|
||||
*/
|
||||
news.buildQueryString = function (feed) {
|
||||
|
||||
return this._yqURL + this._yqlQS + '\'' + encodeURIComponent(feed) + '\'';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the news for each feed provided in the config file
|
||||
*/
|
||||
news.fetchNews = function () {
|
||||
|
||||
// Reset the news feed
|
||||
this.newsItems = [];
|
||||
|
||||
this.feed.forEach(function (_curr) {
|
||||
|
||||
var _yqUrlString = this.buildQueryString(_curr);
|
||||
this.fetchFeed(_yqUrlString);
|
||||
|
||||
}.bind(this));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a GET request to Yahoo's service
|
||||
* @param {string} yqUrl The URL being used to grab the RSS feed (in JSON format)
|
||||
*/
|
||||
news.fetchFeed = function (yqUrl) {
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
datatype:'jsonp',
|
||||
url: yqUrl,
|
||||
success: function (data) {
|
||||
|
||||
if (data.query.count > 0) {
|
||||
this.parseFeed(data.query.results.item);
|
||||
} else {
|
||||
console.error('No feed results for: ' + yqUrl);
|
||||
}
|
||||
|
||||
}.bind(this),
|
||||
error: function () {
|
||||
// non-specific error message that should be updated
|
||||
console.error('No feed results for: ' + yqUrl);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses each item in a single news feed
|
||||
* @param {Object} data The news feed that was returned by Yahoo
|
||||
* @return {boolean} Confirms that the feed was parsed correctly
|
||||
*/
|
||||
news.parseFeed = function (data) {
|
||||
|
||||
var _rssItems = [];
|
||||
|
||||
for (var i = 0, count = data.length; i < count; i++) {
|
||||
|
||||
_rssItems.push(data[i].title);
|
||||
|
||||
}
|
||||
|
||||
this.newsItems = this.newsItems.concat(_rssItems);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through each available and unseen news feed after it has been retrieved from Yahoo and shows it on the screen
|
||||
* When all news titles have been exhausted, the list resets and randomly chooses from the original set of items
|
||||
* @return {boolean} Confirms that there is a list of news items to loop through and that one has been shown on the screen
|
||||
*/
|
||||
news.showNews = function () {
|
||||
|
||||
// If all items have been seen, swap seen to unseen
|
||||
if (this.newsItems.length === 0 && this.seenNewsItem.length !== 0) {
|
||||
|
||||
if (this._failedAttempts === 20) {
|
||||
console.error('Failed to show a news story 20 times, stopping any attempts');
|
||||
return false;
|
||||
}
|
||||
|
||||
this._failedAttempts++;
|
||||
|
||||
setTimeout(function () {
|
||||
this.showNews();
|
||||
}.bind(this), 3000);
|
||||
|
||||
} else if (this.newsItems.length === 0 && this.seenNewsItem.length !== 0) {
|
||||
this.newsItems = this.seenNewsItem.splice(0);
|
||||
}
|
||||
|
||||
var _location = Math.floor(Math.random() * this.newsItems.length);
|
||||
|
||||
var _item = news.newsItems.splice(_location, 1)[0];
|
||||
|
||||
this.seenNewsItem.push(_item);
|
||||
|
||||
$(this.newsLocation).updateWithText(_item, this.fadeInterval);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
news.init = function () {
|
||||
|
||||
if (this.feed === null || (this.feed instanceof Array === false && typeof this.feed !== 'string')) {
|
||||
return false;
|
||||
} else if (typeof this.feed === 'string') {
|
||||
this.feed = [this.feed];
|
||||
}
|
||||
|
||||
this.fetchNews();
|
||||
this.showNews();
|
||||
|
||||
this.fetchNewsIntervalId = setInterval(function () {
|
||||
this.fetchNews()
|
||||
}.bind(this), this.fetchInterval)
|
||||
|
||||
this.intervalId = setInterval(function () {
|
||||
this.showNews();
|
||||
}.bind(this), this.updateInterval);
|
||||
|
||||
}
|
1910
js/rrule.js
1910
js/rrule.js
File diff suppressed because it is too large
Load Diff
78
js/server.js
Normal file
78
js/server.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/* Magic Mirror
|
||||
* Server
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var express = require("express");
|
||||
var app = require("express")();
|
||||
var server = require("http").Server(app);
|
||||
var io = require("socket.io")(server);
|
||||
var path = require("path");
|
||||
var ipfilter = require("express-ipfilter").IpFilter;
|
||||
var fs = require("fs");
|
||||
var helmet = require("helmet");
|
||||
var Utils = require(__dirname + "/utils.js");
|
||||
|
||||
var Server = function(config, callback) {
|
||||
|
||||
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: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false})(req, res, function(err) {
|
||||
if (err === undefined) {
|
||||
return next();
|
||||
}
|
||||
console.log(err.message);
|
||||
res.status(403).send("This device is not allowed to access your mirror. <br> Please check your config.js or config.js.sample to change this.");
|
||||
});
|
||||
});
|
||||
app.use(helmet());
|
||||
|
||||
app.use("/js", express.static(__dirname));
|
||||
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);
|
||||
});
|
||||
|
||||
if (typeof callback === "function") {
|
||||
callback(app, io);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Server;
|
2
js/socket.io.min.js
vendored
2
js/socket.io.min.js
vendored
File diff suppressed because one or more lines are too long
35
js/socket.js
Normal file
35
js/socket.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/* exported Log */
|
||||
|
||||
/* Magic Mirror
|
||||
* Socket Connection
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var MMSocket = function(moduleName) {
|
||||
|
||||
var self = this;
|
||||
|
||||
if (typeof moduleName !== "string") {
|
||||
throw new Error("Please set the module name for the MMSocket.");
|
||||
}
|
||||
|
||||
self.moduleName = moduleName;
|
||||
|
||||
self.socket = io("http://localhost:8080");
|
||||
self.socket.on("notification", function(data) {
|
||||
MM.sendNotification(data.notification, data.payload, Socket);
|
||||
});
|
||||
|
||||
return {
|
||||
sendMessage: function(notification, payload, sender) {
|
||||
Log.log("Send socket message: " + notification);
|
||||
self.socket.emit("notification", {
|
||||
notification: notification,
|
||||
sender: sender,
|
||||
payload: payload
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
40
js/socketclient.js
Normal file
40
js/socketclient.js
Normal file
@@ -0,0 +1,40 @@
|
||||
var MMSocket = function(moduleName) {
|
||||
var self = this;
|
||||
|
||||
if (typeof moduleName !== "string") {
|
||||
throw new Error("Please set the module name for the MMSocket.");
|
||||
}
|
||||
|
||||
self.moduleName = moduleName;
|
||||
|
||||
// Private Methods
|
||||
self.socket = io("/" + self.moduleName);
|
||||
var notificationCallback = function() {};
|
||||
|
||||
var onevent = self.socket.onevent;
|
||||
self.socket.onevent = function(packet) {
|
||||
var args = packet.data || [];
|
||||
onevent.call(this, packet); // original call
|
||||
packet.data = ["*"].concat(args);
|
||||
onevent.call(this, packet); // additional call to catch-all
|
||||
};
|
||||
|
||||
// register catch all.
|
||||
self.socket.on("*", function(notification, payload) {
|
||||
if (notification !== "*") {
|
||||
notificationCallback(notification, payload);
|
||||
}
|
||||
});
|
||||
|
||||
// Public Methods
|
||||
this.setNotificationCallback = function(callback) {
|
||||
notificationCallback = callback;
|
||||
};
|
||||
|
||||
this.sendNotification = function(notification, payload) {
|
||||
if (typeof payload === "undefined") {
|
||||
payload = {};
|
||||
}
|
||||
self.socket.emit(notification, payload);
|
||||
};
|
||||
};
|
@@ -1,34 +0,0 @@
|
||||
var time = {
|
||||
timeFormat: config.time.timeFormat || 24,
|
||||
dateLocation: '.date',
|
||||
timeLocation: '.time',
|
||||
updateInterval: 1000,
|
||||
intervalId: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the time that is shown on the screen
|
||||
*/
|
||||
time.updateTime = function () {
|
||||
|
||||
var _now = moment(),
|
||||
_date = _now.format('dddd, LL');
|
||||
|
||||
$(this.dateLocation).html(_date);
|
||||
$(this.timeLocation).html(_now.format(this._timeFormat+':mm[<span class="sec">]ss[</span>]'));
|
||||
|
||||
}
|
||||
|
||||
time.init = function () {
|
||||
|
||||
if (parseInt(time.timeFormat) === 12) {
|
||||
time._timeFormat = 'hh'
|
||||
} else {
|
||||
time._timeFormat = 'HH';
|
||||
}
|
||||
|
||||
this.intervalId = setInterval(function () {
|
||||
this.updateTime();
|
||||
}.bind(this), 1000);
|
||||
|
||||
}
|
225
js/translator.js
Normal file
225
js/translator.js
Normal file
@@ -0,0 +1,225 @@
|
||||
/* exported Translator */
|
||||
/* Magic Mirror
|
||||
* Translator (l10n)
|
||||
*
|
||||
* By Christopher Fenner http://github.com/CFenner
|
||||
* MIT Licensed.
|
||||
*/
|
||||
var Translator = (function() {
|
||||
|
||||
/* loadJSON(file, callback)
|
||||
* Load a JSON file via XHR.
|
||||
*
|
||||
* argument file string - Path of the file we want to load.
|
||||
* argument callback function - Function called when done.
|
||||
*/
|
||||
function loadJSON(file, callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.overrideMimeType("application/json");
|
||||
xhr.open("GET", file, true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState == 4 && xhr.status == "200") {
|
||||
callback(JSON.parse(stripComments(xhr.responseText)));
|
||||
}
|
||||
};
|
||||
xhr.send(null);
|
||||
}
|
||||
|
||||
/* loadJSON(str, options)
|
||||
* Remove any commenting from a json file so it can be parsed.
|
||||
*
|
||||
* argument str string - The string that contains json with comments.
|
||||
* argument opts function - Strip options.
|
||||
*
|
||||
* return the stripped string.
|
||||
*/
|
||||
function stripComments(str, opts) {
|
||||
// strip comments copied from: https://github.com/sindresorhus/strip-json-comments
|
||||
|
||||
var singleComment = 1;
|
||||
var multiComment = 2;
|
||||
|
||||
function stripWithoutWhitespace() {
|
||||
return "";
|
||||
}
|
||||
|
||||
function stripWithWhitespace(str, start, end) {
|
||||
return str.slice(start, end).replace(/\S/g, " ");
|
||||
}
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
var currentChar;
|
||||
var nextChar;
|
||||
var insideString = false;
|
||||
var insideComment = false;
|
||||
var offset = 0;
|
||||
var ret = "";
|
||||
var strip = opts.whitespace === false ? stripWithoutWhitespace : stripWithWhitespace;
|
||||
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
currentChar = str[i];
|
||||
nextChar = str[i + 1];
|
||||
|
||||
if (!insideComment && currentChar === "\"") {
|
||||
var escaped = str[i - 1] === "\\" && str[i - 2] !== "\\";
|
||||
if (!escaped) {
|
||||
insideString = !insideString;
|
||||
}
|
||||
}
|
||||
|
||||
if (insideString) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!insideComment && currentChar + nextChar === "//") {
|
||||
ret += str.slice(offset, i);
|
||||
offset = i;
|
||||
insideComment = singleComment;
|
||||
i++;
|
||||
} else if (insideComment === singleComment && currentChar + nextChar === "\r\n") {
|
||||
i++;
|
||||
insideComment = false;
|
||||
ret += strip(str, offset, i);
|
||||
offset = i;
|
||||
continue;
|
||||
} else if (insideComment === singleComment && currentChar === "\n") {
|
||||
insideComment = false;
|
||||
ret += strip(str, offset, i);
|
||||
offset = i;
|
||||
} else if (!insideComment && currentChar + nextChar === "/*") {
|
||||
ret += str.slice(offset, i);
|
||||
offset = i;
|
||||
insideComment = multiComment;
|
||||
i++;
|
||||
continue;
|
||||
} else if (insideComment === multiComment && currentChar + nextChar === "*/") {
|
||||
i++;
|
||||
insideComment = false;
|
||||
ret += strip(str, offset, i + 1);
|
||||
offset = i + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return ret + (insideComment ? strip(str.substr(offset)) : str.substr(offset));
|
||||
}
|
||||
|
||||
return {
|
||||
coreTranslations: {},
|
||||
coreTranslationsFallback: {},
|
||||
translations: {},
|
||||
translationsFallback: {},
|
||||
|
||||
/* 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, 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(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 createStringFromTemplate(this.translations[module.name][key], variables);
|
||||
}
|
||||
|
||||
if (key in this.coreTranslations) {
|
||||
// Log.log("Got translation for " + key + " from core translation.");
|
||||
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 createStringFromTemplate(this.translationsFallback[module.name][key], variables);
|
||||
}
|
||||
|
||||
if (key in this.coreTranslationsFallback) {
|
||||
// Log.log("Got translation for " + key + " from core translation fallback.");
|
||||
return createStringFromTemplate(this.coreTranslationsFallback[key], variables);
|
||||
}
|
||||
|
||||
return key;
|
||||
},
|
||||
/* load(module, file, 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 callback function - Function called when done.
|
||||
*/
|
||||
load: function(module, file, isFallback, callback) {
|
||||
if (!isFallback) {
|
||||
Log.log(module.name + " - Load translation: " + file);
|
||||
} else {
|
||||
Log.log(module.name + " - Load translation fallback: " + file);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
if(!this.translationsFallback[module.name]) {
|
||||
loadJSON(module.file(file), function(json) {
|
||||
if (!isFallback) {
|
||||
self.translations[module.name] = json;
|
||||
} else {
|
||||
self.translationsFallback[module.name] = json;
|
||||
}
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
|
||||
/* loadCoreTranslations(lang)
|
||||
* Load the core translations.
|
||||
*
|
||||
* argument lang String - The language identifier of the core language.
|
||||
*/
|
||||
loadCoreTranslations: function(lang) {
|
||||
var self = this;
|
||||
|
||||
if (lang in translations) {
|
||||
Log.log("Loading core translation file: " + translations[lang]);
|
||||
loadJSON(translations[lang], function(translations) {
|
||||
self.coreTranslations = translations;
|
||||
});
|
||||
} else {
|
||||
Log.log("Configured language not found in core translations.");
|
||||
}
|
||||
|
||||
self.loadCoreTranslationsFallback();
|
||||
},
|
||||
|
||||
/* loadCoreTranslationsFallback()
|
||||
* Load the core translations fallback.
|
||||
* The first language defined in translations.js will be used.
|
||||
*/
|
||||
loadCoreTranslationsFallback: function() {
|
||||
var self = this;
|
||||
|
||||
// The variable `first` will contain the first
|
||||
// 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;
|
||||
});
|
||||
},
|
||||
};
|
||||
})();
|
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;}
|
@@ -1,34 +0,0 @@
|
||||
var version = {
|
||||
updateInterval: 600000,
|
||||
intervalId: null
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the version and refreshes the page if a new version has been pulled
|
||||
*/
|
||||
version.checkVersion = function () {
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: 'controllers/hash.php',
|
||||
success: function (data) {
|
||||
// The githash variable is located in index.php
|
||||
if (data && data.gitHash !== gitHash) {
|
||||
window.location.reload();
|
||||
window.location.href = window.location.href;
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
version.init = function () {
|
||||
|
||||
this.intervalId = setInterval(function () {
|
||||
this.checkVersion();
|
||||
}.bind(this), this.updateInterval);
|
||||
|
||||
}
|
@@ -1,169 +0,0 @@
|
||||
var weather = {
|
||||
// Default language is Dutch because that is what the original author used
|
||||
lang: config.lang || 'nl',
|
||||
params: config.weather.params || null,
|
||||
iconTable: {
|
||||
'01d':'wi-day-sunny',
|
||||
'02d':'wi-day-cloudy',
|
||||
'03d':'wi-cloudy',
|
||||
'04d':'wi-cloudy-windy',
|
||||
'09d':'wi-showers',
|
||||
'10d':'wi-rain',
|
||||
'11d':'wi-thunderstorm',
|
||||
'13d':'wi-snow',
|
||||
'50d':'wi-fog',
|
||||
'01n':'wi-night-clear',
|
||||
'02n':'wi-night-cloudy',
|
||||
'03n':'wi-night-cloudy',
|
||||
'04n':'wi-night-cloudy',
|
||||
'09n':'wi-night-showers',
|
||||
'10n':'wi-night-rain',
|
||||
'11n':'wi-night-thunderstorm',
|
||||
'13n':'wi-night-snow',
|
||||
'50n':'wi-night-alt-cloudy-windy'
|
||||
},
|
||||
temperatureLocation: '.temp',
|
||||
windSunLocation: '.windsun',
|
||||
forecastLocation: '.forecast',
|
||||
apiVersion: '2.5',
|
||||
apiBase: 'http://api.openweathermap.org/data/',
|
||||
weatherEndpoint: 'weather',
|
||||
forecastEndpoint: 'forecast/daily',
|
||||
updateInterval: config.weather.interval || 6000,
|
||||
fadeInterval: config.weather.fadeInterval || 1000,
|
||||
intervalId: null
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds a float to one decimal place
|
||||
* @param {float} temperature The temperature to be rounded
|
||||
* @return {float} The new floating point value
|
||||
*/
|
||||
weather.roundValue = function (temperature) {
|
||||
return parseFloat(temperature).toFixed(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the wind speed (km/h) into the values given by the Beaufort Wind Scale
|
||||
* @see http://www.spc.noaa.gov/faq/tornado/beaufort.html
|
||||
* @param {int} kmh The wind speed in Kilometers Per Hour
|
||||
* @return {int} The wind speed converted into its corresponding Beaufort number
|
||||
*/
|
||||
weather.ms2Beaufort = function(ms) {
|
||||
var kmh = ms * 60 * 60 / 1000;
|
||||
var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
|
||||
for (var beaufort in speeds) {
|
||||
var speed = speeds[beaufort];
|
||||
if (speed > kmh) {
|
||||
return beaufort;
|
||||
}
|
||||
}
|
||||
return 12;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current temperature and weather patter from the OpenWeatherMap API
|
||||
*/
|
||||
weather.updateCurrentWeather = function () {
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: weather.apiBase + '/' + weather.apiVersion + '/' + weather.weatherEndpoint,
|
||||
dataType: 'json',
|
||||
data: weather.params,
|
||||
success: function (data) {
|
||||
|
||||
var _temperature = this.roundValue(data.main.temp),
|
||||
_temperatureMin = this.roundValue(data.main.temp_min),
|
||||
_temperatureMax = this.roundValue(data.main.temp_max),
|
||||
_wind = this.roundValue(data.wind.speed),
|
||||
_iconClass = this.iconTable[data.weather[0].icon];
|
||||
|
||||
var _icon = '<span class="icon ' + _iconClass + ' dimmed wi"></span>';
|
||||
|
||||
var _newTempHtml = _icon + '' + _temperature + '°';
|
||||
|
||||
$(this.temperatureLocation).updateWithText(_newTempHtml, this.fadeInterval);
|
||||
|
||||
var _now = moment().format('HH:mm'),
|
||||
_sunrise = moment(data.sys.sunrise*1000).format('HH:mm'),
|
||||
_sunset = moment(data.sys.sunset*1000).format('HH:mm');
|
||||
|
||||
var _newWindHtml = '<span class="wi wi-strong-wind xdimmed"></span> ' + this.ms2Beaufort(_wind),
|
||||
_newSunHtml = '<span class="wi wi-sunrise xdimmed"></span> ' + _sunrise;
|
||||
|
||||
if (_sunrise < _now && _sunset > _now) {
|
||||
_newSunHtml = '<span class="wi wi-sunset xdimmed"></span> ' + _sunset;
|
||||
}
|
||||
|
||||
$(this.windSunLocation).updateWithText(_newWindHtml + ' ' + _newSunHtml, this.fadeInterval);
|
||||
|
||||
}.bind(this),
|
||||
error: function () {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the 5 Day Forecast from the OpenWeatherMap API
|
||||
*/
|
||||
weather.updateWeatherForecast = function () {
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: weather.apiBase + '/' + weather.apiVersion + '/' + weather.forecastEndpoint,
|
||||
data: weather.params,
|
||||
success: function (data) {
|
||||
|
||||
var _opacity = 1,
|
||||
_forecastHtml = '';
|
||||
|
||||
_forecastHtml += '<table class="forecast-table">';
|
||||
|
||||
for (var i = 0, count = data.list.length; i < count; i++) {
|
||||
|
||||
var _forecast = data.list[i];
|
||||
|
||||
_forecastHtml += '<tr style="opacity:' + _opacity + '">';
|
||||
|
||||
_forecastHtml += '<td class="day">' + moment(_forecast.dt, 'X').format('ddd') + '</td>';
|
||||
_forecastHtml += '<td class="icon-small ' + this.iconTable[_forecast.weather[0].icon] + '"></td>';
|
||||
_forecastHtml += '<td class="temp-max">' + this.roundValue(_forecast.temp.max) + '</td>';
|
||||
_forecastHtml += '<td class="temp-min">' + this.roundValue(_forecast.temp.min) + '</td>';
|
||||
|
||||
_forecastHtml += '</tr>';
|
||||
|
||||
_opacity -= 0.155;
|
||||
|
||||
}
|
||||
|
||||
_forecastHtml += '</table>';
|
||||
|
||||
$(this.forecastLocation).updateWithText(_forecastHtml, this.fadeInterval);
|
||||
|
||||
}.bind(this),
|
||||
error: function () {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
weather.init = function () {
|
||||
|
||||
if (this.params.lang === undefined) {
|
||||
this.params.lang = this.lang;
|
||||
}
|
||||
|
||||
if (this.params.cnt === undefined) {
|
||||
this.params.cnt = 5;
|
||||
}
|
||||
|
||||
this.intervalId = setInterval(function () {
|
||||
this.updateCurrentWeather();
|
||||
this.updateWeatherForecast();
|
||||
}.bind(this), this.updateInterval);
|
||||
|
||||
}
|
13
jsconfig.json
Normal file
13
jsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=759670
|
||||
// for the documentation about the jsconfig.json format
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"exclude": [
|
||||
"modules",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
698
modules/README.md
Normal file
698
modules/README.md
Normal file
@@ -0,0 +1,698 @@
|
||||
# MagicMirror² Module Development Documentation
|
||||
|
||||
This document describes the way to develop your own MagicMirror² modules.
|
||||
|
||||
## 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.
|
||||
|
||||
A module can be placed in one single folder. Or multiple modules can be grouped in a subfolder. Note that name of the module must be unique. Even when a module with a similar name is placed in a different folder, they can't be loaded at the same time.
|
||||
|
||||
### Files
|
||||
- **modulename/modulename.js** - This is your core module script.
|
||||
- **modulename/node_helper.js** - This is an optional helper that will be loaded by the node script. The node helper and module script can communicate with each other using an intergrated socket system.
|
||||
- **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
|
||||
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",{});
|
||||
````
|
||||
Of course, the above module would not do anything fancy, so it's good to look at one of the simplest modules: **helloworld**:
|
||||
|
||||
````javascript
|
||||
//helloworld.js:
|
||||
|
||||
Module.register("helloworld",{
|
||||
// Default module config.
|
||||
defaults: {
|
||||
text: "Hello World!"
|
||||
},
|
||||
|
||||
// Override dom generator.
|
||||
getDom: function() {
|
||||
var wrapper = document.createElement("div");
|
||||
wrapper.innerHTML = this.config.text;
|
||||
return wrapper;
|
||||
}
|
||||
});
|
||||
````
|
||||
|
||||
As you can see, the `Module.register()` method takes two arguments: the name of the module and an object with the module properties.
|
||||
|
||||
### Available module instance properties
|
||||
After the module is initialized, the module instance has a few available module properties:
|
||||
|
||||
#### `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 contains additional metadata about the module instance:
|
||||
- `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.
|
||||
- `data.header` - The header added to the module.
|
||||
- `data.position` - The position in which the instance will be shown.
|
||||
|
||||
|
||||
#### `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:`
|
||||
|
||||
*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. Make sure to also set this value in the Node helper.
|
||||
|
||||
**Note:** Since this check is introduced in version 2.1.0, this check will not be run in older versions. Keep this in mind if you get issue reports on your module.
|
||||
|
||||
Example:
|
||||
````javascript
|
||||
requiresVersion: "2.1.0",
|
||||
````
|
||||
|
||||
### Subclassable module methods
|
||||
|
||||
#### `init()`
|
||||
This method is called when a module gets instantiated. In most cases you do not need to subclass this method.
|
||||
|
||||
#### `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:**
|
||||
````javascript
|
||||
start: function() {
|
||||
this.mySpecialProperty = "So much wow!";
|
||||
Log.log(this.name + ' is started!');
|
||||
}
|
||||
````
|
||||
|
||||
#### `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.
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
getScripts: function() {
|
||||
return [
|
||||
'script.js', // will try to load it from the vendor folder, otherwise it will load is from the module folder.
|
||||
'moment.js', // this file is available in the vendor folder, so it doesn't need to be available in the module folder.
|
||||
this.file('anotherfile.js'), // this file will be loaded straight from the module folder.
|
||||
'https://code.jquery.com/jquery-2.2.3.min.js', // this file will be loaded from the jquery servers.
|
||||
]
|
||||
}
|
||||
|
||||
````
|
||||
**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()`
|
||||
**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.
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
getStyles: function() {
|
||||
return [
|
||||
'script.css', // will try to load it from the vendor folder, otherwise it will load is from the module folder.
|
||||
'font-awesome.css', // this file is available in the vendor folder, so it doesn't need to be avialable in the module folder.
|
||||
this.file('anotherfile.css'), // this file will be loaded straight from the module folder.
|
||||
'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css', // this file will be loaded from the bootstrapcdn servers.
|
||||
]
|
||||
}
|
||||
|
||||
````
|
||||
**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()`
|
||||
**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.
|
||||
|
||||
If the module does not have any module specific translations, the function can just be omitted or return `false`.
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
getTranslations: function() {
|
||||
return {
|
||||
en: "translations/en.json",
|
||||
de: "translations/de.json"
|
||||
}
|
||||
}
|
||||
|
||||
````
|
||||
|
||||
#### `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.
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
getDom: function() {
|
||||
var wrapper = document.createElement("div");
|
||||
wrapper.innerHTML = 'Hello world!';
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
````
|
||||
|
||||
#### `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.
|
||||
|
||||
If you want to use the original user's configured header, reference `this.data.header`.
|
||||
|
||||
**NOTE:** If the user did not configure a default header, no header will be displayed and thus this method will not be called.
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
getHeader: function() {
|
||||
return this.data.header + ' Foo Bar';
|
||||
}
|
||||
|
||||
````
|
||||
|
||||
#### `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:
|
||||
|
||||
- `notification` - String - The notification identifier.
|
||||
- `payload` - AnyType - The payload of a notification.
|
||||
- `sender` - Module - The sender of the notification. If this argument is `undefined`, the sender of the notififiction is the core system.
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
notificationReceived: function(notification, payload, sender) {
|
||||
if (sender) {
|
||||
Log.log(this.name + " received a module notification: " + notification + " from sender: " + sender.name);
|
||||
} else {
|
||||
Log.log(this.name + " received a system notification: " + notification);
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
**Note:** the system sends two 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.
|
||||
|
||||
|
||||
#### `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.
|
||||
- `payload` - AnyType - The payload of a notification.
|
||||
|
||||
**Note 1:** When a node helper sends a notification, all modules of that module type receive the same notifications. <br>
|
||||
**Note 2:** The socket connection is established as soon as the module sends its first message using [sendSocketNotification](thissendsocketnotificationnotification-payload).
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
socketNotificationReceived: function(notification, payload) {
|
||||
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
|
||||
},
|
||||
````
|
||||
|
||||
#### `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 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
|
||||
|
||||
Each module instance has some handy methods which can be helpful building your module.
|
||||
|
||||
|
||||
#### `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)`
|
||||
***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.
|
||||
|
||||
As an example: the clock modules calls this method every second:
|
||||
|
||||
````javascript
|
||||
...
|
||||
start: function() {
|
||||
var self = this;
|
||||
setInterval(function() {
|
||||
self.updateDom(); // no speed defined, so it updates instantly.
|
||||
}, 1000); //perform every 1000 milliseconds.
|
||||
},
|
||||
...
|
||||
````
|
||||
|
||||
#### `this.sendNotification(notification, payload)`
|
||||
***notification* String** - The notification identifier.<br>
|
||||
***payload* AnyType** - Optional. A notification payload.<br>
|
||||
|
||||
If you want to send a notification to all other modules, use the `sendNotification(notification, payload)`. All other modules will receive the message via the [notificationReceived](#notificationreceivednotification-payload-sender) method. In that case, the sender is automatically set to the instance calling the sendNotification method.
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
this.sendNotification('MYMODULE_READY_FOR_ACTION', {foo:bar});
|
||||
````
|
||||
|
||||
#### `this.sendSocketNotification(notification, payload)`
|
||||
***notification* String** - The notification identifier.<br>
|
||||
***payload* AnyType** - Optional. A notification payload.<br>
|
||||
|
||||
If you want to send a notification to the node_helper, use the `sendSocketNotification(notification, payload)`. Only the node_helper of this module will receive the socket notification.
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
this.sendSocketNotification('SET_CONFIG', this.config);
|
||||
````
|
||||
|
||||
#### `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.*)
|
||||
|
||||
To hide a module, you can call the `hide(speed, callback)` method. You can call the hide method on the module instance itself using `this.hide()`, but of course you can also hide another module using `anOtherModule.hide()`.
|
||||
|
||||
Possible configurable options:
|
||||
|
||||
- `lockString` - String - When setting lock string, the module can not be shown without passing the correct lockstring. This way (multiple) modules can prevent a module from showing. It's considered best practice to use your modules identifier as the locksString: `this.identifier`. See *visibility locking* below.
|
||||
|
||||
|
||||
**Note 1:** If the hide animation is canceled, for instance because the show method is called before the hide animation was finished, the callback will not be called.<br>
|
||||
**Note 2:** If the hide animation is hijacked (an other method calls hide on the same module), the callback will not be called.<br>
|
||||
**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)`
|
||||
***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.*)
|
||||
|
||||
To show a module, you can call the `show(speed, callback)` method. You can call the show method on the module instance itself using `this.show()`, but of course you can also show another module using `anOtherModule.show()`.
|
||||
|
||||
Possible configurable options:
|
||||
|
||||
- `lockString` - String - When setting lock string, the module can not be shown without passing the correct lockstring. This way (multiple) modules can prevent a module from showing. See *visibility locking* below.
|
||||
- `force` - Boolean - When setting the force tag to `true`, the locking mechanism will be overwritten. Use this option with caution. It's considered best practice to let the usage of the force option be use- configurable. See *visibility locking* below.
|
||||
|
||||
**Note 1:** If the show animation is canceled, for instance because the hide method is called before the show animation was finished, the callback will not be called.<br>
|
||||
**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
|
||||
|
||||
(*Introduced in version: 2.1.0.*)
|
||||
|
||||
Visiblity locking helps the module system to prevent unwanted hide/show actions. The following scenario explains the concept:
|
||||
|
||||
**Module B asks module A to hide:**
|
||||
````javascript
|
||||
moduleA.hide(0, {lockString: "module_b_identifier"});
|
||||
````
|
||||
Module A is now hidden, and has an lock array with the following strings:
|
||||
````javascript
|
||||
moduleA.lockStrings == ["module_b_identifier"]
|
||||
moduleA.hidden == true
|
||||
````
|
||||
**Module C asks module A to hide:**
|
||||
````javascript
|
||||
moduleA.hide(0, {lockString: "module_c_identifier"});
|
||||
````
|
||||
Module A is now hidden, and has an lock array with the following strings:
|
||||
````javascript
|
||||
moduleA.lockStrings == ["module_b_identifier", "module_c_identifier"]
|
||||
moduleA.hidden == true
|
||||
````
|
||||
**Module B asks module A to show:**
|
||||
````javascript
|
||||
moduleA.show(0, {lockString: "module_b_identifier"});
|
||||
````
|
||||
The lockString will be removed from moduleA’s locks array, but since there still is an other lock string available, the module remains hidden:
|
||||
````javascript
|
||||
moduleA.lockStrings == ["module_c_identifier"]
|
||||
moduleA.hidden == true
|
||||
````
|
||||
**Module C asks module A to show:**
|
||||
````javascript
|
||||
moduleA.show(0, {lockString: "module_c_identifier"});
|
||||
````
|
||||
The lockString will be removed from moduleA’s locks array, and since this will result in an empty lock array, the module will be visible:
|
||||
````javascript
|
||||
moduleA.lockStrings == []
|
||||
moduleA.hidden == false
|
||||
````
|
||||
|
||||
**Note:** The locking mechanism can be overwritten by using the force tag:
|
||||
````javascript
|
||||
moduleA.show(0, {force: true});
|
||||
````
|
||||
This will reset the lockstring array, and will show the module.
|
||||
````javascript
|
||||
moduleA.lockStrings == []
|
||||
moduleA.hidden == false
|
||||
````
|
||||
|
||||
Use this `force` method with caution. See `show()` method for more information.
|
||||
|
||||
|
||||
|
||||
#### `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.
|
||||
|
||||
If no translation is found, a fallback will be used. The fallback sequence is as follows:
|
||||
- 1. Translation as defined in module translation file of the user's preferred language.
|
||||
- 2. Translation as defined in core translation file of the user's preferred language.
|
||||
- 3. Translation as defined in module translation file of the fallback language (the first defined module translation file).
|
||||
- 4. Translation as defined in core translation file of the fallback language (the first defined core translation file).
|
||||
- 5. The key (identifier) of the translation.
|
||||
|
||||
When adding translations to your module, it's a good idea to see if an apropriate translation is already available in the [core translation files](https://github.com/MichMich/MagicMirror/tree/master/translations). This way, your module can benefit from the existing translations.
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
this.translate("INFO") //Will return a translated string for the identifier INFO
|
||||
````
|
||||
|
||||
**Example json file:**
|
||||
````javascript
|
||||
{
|
||||
"INFO": "Really important information!"
|
||||
}
|
||||
````
|
||||
|
||||
**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
|
||||
|
||||
The node helper is a Node.js script that is able to do some backend task to support your module. For every module type, only one node helper instance will be created. For example: if your MagicMirror uses two calendar modules, there will be only one calendar node helper instantiated.
|
||||
|
||||
**Note:** Because there is only one node helper per module type, there is no default config available within your module. It's your task to send the desired config from your module to your node helper.
|
||||
|
||||
In it's most simple form, the node_helper.js file must contain:
|
||||
|
||||
````javascript
|
||||
var NodeHelper = require("node_helper");
|
||||
module.exports = NodeHelper.create({});
|
||||
````
|
||||
|
||||
Of course, the above helper would not do anything useful. So with the information above, you should be able to make it a bit more sophisticated.
|
||||
|
||||
### Available module instance properties
|
||||
|
||||
#### `this.name`
|
||||
**String**
|
||||
|
||||
The name of the module
|
||||
|
||||
#### `this.path`
|
||||
**String**
|
||||
|
||||
The path of the module
|
||||
|
||||
#### `this.expressApp`
|
||||
**Express App Instance**
|
||||
|
||||
This is a link to the express instance. It will allow you to define extra routes.
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
start: function() {
|
||||
this.expressApp.get('/foobar', function (req, res) {
|
||||
res.send('GET request to /foobar');
|
||||
});
|
||||
}
|
||||
````
|
||||
|
||||
**Note:** By default, a public path to your module's public folder will be created:
|
||||
````javascript
|
||||
this.expressApp.use("/" + this.name, express.static(this.path + "/public"));
|
||||
````
|
||||
|
||||
#### `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:`
|
||||
*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.
|
||||
|
||||
**Note:** Since this check is introduced in version 2.1.0, this check will not be run in older versions. Keep this in mind if you get issue reports on your module.
|
||||
|
||||
Example:
|
||||
````javascript
|
||||
requiresVersion: "2.1.0",
|
||||
````
|
||||
|
||||
### Subclassable module methods
|
||||
|
||||
#### `init()`
|
||||
This method is called when a node helper gets instantiated. In most cases you do not need to subclass this method.
|
||||
|
||||
#### `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:**
|
||||
````javascript
|
||||
start: function() {
|
||||
this.mySpecialProperty = "So much wow!";
|
||||
Log.log(this.name + ' is started!');
|
||||
}
|
||||
````
|
||||
|
||||
#### `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.
|
||||
- `payload` - AnyType - The payload of a notification.
|
||||
|
||||
**Note:** The socket connection is established as soon as the module sends its first message using [sendSocketNotification](thissendsocketnotificationnotification-payload).
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
socketNotificationReceived: function(notification, payload) {
|
||||
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
|
||||
},
|
||||
````
|
||||
|
||||
### Module instance methods
|
||||
|
||||
Each node helper has some handy methods which can be helpful building your module.
|
||||
|
||||
#### `this.sendSocketNotification(notification, payload)`
|
||||
***notification* String** - The notification identifier.<br>
|
||||
***payload* AnyType** - Optional. A notification payload.<br>
|
||||
|
||||
If you want to send a notification to all your modules, use the `sendSocketNotification(notification, payload)`. Only the module of your module type will receive the socket notification.
|
||||
|
||||
**Note:** Since all instances of your module will receive the notifications, it's your task to make sure the right module responds to your messages.
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
this.sendSocketNotification('SET_CONFIG', this.config);
|
||||
````
|
||||
|
||||
## MagicMirror Helper Methods
|
||||
|
||||
The core Magic Mirror object: `MM` has some handy method that will help you in controlling your and other modules. Most of the `MM` methods are available via convenience methods on the Module instance.
|
||||
|
||||
### 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()`
|
||||
**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.
|
||||
|
||||
**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)`
|
||||
***classnames* String or Array** - The class names on which you want to filter.
|
||||
**Returns Array** - An array with module instances.<br>
|
||||
|
||||
If you want to make a selection based on one or more class names, use the withClass method on a result of the `MM.getModules()` method. The argument of the `withClass(classname)` method can be an array, or space separated string.
|
||||
|
||||
**Examples:**
|
||||
````javascript
|
||||
var modules = MM.getModules().withClass('classname');
|
||||
var modules = MM.getModules().withClass('classname1 classname2');
|
||||
var modules = MM.getModules().withClass(['classname1','classname2']);
|
||||
````
|
||||
|
||||
##### `.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>
|
||||
|
||||
If you to remove some modules from a selection based on a classname, use the exceptWithClass method on a result of the `MM.getModules()` method. The argument of the `exceptWithClass(classname)` method can be an array, or space separated string.
|
||||
|
||||
**Examples:**
|
||||
````javascript
|
||||
var modules = MM.getModules().exceptWithClass('classname');
|
||||
var modules = MM.getModules().exceptWithClass('classname1 classname2');
|
||||
var modules = MM.getModules().exceptWithClass(['classname1','classname2']);
|
||||
````
|
||||
|
||||
##### `.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>
|
||||
|
||||
If you to remove a specific module instance from a selection based on a classname, use the exceptWithClass method on a result of the `MM.getModules()` method. This can be helpful if you want to select all module instances except the instance of your module.
|
||||
|
||||
**Examples:**
|
||||
````javascript
|
||||
var modules = MM.getModules().exceptModule(this);
|
||||
````
|
||||
|
||||
Of course, you can combine all of the above filters:
|
||||
|
||||
**Example:**
|
||||
````javascript
|
||||
var modules = MM.getModules().withClass('classname1').exceptwithClass('classname2').exceptModule(aModule);
|
||||
````
|
||||
|
||||
##### `.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:
|
||||
|
||||
````javascript
|
||||
MM.getModules().enumerate(function(module) {
|
||||
Log.log(module.name);
|
||||
});
|
||||
````
|
||||
|
||||
**Example:**
|
||||
To hide all modules except the your module instance, you could write something like:
|
||||
````javascript
|
||||
Module.register("modulename",{
|
||||
//...
|
||||
notificationReceived: function(notification, payload, sender) {
|
||||
if (notification === 'DOM_OBJECTS_CREATED') {
|
||||
MM.getModules().exceptModule(this).enumerate(function(module) {
|
||||
module.hide(1000, function() {
|
||||
//Module hidden.
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
//...
|
||||
});
|
||||
````
|
||||
|
||||
## MagicMirror Logger
|
||||
|
||||
The Magic Mirror contains a convenience wrapper for logging. Currently, this logger is a simple proxy to the original `console.log` methods. But it might get additional features in the future. The Loggers is currently only available in the core module file (not in the node_helper).
|
||||
|
||||
**Examples:**
|
||||
````javascript
|
||||
Log.info('error');
|
||||
Log.log('log');
|
||||
Log.error('info');
|
||||
````
|
64
modules/default/alert/README.md
Normal file
64
modules/default/alert/README.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Module: Alert
|
||||
The alert module is one of the default modules of the MagicMirror. This module displays notifications from other modules.
|
||||
|
||||
## Usage
|
||||
To use this module, add it to the modules array in the config/config.js file:
|
||||
|
||||
```
|
||||
modules: [
|
||||
{
|
||||
module: "alert",
|
||||
config: {
|
||||
// The config property is optional.
|
||||
// See 'Configuration options' for more information.
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Configuration options
|
||||
|
||||
The following properties can be configured:
|
||||
|
||||
|
||||
| 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"});
|
||||
```
|
||||
For alerts use:
|
||||
|
||||
```
|
||||
self.sendNotification("SHOW_ALERT", {});
|
||||
```
|
||||
|
||||
### Notification params
|
||||
| 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
|
||||
| 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)
|
||||
See [ympanus.net](http://tympanus.net/codrops/licensing/) for license.
|
154
modules/default/alert/alert.js
Normal file
154
modules/default/alert/alert.js
Normal file
@@ -0,0 +1,154 @@
|
||||
/* global Module */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: alert
|
||||
*
|
||||
* By Paul-Vincent Roll http://paulvincentroll.com
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
Module.register("alert",{
|
||||
defaults: {
|
||||
// scale|slide|genie|jelly|flip|bouncyflip|exploader
|
||||
effect: "slide",
|
||||
// scale|slide|genie|jelly|flip|bouncyflip|exploader
|
||||
alert_effect: "jelly",
|
||||
//time a notification is displayed in seconds
|
||||
display_time: 3500,
|
||||
//Position
|
||||
position: "center",
|
||||
//shown at startup
|
||||
welcome_message: false,
|
||||
},
|
||||
getScripts: function() {
|
||||
return ["classie.js", "modernizr.custom.js", "notificationFx.js"];
|
||||
},
|
||||
getStyles: function() {
|
||||
return ["ns-default.css"];
|
||||
},
|
||||
// Define required translations.
|
||||
getTranslations: function() {
|
||||
return {
|
||||
en: "translations/en.json",
|
||||
de: "translations/de.json",
|
||||
nl: "translations/nl.json",
|
||||
};
|
||||
},
|
||||
show_notification: function(message) {
|
||||
if (this.config.effect == "slide") {this.config.effect = this.config.effect + "-" + this.config.position;}
|
||||
msg = "";
|
||||
if (message.title) {
|
||||
msg += "<span class='thin' style='line-height: 35px; font-size:24px' color='#4A4A4A'>" + message.title + "</span>";
|
||||
}
|
||||
if (message.message){
|
||||
if (msg != ""){
|
||||
msg+= "<br />";
|
||||
}
|
||||
msg += "<span class='light' style='font-size:28px;line-height: 30px;'>" + message.message + "</span>";
|
||||
}
|
||||
|
||||
new NotificationFx({
|
||||
message: msg,
|
||||
layout: "growl",
|
||||
effect: this.config.effect,
|
||||
ttl: this.config.display_time
|
||||
}).show();
|
||||
},
|
||||
show_alert: function(params, sender) {
|
||||
var self = this;
|
||||
//Set standard params if not provided by module
|
||||
if (typeof params.timer === "undefined") { params.timer = null; }
|
||||
if (typeof params.imageHeight === "undefined") { params.imageHeight = "80px"; }
|
||||
if (typeof params.imageUrl === "undefined" && typeof params.imageFA === "undefined") {
|
||||
params.imageUrl = null;
|
||||
image = "";
|
||||
} else if (typeof params.imageFA === "undefined"){
|
||||
image = "<img src='" + (params.imageUrl).toString() + "' height=" + (params.imageHeight).toString() + " style='margin-bottom: 10px;'/><br />";
|
||||
} else if (typeof params.imageUrl === "undefined"){
|
||||
image = "<span class='" + "fa fa-" + params.imageFA + "' style='margin-bottom: 10px;color: #fff;font-size:" + (params.imageHeight).toString() + ";'/></span><br />";
|
||||
}
|
||||
//Create overlay
|
||||
var overlay = document.createElement("div");
|
||||
overlay.id = "overlay";
|
||||
overlay.innerHTML += "<div class=\"black_overlay\"></div>";
|
||||
document.body.insertBefore(overlay, document.body.firstChild);
|
||||
|
||||
//If module already has an open alert close it
|
||||
if (this.alerts[sender.name]) {
|
||||
this.hide_alert(sender);
|
||||
}
|
||||
|
||||
//Display title and message only if they are provided in notification parameters
|
||||
message ="";
|
||||
if (params.title) {
|
||||
message += "<span class='light' style='line-height: 35px; font-size:30px' color='#4A4A4A'>" + params.title + "</span>"
|
||||
}
|
||||
if (params.message) {
|
||||
if (message != ""){
|
||||
message += "<br />";
|
||||
}
|
||||
|
||||
message += "<span class='thin' style='font-size:22px;line-height: 30px;'>" + params.message + "</span>";
|
||||
}
|
||||
|
||||
//Store alert in this.alerts
|
||||
this.alerts[sender.name] = new NotificationFx({
|
||||
message: image + message,
|
||||
effect: this.config.alert_effect,
|
||||
ttl: params.timer,
|
||||
al_no: "ns-alert"
|
||||
});
|
||||
//Show alert
|
||||
this.alerts[sender.name].show();
|
||||
//Add timer to dismiss alert and overlay
|
||||
if (params.timer) {
|
||||
setTimeout(function() {
|
||||
self.hide_alert(sender);
|
||||
}, params.timer);
|
||||
}
|
||||
|
||||
},
|
||||
hide_alert: function(sender) {
|
||||
//Dismiss alert and remove from this.alerts
|
||||
this.alerts[sender.name].dismiss();
|
||||
this.alerts[sender.name] = null;
|
||||
//Remove overlay
|
||||
var overlay = document.getElementById("overlay");
|
||||
overlay.parentNode.removeChild(overlay);
|
||||
},
|
||||
setPosition: function(pos) {
|
||||
//Add css to body depending on the set position for notifications
|
||||
var sheet = document.createElement("style");
|
||||
if (pos == "center") {sheet.innerHTML = ".ns-box {margin-left: auto; margin-right: auto;text-align: center;}";}
|
||||
if (pos == "right") {sheet.innerHTML = ".ns-box {margin-left: auto;text-align: right;}";}
|
||||
if (pos == "left") {sheet.innerHTML = ".ns-box {margin-right: auto;text-align: left;}";}
|
||||
document.body.appendChild(sheet);
|
||||
|
||||
},
|
||||
notificationReceived: function(notification, payload, sender) {
|
||||
if (notification === "SHOW_ALERT") {
|
||||
if (typeof payload.type === "undefined") { payload.type = "alert"; }
|
||||
if (payload.type == "alert") {
|
||||
this.show_alert(payload, sender);
|
||||
} else if (payload.type = "notification") {
|
||||
this.show_notification(payload);
|
||||
}
|
||||
} else if (notification === "HIDE_ALERT") {
|
||||
this.hide_alert(sender);
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
this.alerts = {};
|
||||
this.setPosition(this.config.position);
|
||||
if (this.config.welcome_message) {
|
||||
if (this.config.welcome_message == true){
|
||||
this.show_notification({title: this.translate("sysTitle"), message: this.translate("welcome")});
|
||||
}
|
||||
else{
|
||||
this.show_notification({title: this.translate("sysTitle"), message: this.config.welcome_message});
|
||||
}
|
||||
}
|
||||
Log.info("Starting module: " + this.name);
|
||||
}
|
||||
|
||||
});
|
79
modules/default/alert/classie.js
Normal file
79
modules/default/alert/classie.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/*!
|
||||
* classie - class helper functions
|
||||
* from bonzo https://github.com/ded/bonzo
|
||||
*
|
||||
* classie.has( elem, 'my-class' ) -> true/false
|
||||
* classie.add( elem, 'my-new-class' )
|
||||
* classie.remove( elem, 'my-unwanted-class' )
|
||||
* classie.toggle( elem, 'my-class' )
|
||||
*/
|
||||
// jscs:disable
|
||||
/*jshint browser: true, strict: true, undef: true */
|
||||
/*global define: false */
|
||||
|
||||
(function(window) {
|
||||
|
||||
"use strict";
|
||||
|
||||
// class helper functions from bonzo https://github.com/ded/bonzo
|
||||
|
||||
function classReg(className) {
|
||||
return new RegExp("(^|\\s+)" + className + "(\\s+|$)");
|
||||
}
|
||||
|
||||
// classList support for class management
|
||||
// altho to be fair, the api sucks because it won't accept multiple classes at once
|
||||
var hasClass, addClass, removeClass;
|
||||
|
||||
if ("classList" in document.documentElement) {
|
||||
hasClass = function(elem, c) {
|
||||
return elem.classList.contains(c);
|
||||
};
|
||||
addClass = function(elem, c) {
|
||||
elem.classList.add(c);
|
||||
};
|
||||
removeClass = function(elem, c) {
|
||||
elem.classList.remove(c);
|
||||
};
|
||||
} else {
|
||||
hasClass = function(elem, c) {
|
||||
return classReg(c).test(elem.className);
|
||||
};
|
||||
addClass = function(elem, c) {
|
||||
if (!hasClass(elem, c)) {
|
||||
elem.className = elem.className + " " + c;
|
||||
}
|
||||
};
|
||||
removeClass = function(elem, c) {
|
||||
elem.className = elem.className.replace(classReg(c), " ");
|
||||
};
|
||||
}
|
||||
|
||||
function toggleClass(elem, c) {
|
||||
var fn = hasClass(elem, c) ? removeClass : addClass;
|
||||
fn(elem, c);
|
||||
}
|
||||
|
||||
var classie = {
|
||||
// full names
|
||||
hasClass: hasClass,
|
||||
addClass: addClass,
|
||||
removeClass: removeClass,
|
||||
toggleClass: toggleClass,
|
||||
// short names
|
||||
has: hasClass,
|
||||
add: addClass,
|
||||
remove: removeClass,
|
||||
toggle: toggleClass
|
||||
};
|
||||
|
||||
// transport
|
||||
if (typeof define === "function" && define.amd) {
|
||||
// AMD
|
||||
define(classie);
|
||||
} else {
|
||||
// browser global
|
||||
window.classie = classie;
|
||||
}
|
||||
|
||||
})(window);
|
5
modules/default/alert/modernizr.custom.js
Normal file
5
modules/default/alert/modernizr.custom.js
Normal file
File diff suppressed because one or more lines are too long
165
modules/default/alert/notificationFx.js
Normal file
165
modules/default/alert/notificationFx.js
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* notificationFx.js v1.0.0
|
||||
* http://www.codrops.com
|
||||
*
|
||||
* Licensed under the MIT license.
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* Copyright 2014, Codrops
|
||||
* http://www.codrops.com
|
||||
*/
|
||||
// jscs:disable
|
||||
;(function(window) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var docElem = window.document.documentElement,
|
||||
support = {animations: Modernizr.cssanimations},
|
||||
animEndEventNames = {
|
||||
"WebkitAnimation": "webkitAnimationEnd",
|
||||
"OAnimation": "oAnimationEnd",
|
||||
"msAnimation": "MSAnimationEnd",
|
||||
"animation": "animationend"
|
||||
},
|
||||
// animation end event name
|
||||
animEndEventName = animEndEventNames[ Modernizr.prefixed("animation") ];
|
||||
|
||||
/**
|
||||
* extend obj function
|
||||
*/
|
||||
function extend(a, b) {
|
||||
for (var key in b) {
|
||||
if (b.hasOwnProperty(key)) {
|
||||
a[key] = b[key];
|
||||
}
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* NotificationFx function
|
||||
*/
|
||||
function NotificationFx(options) {
|
||||
this.options = extend({}, this.options);
|
||||
extend(this.options, options);
|
||||
this._init();
|
||||
}
|
||||
|
||||
/**
|
||||
* NotificationFx options
|
||||
*/
|
||||
NotificationFx.prototype.options = {
|
||||
// element to which the notification will be appended
|
||||
// defaults to the document.body
|
||||
wrapper: document.body,
|
||||
// the message
|
||||
message: "yo!",
|
||||
// layout type: growl|attached|bar|other
|
||||
layout: "growl",
|
||||
// effects for the specified layout:
|
||||
// for growl layout: scale|slide|genie|jelly
|
||||
// for attached layout: flip|bouncyflip
|
||||
// for other layout: boxspinner|cornerexpand|loadingcircle|thumbslider
|
||||
// ...
|
||||
effect: "slide",
|
||||
// notice, warning, error, success
|
||||
// will add class ns-type-warning, ns-type-error or ns-type-success
|
||||
type: "notice",
|
||||
// if the user doesn´t close the notification then we remove it
|
||||
// after the following time
|
||||
ttl: 6000,
|
||||
al_no: "ns-box",
|
||||
// callbacks
|
||||
onClose: function() { return false; },
|
||||
onOpen: function() { return false; }
|
||||
};
|
||||
|
||||
/**
|
||||
* init function
|
||||
* initialize and cache some vars
|
||||
*/
|
||||
NotificationFx.prototype._init = function() {
|
||||
// create HTML structure
|
||||
this.ntf = document.createElement("div");
|
||||
this.ntf.className = this.options.al_no + " ns-" + this.options.layout + " ns-effect-" + this.options.effect + " ns-type-" + this.options.type;
|
||||
var strinner = "<div class=\"ns-box-inner\">";
|
||||
strinner += this.options.message;
|
||||
strinner += "</div>";
|
||||
this.ntf.innerHTML = strinner;
|
||||
|
||||
// append to body or the element specified in options.wrapper
|
||||
this.options.wrapper.insertBefore(this.ntf, this.options.wrapper.nextSibling);
|
||||
|
||||
// dismiss after [options.ttl]ms
|
||||
var self = this;
|
||||
if (this.options.ttl) {
|
||||
this.dismissttl = setTimeout(function() {
|
||||
if (self.active) {
|
||||
self.dismiss();
|
||||
}
|
||||
}, this.options.ttl);
|
||||
}
|
||||
|
||||
// init events
|
||||
this._initEvents();
|
||||
};
|
||||
|
||||
/**
|
||||
* init events
|
||||
*/
|
||||
NotificationFx.prototype._initEvents = function() {
|
||||
var self = this;
|
||||
// dismiss notification by tapping on it if someone has a touchscreen
|
||||
this.ntf.querySelector(".ns-box-inner").addEventListener("click", function() { self.dismiss(); });
|
||||
};
|
||||
|
||||
/**
|
||||
* show the notification
|
||||
*/
|
||||
NotificationFx.prototype.show = function() {
|
||||
this.active = true;
|
||||
classie.remove(this.ntf, "ns-hide");
|
||||
classie.add(this.ntf, "ns-show");
|
||||
this.options.onOpen();
|
||||
};
|
||||
|
||||
/**
|
||||
* dismiss the notification
|
||||
*/
|
||||
NotificationFx.prototype.dismiss = function() {
|
||||
var self = this;
|
||||
this.active = false;
|
||||
clearTimeout(this.dismissttl);
|
||||
classie.remove(this.ntf, "ns-show");
|
||||
setTimeout(function() {
|
||||
classie.add(self.ntf, "ns-hide");
|
||||
|
||||
// callback
|
||||
self.options.onClose();
|
||||
}, 25);
|
||||
|
||||
// after animation ends remove ntf from the DOM
|
||||
var onEndAnimationFn = function(ev) {
|
||||
if (support.animations) {
|
||||
if (ev.target !== self.ntf) return false;
|
||||
this.removeEventListener(animEndEventName, onEndAnimationFn);
|
||||
}
|
||||
|
||||
if (this.parentNode === self.options.wrapper) {
|
||||
self.options.wrapper.removeChild(this);
|
||||
}
|
||||
};
|
||||
|
||||
if (support.animations) {
|
||||
this.ntf.addEventListener(animEndEventName, onEndAnimationFn);
|
||||
} else {
|
||||
onEndAnimationFn();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* add to global namespace
|
||||
*/
|
||||
window.NotificationFx = NotificationFx;
|
||||
|
||||
})(window);
|
1739
modules/default/alert/ns-default.css
Normal file
1739
modules/default/alert/ns-default.css
Normal file
File diff suppressed because it is too large
Load Diff
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/da.json
Normal file
4
modules/default/alert/translations/da.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror Notifikation",
|
||||
"welcome": "Velkommen, modulet er succesfuldt startet!"
|
||||
}
|
4
modules/default/alert/translations/de.json
Normal file
4
modules/default/alert/translations/de.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror Benachrichtigung",
|
||||
"welcome": "Willkommen, Start war erfolgreich!"
|
||||
}
|
4
modules/default/alert/translations/en.json
Normal file
4
modules/default/alert/translations/en.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror Notification",
|
||||
"welcome": "Welcome, start was successful!"
|
||||
}
|
4
modules/default/alert/translations/es.json
Normal file
4
modules/default/alert/translations/es.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"sysTitle": "MagicMirror Notificaciones",
|
||||
"welcome": "Bienvenido, ¡se iniciado correctamente!"
|
||||
}
|
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": "Добро пожаловать, старт был успешным!"
|
||||
}
|
91
modules/default/calendar/README.md
Normal file
91
modules/default/calendar/README.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Module: Calendar
|
||||
The `calendar` module is one of the default modules of the MagicMirror.
|
||||
This module displays events from a public .ical calendar. It can combine multiple calendars.
|
||||
|
||||
## Using the module
|
||||
|
||||
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.
|
||||
config: {
|
||||
// The config property is optional.
|
||||
// If no config is set, an example calendar is shown.
|
||||
// See 'Configuration options' for more information.
|
||||
}
|
||||
}
|
||||
]
|
||||
````
|
||||
|
||||
## Configuration options
|
||||
|
||||
The following properties can be configured:
|
||||
|
||||
|
||||
| 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> **Example:** `['Birthday', 'Hide This Event']` <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:
|
||||
| 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`
|
24
modules/default/calendar/calendar.css
Normal file
24
modules/default/calendar/calendar.css
Normal file
@@ -0,0 +1,24 @@
|
||||
.calendar .symbol {
|
||||
padding-left: 0;
|
||||
padding-right: 10px;
|
||||
font-size: 80%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.calendar .symbol span {
|
||||
display: inline-block;
|
||||
-ms-transform: translate(0, 2px); /* IE 9 */
|
||||
-webkit-transform: translate(0, 2px); /* Safari */
|
||||
transform: translate(0, 2px);
|
||||
}
|
||||
|
||||
.calendar .title {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.calendar .time {
|
||||
padding-left: 30px;
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
}
|
532
modules/default/calendar/calendar.js
Normal file
532
modules/default/calendar/calendar.js
Normal file
@@ -0,0 +1,532 @@
|
||||
/* global Module */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: Calendar
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
Module.register("calendar", {
|
||||
|
||||
// Define module defaults
|
||||
defaults: {
|
||||
maximumEntries: 10, // Total Maximum Entries
|
||||
maximumNumberOfDays: 365,
|
||||
displaySymbol: true,
|
||||
defaultSymbol: "calendar", // Fontawesome Symbol see http://fontawesome.io/cheatsheet/
|
||||
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",
|
||||
url: "http://www.calendarlabs.com/templates/ical/US-Holidays.ics",
|
||||
},
|
||||
],
|
||||
titleReplace: {
|
||||
"De verjaardag van ": "",
|
||||
"'s birthday": ""
|
||||
},
|
||||
broadcastEvents: true,
|
||||
excludedEvents: []
|
||||
},
|
||||
|
||||
// Define required scripts.
|
||||
getStyles: function () {
|
||||
return ["calendar.css", "font-awesome.css"];
|
||||
},
|
||||
|
||||
// Define required scripts.
|
||||
getScripts: function () {
|
||||
return ["moment.js"];
|
||||
},
|
||||
|
||||
// Define required translations.
|
||||
getTranslations: function () {
|
||||
// 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;
|
||||
},
|
||||
|
||||
// Override start method.
|
||||
start: function () {
|
||||
Log.log("Starting module: " + this.name);
|
||||
|
||||
// Set locale.
|
||||
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://");
|
||||
|
||||
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 = {};
|
||||
this.loaded = false;
|
||||
},
|
||||
|
||||
// Override socket notification handler.
|
||||
socketNotificationReceived: function (notification, payload) {
|
||||
if (notification === "CALENDAR_EVENTS") {
|
||||
if (this.hasCalendarURL(payload.url)) {
|
||||
this.calendarData[payload.url] = payload.events;
|
||||
this.loaded = true;
|
||||
|
||||
if (this.config.broadcastEvents) {
|
||||
this.broadcastEvents();
|
||||
}
|
||||
}
|
||||
} else if (notification === "FETCH_ERROR") {
|
||||
Log.error("Calendar Error. Could not fetch calendar: " + payload.url);
|
||||
} else if (notification === "INCORRECT_URL") {
|
||||
Log.error("Calendar Error. Incorrect url: " + payload.url);
|
||||
} else {
|
||||
Log.log("Calendar received an unknown socket notification: " + notification);
|
||||
}
|
||||
|
||||
this.updateDom(this.config.animationSpeed);
|
||||
},
|
||||
|
||||
// Override dom generator.
|
||||
getDom: function () {
|
||||
|
||||
var events = this.createEventList();
|
||||
var wrapper = document.createElement("table");
|
||||
wrapper.className = "small";
|
||||
|
||||
if (events.length === 0) {
|
||||
wrapper.innerHTML = (this.loaded) ? this.translate("EMPTY") : this.translate("LOADING");
|
||||
wrapper.className = "small dimmed";
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
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 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);
|
||||
|
||||
if (repeatingCountTitle !== "") {
|
||||
var thisYear = new Date(parseInt(event.startDate)).getFullYear(),
|
||||
yearDiff = thisYear - event.firstYear;
|
||||
|
||||
repeatingCountTitle = ", " + yearDiff + ". " + repeatingCountTitle;
|
||||
}
|
||||
}
|
||||
|
||||
titleWrapper.innerHTML = this.titleTransform(event.title) + repeatingCountTitle;
|
||||
|
||||
if (!this.config.colored) {
|
||||
titleWrapper.className = "title bright";
|
||||
} else {
|
||||
titleWrapper.className = "title";
|
||||
}
|
||||
|
||||
eventWrapper.appendChild(titleWrapper);
|
||||
|
||||
var timeWrapper = document.createElement("td");
|
||||
//console.log(event.today);
|
||||
var now = new Date();
|
||||
// Define second, minute, hour, and day variables
|
||||
var oneSecond = 1000; // 1,000 milliseconds
|
||||
var oneMinute = oneSecond * 60;
|
||||
var oneHour = oneMinute * 60;
|
||||
var oneDay = oneHour * 24;
|
||||
if (event.fullDayEvent) {
|
||||
if (event.today) {
|
||||
timeWrapper.innerHTML = this.capFirst(this.translate("TODAY"));
|
||||
} else if (event.startDate - now < oneDay && event.startDate - now > 0) {
|
||||
timeWrapper.innerHTML = this.capFirst(this.translate("TOMORROW"));
|
||||
} else if (event.startDate - now < 2 * oneDay && event.startDate - now > 0) {
|
||||
if (this.translate("DAYAFTERTOMORROW") !== "DAYAFTERTOMORROW") {
|
||||
timeWrapper.innerHTML = this.capFirst(this.translate("DAYAFTERTOMORROW"));
|
||||
} else {
|
||||
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
|
||||
}
|
||||
} else {
|
||||
/* Check to see if the user displays absolute or relative dates with their events
|
||||
* Also check to see if an event is happening within an 'urgency' time frameElement
|
||||
* For example, if the user set an .urgency of 7 days, those events that fall within that
|
||||
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
|
||||
*
|
||||
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
|
||||
*/
|
||||
if (this.config.timeFormat === "absolute") {
|
||||
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * oneDay))) {
|
||||
// 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.fullDayEventDateFormat));
|
||||
}
|
||||
} else {
|
||||
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (event.startDate >= new Date()) {
|
||||
if (event.startDate - now < 2 * oneDay) {
|
||||
// This event is within the next 48 hours (2 days)
|
||||
if (event.startDate - now < this.config.getRelative * oneHour) {
|
||||
// If event is within 6 hour, display 'in xxx' time format or moment.fromNow()
|
||||
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
|
||||
} else {
|
||||
// Otherwise just say 'Today/Tomorrow at such-n-such time'
|
||||
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar());
|
||||
}
|
||||
} else {
|
||||
/* Check to see if the user displays absolute or relative dates with their events
|
||||
* Also check to see if an event is happening within an 'urgency' time frameElement
|
||||
* For example, if the user set an .urgency of 7 days, those events that fall within that
|
||||
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
|
||||
*
|
||||
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
|
||||
*/
|
||||
if (this.config.timeFormat === "absolute") {
|
||||
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * oneDay))) {
|
||||
// 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));
|
||||
}
|
||||
} else {
|
||||
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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');
|
||||
//console.log(event);
|
||||
timeWrapper.className = "time light";
|
||||
eventWrapper.appendChild(timeWrapper);
|
||||
|
||||
wrapper.appendChild(eventWrapper);
|
||||
|
||||
// Create fade effect.
|
||||
if (this.config.fade && this.config.fadePoint < 1) {
|
||||
if (this.config.fadePoint < 0) {
|
||||
this.config.fadePoint = 0;
|
||||
}
|
||||
var startingPoint = events.length * this.config.fadePoint;
|
||||
var steps = events.length - startingPoint;
|
||||
if (e >= startingPoint) {
|
||||
var currentStep = e - startingPoint;
|
||||
eventWrapper.style.opacity = 1 - (1 / steps * currentStep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 string - Url to look for.
|
||||
*
|
||||
* return bool - Has calendar url
|
||||
*/
|
||||
hasCalendarURL: function (url) {
|
||||
for (var c in this.config.calendars) {
|
||||
var calendar = this.config.calendars[c];
|
||||
if (calendar.url === url) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/* createEventList()
|
||||
* Creates the sorted list of all events.
|
||||
*
|
||||
* return array - Array with events.
|
||||
*/
|
||||
createEventList: function () {
|
||||
var events = [];
|
||||
var today = moment().startOf("day");
|
||||
for (var c in this.calendarData) {
|
||||
var calendar = this.calendarData[c];
|
||||
for (var e in calendar) {
|
||||
var event = calendar[e];
|
||||
if(this.config.hidePrivate) {
|
||||
if(event.class === "PRIVATE") {
|
||||
// do not add the current event, skip it
|
||||
continue;
|
||||
}
|
||||
}
|
||||
event.url = c;
|
||||
event.today = event.startDate >= today && event.startDate < (today + 24 * 60 * 60 * 1000);
|
||||
events.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
events.sort(function (a, b) {
|
||||
return a.startDate - b.startDate;
|
||||
});
|
||||
|
||||
return events.slice(0, this.config.maximumEntries);
|
||||
},
|
||||
|
||||
/* createEventList(url)
|
||||
* Requests node helper to add calendar url.
|
||||
*
|
||||
* argument url string - Url to add.
|
||||
*/
|
||||
addCalendar: function (url, auth, calendarConfig) {
|
||||
this.sendSocketNotification("ADD_CALENDAR", {
|
||||
url: url,
|
||||
excludedEvents: calendarConfig.excludedEvents || this.config.excludedEvents,
|
||||
maximumEntries: calendarConfig.maximumEntries || this.config.maximumEntries,
|
||||
maximumNumberOfDays: calendarConfig.maximumNumberOfDays || this.config.maximumNumberOfDays,
|
||||
fetchInterval: this.config.fetchInterval,
|
||||
auth: auth
|
||||
});
|
||||
},
|
||||
|
||||
/* symbolsForUrl(url)
|
||||
* Retrieves the symbols for a specific url.
|
||||
*
|
||||
* argument url string - Url to look for.
|
||||
*
|
||||
* return string/array - The Symbols
|
||||
*/
|
||||
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 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 && calendar.hasOwnProperty(property)) {
|
||||
return calendar[property];
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shortens a string if it's longer than maxLength and add a ellipsis to the end
|
||||
*
|
||||
* @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, wrapEvents) {
|
||||
if (typeof string !== "string") {
|
||||
return "";
|
||||
}
|
||||
|
||||
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
|
||||
* Return capitalized string
|
||||
*/
|
||||
|
||||
capFirst: function (string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
},
|
||||
|
||||
/* 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 and config.wrapEvents
|
||||
*
|
||||
* argument title string - The title to transform.
|
||||
*
|
||||
* return string - The transformed title.
|
||||
*/
|
||||
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, this.config.wrapEvents);
|
||||
return title;
|
||||
},
|
||||
|
||||
/* broadcastEvents()
|
||||
* Broadcasts the events to all other modules for reuse.
|
||||
* The all events available in one array, sorted on startdate.
|
||||
*/
|
||||
broadcastEvents: function () {
|
||||
var eventList = [];
|
||||
for (var url in this.calendarData) {
|
||||
var calendar = this.calendarData[url];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
eventList.sort(function(a,b) {
|
||||
return a.startDate - b.startDate;
|
||||
});
|
||||
|
||||
this.sendNotification("CALENDAR_EVENTS", eventList);
|
||||
|
||||
}
|
||||
});
|
295
modules/default/calendar/calendarfetcher.js
Normal file
295
modules/default/calendar/calendarfetcher.js
Normal file
@@ -0,0 +1,295 @@
|
||||
/* Magic Mirror
|
||||
* Node Helper: Calendar - CalendarFetcher
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var ical = require("./vendor/ical.js");
|
||||
var moment = require("moment");
|
||||
|
||||
var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth) {
|
||||
var self = this;
|
||||
|
||||
var reloadTimer = null;
|
||||
var events = [];
|
||||
|
||||
var fetchFailedCallback = function() {};
|
||||
var eventsReceivedCallback = function() {};
|
||||
|
||||
/* fetchCalendar()
|
||||
* Initiates calendar fetch.
|
||||
*/
|
||||
var fetchCalendar = function() {
|
||||
|
||||
clearTimeout(reloadTimer);
|
||||
reloadTimer = null;
|
||||
|
||||
nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
|
||||
var opts = {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Node.js "+ nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)"
|
||||
}
|
||||
};
|
||||
|
||||
if (auth) {
|
||||
if(auth.method === "bearer"){
|
||||
opts.auth = {
|
||||
bearer: auth.pass
|
||||
}
|
||||
|
||||
}else{
|
||||
opts.auth = {
|
||||
user: auth.user,
|
||||
pass: auth.pass
|
||||
};
|
||||
|
||||
if(auth.method === "digest"){
|
||||
opts.auth.sendImmediately = false;
|
||||
}else{
|
||||
opts.auth.sendImmediately = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ical.fromURL(url, opts, function(err, data) {
|
||||
if (err) {
|
||||
fetchFailedCallback(self, err);
|
||||
scheduleTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log(data);
|
||||
newEvents = [];
|
||||
|
||||
var limitFunction = function(date, i) {return i < maximumEntries;};
|
||||
|
||||
var eventDate = function(event, time) {
|
||||
return (event[time].length === 8) ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time]));
|
||||
};
|
||||
|
||||
for (var e in data) {
|
||||
var event = data[e];
|
||||
var now = new Date();
|
||||
var today = moment().startOf("day").toDate();
|
||||
var future = moment().startOf("day").add(maximumNumberOfDays, "days").subtract(1,"seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat.
|
||||
|
||||
// FIXME:
|
||||
// Ugly fix to solve the facebook birthday issue.
|
||||
// Otherwise, the recurring events only show the birthday for next year.
|
||||
var isFacebookBirthday = false;
|
||||
if (typeof event.uid !== "undefined") {
|
||||
if (event.uid.indexOf("@facebook.com") !== -1) {
|
||||
isFacebookBirthday = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type === "VEVENT") {
|
||||
|
||||
var startDate = eventDate(event, "start");
|
||||
var endDate;
|
||||
if (typeof event.end !== "undefined") {
|
||||
endDate = eventDate(event, "end");
|
||||
} else {
|
||||
if (!isFacebookBirthday) {
|
||||
endDate = startDate;
|
||||
} else {
|
||||
endDate = moment(startDate).add(1, "days");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// calculate the duration f the event for use with recurring events.
|
||||
var duration = parseInt(endDate.format("x")) - parseInt(startDate.format("x"));
|
||||
|
||||
if (event.start.length === 8) {
|
||||
startDate = startDate.startOf("day");
|
||||
}
|
||||
|
||||
var title = "Event";
|
||||
if (event.summary) {
|
||||
title = (typeof event.summary.val !== "undefined") ? event.summary.val : event.summary;
|
||||
} else if(event.description) {
|
||||
title = event.description;
|
||||
}
|
||||
|
||||
var excluded = false;
|
||||
for (var f in excludedEvents) {
|
||||
var filter = excludedEvents[f];
|
||||
if (title.toLowerCase().includes(filter.toLowerCase())) {
|
||||
excluded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (excluded) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var location = event.location || false;
|
||||
var geo = event.geo || false;
|
||||
var description = event.description || false;
|
||||
|
||||
if (typeof event.rrule != "undefined" && !isFacebookBirthday) {
|
||||
var rule = event.rrule;
|
||||
var dates = rule.between(today, future, true, limitFunction);
|
||||
|
||||
for (var d in dates) {
|
||||
startDate = moment(new Date(dates[d]));
|
||||
endDate = moment(parseInt(startDate.format("x")) + duration, "x");
|
||||
if (endDate.format("x") > now) {
|
||||
newEvents.push({
|
||||
title: title,
|
||||
startDate: startDate.format("x"),
|
||||
endDate: endDate.format("x"),
|
||||
fullDayEvent: isFullDayEvent(event),
|
||||
class: event.class,
|
||||
firstYear: event.start.getFullYear(),
|
||||
location: location,
|
||||
geo: geo,
|
||||
description: description
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// console.log("Single event ...");
|
||||
// Single event.
|
||||
var fullDayEvent = (isFacebookBirthday) ? true : isFullDayEvent(event);
|
||||
|
||||
if (!fullDayEvent && endDate < new Date()) {
|
||||
//console.log("It's not a fullday event, and it is in the past. So skip: " + title);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fullDayEvent && endDate <= today) {
|
||||
//console.log("It's a fullday event, and it is before today. So skip: " + title);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (startDate > future) {
|
||||
//console.log("It exceeds the maximumNumberOfDays limit. So skip: " + title);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Every thing is good. Add it to the list.
|
||||
|
||||
newEvents.push({
|
||||
title: title,
|
||||
startDate: startDate.format("x"),
|
||||
endDate: endDate.format("x"),
|
||||
fullDayEvent: fullDayEvent,
|
||||
class: event.class,
|
||||
location: location,
|
||||
geo: geo,
|
||||
description: description
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newEvents.sort(function(a, b) {
|
||||
return a.startDate - b.startDate;
|
||||
});
|
||||
|
||||
//console.log(newEvents);
|
||||
|
||||
events = newEvents.slice(0, maximumEntries);
|
||||
|
||||
self.broadcastEvents();
|
||||
scheduleTimer();
|
||||
});
|
||||
};
|
||||
|
||||
/* scheduleTimer()
|
||||
* Schedule the timer for the next update.
|
||||
*/
|
||||
var scheduleTimer = function() {
|
||||
//console.log('Schedule update timer.');
|
||||
clearTimeout(reloadTimer);
|
||||
reloadTimer = setTimeout(function() {
|
||||
fetchCalendar();
|
||||
}, reloadInterval);
|
||||
};
|
||||
|
||||
/* isFullDayEvent(event)
|
||||
* Checks if an event is a fullday event.
|
||||
*
|
||||
* argument event obejct - The event object to check.
|
||||
*
|
||||
* return bool - The event is a fullday event.
|
||||
*/
|
||||
var isFullDayEvent = function(event) {
|
||||
if (event.start.length === 8) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var start = event.start || 0;
|
||||
var startDate = new Date(start);
|
||||
var end = event.end || 0;
|
||||
|
||||
if (end - start === 24 * 60 * 60 * 1000 && startDate.getHours() === 0 && startDate.getMinutes() === 0) {
|
||||
// Is 24 hours, and starts on the middle of the night.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/* public methods */
|
||||
|
||||
/* startFetch()
|
||||
* Initiate fetchCalendar();
|
||||
*/
|
||||
this.startFetch = function() {
|
||||
fetchCalendar();
|
||||
};
|
||||
|
||||
/* broadcastItems()
|
||||
* Broadcast the existing events.
|
||||
*/
|
||||
this.broadcastEvents = function() {
|
||||
//console.log('Broadcasting ' + events.length + ' events.');
|
||||
eventsReceivedCallback(self);
|
||||
};
|
||||
|
||||
/* onReceive(callback)
|
||||
* Sets the on success callback
|
||||
*
|
||||
* argument callback function - The on success callback.
|
||||
*/
|
||||
this.onReceive = function(callback) {
|
||||
eventsReceivedCallback = callback;
|
||||
};
|
||||
|
||||
/* onError(callback)
|
||||
* Sets the on error callback
|
||||
*
|
||||
* argument callback function - The on error callback.
|
||||
*/
|
||||
this.onError = function(callback) {
|
||||
fetchFailedCallback = callback;
|
||||
};
|
||||
|
||||
/* url()
|
||||
* Returns the url of this fetcher.
|
||||
*
|
||||
* return string - The url of this fetcher.
|
||||
*/
|
||||
this.url = function() {
|
||||
return url;
|
||||
};
|
||||
|
||||
/* events()
|
||||
* Returns current available events for this fetcher.
|
||||
*
|
||||
* return array - The current available events for this fetcher.
|
||||
*/
|
||||
this.events = function() {
|
||||
return events;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
module.exports = CalendarFetcher;
|
40
modules/default/calendar/debug.js
Normal file
40
modules/default/calendar/debug.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* CalendarFetcher Tester
|
||||
* use this script with `node debug.js` to test the fetcher without the need
|
||||
* of starting the MagicMirror core. Adjust the values below to your desire.
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var CalendarFetcher = require("./calendarfetcher.js");
|
||||
|
||||
var url = "https://calendar.google.com/calendar/ical/pkm1t2uedjbp0uvq1o7oj1jouo%40group.calendar.google.com/private-08ba559f89eec70dd74bbd887d0a3598/basic.ics"; // Standard test URL
|
||||
// var url = "https://www.googleapis.com/calendar/v3/calendars/primary/events/"; // URL for Bearer auth (must be configured in Google OAuth2 first)
|
||||
var fetchInterval = 60 * 60 * 1000;
|
||||
var maximumEntries = 10;
|
||||
var maximumNumberOfDays = 365;
|
||||
var user = "magicmirror";
|
||||
var pass = "MyStrongPass";
|
||||
|
||||
var auth = {
|
||||
user: user,
|
||||
pass: pass
|
||||
};
|
||||
|
||||
console.log("Create fetcher ...");
|
||||
|
||||
fetcher = new CalendarFetcher(url, fetchInterval, maximumEntries, maximumNumberOfDays, auth);
|
||||
|
||||
fetcher.onReceive(function(fetcher) {
|
||||
console.log(fetcher.events());
|
||||
console.log("------------------------------------------------------------");
|
||||
});
|
||||
|
||||
fetcher.onError(function(fetcher, error) {
|
||||
console.log("Fetcher error:");
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
fetcher.startFetch();
|
||||
|
||||
console.log("Create fetcher done! ");
|
78
modules/default/calendar/node_helper.js
Normal file
78
modules/default/calendar/node_helper.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/* Magic Mirror
|
||||
* Node Helper: Calendar
|
||||
*
|
||||
* By Michael Teeuw http://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*/
|
||||
|
||||
var NodeHelper = require("node_helper");
|
||||
var validUrl = require("valid-url");
|
||||
var CalendarFetcher = require("./calendarfetcher.js");
|
||||
|
||||
module.exports = NodeHelper.create({
|
||||
// Override start method.
|
||||
start: function() {
|
||||
var events = [];
|
||||
|
||||
this.fetchers = [];
|
||||
|
||||
console.log("Starting node helper for: " + this.name);
|
||||
|
||||
},
|
||||
|
||||
// Override socketNotificationReceived method.
|
||||
socketNotificationReceived: function(notification, payload) {
|
||||
if (notification === "ADD_CALENDAR") {
|
||||
//console.log('ADD_CALENDAR: ');
|
||||
this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth);
|
||||
}
|
||||
},
|
||||
|
||||
/* createFetcher(url, reloadInterval)
|
||||
* Creates a fetcher for a new url if it doesn't exist yet.
|
||||
* Otherwise it reuses the existing one.
|
||||
*
|
||||
* attribute url string - URL of the news feed.
|
||||
* attribute reloadInterval number - Reload interval in milliseconds.
|
||||
*/
|
||||
|
||||
createFetcher: function(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth) {
|
||||
var self = this;
|
||||
|
||||
if (!validUrl.isUri(url)) {
|
||||
self.sendSocketNotification("INCORRECT_URL", {url: url});
|
||||
return;
|
||||
}
|
||||
|
||||
var fetcher;
|
||||
if (typeof self.fetchers[url] === "undefined") {
|
||||
console.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval);
|
||||
fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth);
|
||||
|
||||
fetcher.onReceive(function(fetcher) {
|
||||
//console.log('Broadcast events.');
|
||||
//console.log(fetcher.events());
|
||||
|
||||
self.sendSocketNotification("CALENDAR_EVENTS", {
|
||||
url: fetcher.url(),
|
||||
events: fetcher.events()
|
||||
});
|
||||
});
|
||||
|
||||
fetcher.onError(function(fetcher, error) {
|
||||
self.sendSocketNotification("FETCH_ERROR", {
|
||||
url: fetcher.url(),
|
||||
error: error
|
||||
});
|
||||
});
|
||||
|
||||
self.fetchers[url] = fetcher;
|
||||
} else {
|
||||
//console.log('Use existing news fetcher for url: ' + url);
|
||||
fetcher = self.fetchers[url];
|
||||
fetcher.broadcastEvents();
|
||||
}
|
||||
|
||||
fetcher.startFetch();
|
||||
}
|
||||
});
|
6
modules/default/calendar/vendor/ical.js/.travis.yml
vendored
Normal file
6
modules/default/calendar/vendor/ical.js/.travis.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- "0.12"
|
||||
- "4.2"
|
||||
install: npm install
|
178
modules/default/calendar/vendor/ical.js/LICENSE
vendored
Normal file
178
modules/default/calendar/vendor/ical.js/LICENSE
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
|
||||
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
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user