Compare commits

...

267 Commits

Author SHA1 Message Date
Michael Teeuw
26a76f80d6 Merge pull request #2607 from MichMich/develop
Release v2.16.0
2021-07-01 14:30:00 +02:00
Michael Teeuw
53ead2087f Prepare v2.16.0 2021-07-01 14:08:10 +02:00
Michael Teeuw
faee811d67 Merge pull request #2605 from rico24/patch-1
Update nl.json
2021-06-29 09:56:35 +02:00
Michael Teeuw
b9c739df1f Fix prettier issues. 2021-06-29 09:49:13 +02:00
rico24
6e124842e8 Merge branch 'develop' into patch-1 2021-06-28 22:38:10 +02:00
rico24
7a5928ea24 Update CHANGELOG.md 2021-06-28 21:03:05 +02:00
Michael Teeuw
7fdf7de11c Merge pull request #2600 from njwilliams/njw/openweathermap-onecall-fix 2021-06-25 11:32:49 +02:00
Michael Teeuw
b75eedb84e Merge pull request #2604 from khassel/update_deps 2021-06-25 11:01:08 +02:00
rico24
3b92ae49a9 Update nl.json
Added the missing variables based on the en.json file.
2021-06-25 10:58:05 +02:00
Nick Williams
775d1091db typo when not using /onecall - was getting forecast from wrong attr 2021-06-24 21:39:32 -04:00
Karsten Hassel
1f77b491fc update dependencies 2021-06-24 23:01:29 +02:00
Michael Teeuw
eff2fd7cc0 Merge pull request #2603 from khassel/dev_console_test 2021-06-24 08:38:49 +02:00
Karsten Hassel
58d2a0d874 Merge branch 'develop' into dev_console_test
# Conflicts:
#	CHANGELOG.md
2021-06-23 23:14:19 +02:00
Michael Teeuw
ea9def997a Merge pull request #2602 from khassel/jest 2021-06-22 08:31:05 +02:00
Karsten Hassel
a222c58047 use --forceExit running jest, define timeouts for github workflows 2021-06-21 22:27:36 +02:00
Michael Teeuw
39a838c2ab Merge pull request #2601 from khassel/jest 2021-06-20 14:45:41 +02:00
Karsten Hassel
cfc0bcd5ad workaround for dev_console test using getWindowCount 2021-06-18 21:39:55 +02:00
Karsten Hassel
3418c9b50f revert changes to dev_console test (has impact on other tests), add CHANGELOG 2021-06-18 12:42:13 +02:00
Nick Williams
e686611890 update One Call API results for openweathermap
The results from the /onecall endpoint were not
being parsed correctly in current and forecast mode - it was
assuming the current/forecast endpoint API, and the return
datasets are different. The effect was that the module
would simply display "Loading..." when in /onecall mode, since
it has no way of displaying error status (ideally, it should,
but leave that for another day)
2021-06-17 19:30:56 -04:00
karsten13
ebb5dee1fc run prettier 2021-06-17 23:19:57 +02:00
Karsten Hassel
d9edaffd9c reset changes on js/logger.js, mock logger.js in global_vars tests, workaround for failing dev_console test 2021-06-17 22:50:26 +02:00
Michael Teeuw
cbe7b1a5b9 Merge pull request #2599 from khassel/jest 2021-06-17 07:06:36 +02:00
Karsten Hassel
e758fd4093 add CHANGELOG 2021-06-16 20:54:03 +02:00
Karsten Hassel
14a99a3b25 Merge branch 'develop' into jest 2021-06-16 20:53:02 +02:00
Karsten Hassel
1ba67506a0 fix logger.js after jest changes 2021-06-16 20:51:32 +02:00
Michael Teeuw
2a6ca5d5ac Merge pull request #2581 from Crazylegstoo/envCanadaFix
Fix to Environment Canada weather provider
2021-06-16 10:07:48 +02:00
Michael Teeuw
aa12e6495a Merge pull request #2593 from khassel/jest
replace mocha with jest
2021-06-16 10:06:32 +02:00
Karsten Hassel
9269848f66 Merge branch 'develop' into jest
# Conflicts:
#	CHANGELOG.md
2021-06-15 23:32:22 +02:00
Karsten Hassel
a71e61cd30 use short if in weatherforecast.js, add comment to logger.js 2021-06-15 23:29:07 +02:00
Michael Teeuw
8be4604c97 Merge pull request #2580 from r3wald/feature/newsfeed-show-as-list
feature: show newsfeed as list
2021-06-15 11:14:15 +02:00
Michael Teeuw
a7bba903f5 Merge branch 'develop' into envCanadaFix 2021-06-15 11:13:28 +02:00
Karsten Hassel
1d8af5835d Merge branch 'develop' into jest
# Conflicts:
#	package-lock.json
#	package.json
2021-06-14 23:21:08 +02:00
Karsten Hassel
189c01fc74 update moduleNameMapper 2021-06-14 22:45:21 +02:00
Karsten Hassel
e3a5bbf661 use expect where missing 2021-06-14 21:07:38 +02:00
Karsten Hassel
ee23c5f72c update clock tests, remove testSequencer 2021-06-14 19:21:21 +02:00
Karsten Hassel
a2083be76b test order 2021-06-14 00:45:40 +02:00
Michael Teeuw
a1c4be83d6 Merge pull request #2578 from rejas/docs 2021-06-13 19:27:24 +02:00
Karsten Hassel
d2fde2bfc8 fix segment faults in node16 on ubuntu 2021-06-12 23:53:04 +02:00
Karsten Hassel
e8956b0b55 update package-lock.json 2021-06-12 22:52:05 +02:00
Karsten Hassel
2af4009a93 change missing translations log, disable console.log in unit tests 2021-06-12 22:26:02 +02:00
Karsten Hassel
a5f7c946cc jest setup changes, increase setMaxListeners, disable console.log in tests/unit/global_vars 2021-06-12 21:26:08 +02:00
Karsten Hassel
298542b531 enable missing translation messages 2021-06-11 23:42:24 +02:00
Karsten Hassel
fca6707a29 add eslint-plugin-jest 2021-06-11 23:11:30 +02:00
Karsten Hassel
10d3a284e9 fix js lint warnings 2021-06-11 23:10:41 +02:00
karsten13
1a244726aa run prettier 2021-06-11 22:24:21 +02:00
Karsten Hassel
044935a164 add jest options 2021-06-11 22:18:05 +02:00
Karsten Hassel
99e5edf2c5 add CHANGELOG.md 2021-06-11 01:00:05 +02:00
Karsten Hassel
b26270bd13 fix helloworld_spec tests 2021-06-11 00:36:53 +02:00
Karsten Hassel
65a8cb9ddb remove unused references 2021-06-11 00:32:54 +02:00
Karsten Hassel
ba4b976e80 test config 2021-06-10 00:24:08 +02:00
veeck
297ae1dbaf Try updating dependencies 2021-06-09 13:09:28 +02:00
veeck
214614f740 Cleanup jsdoc via eslint 2021-06-09 12:49:57 +02:00
Karsten Hassel
0e14d3d6e8 snapshot e2e 2021-06-09 00:19:43 +02:00
Karsten Hassel
67011c0c32 snapshot e2e 2021-06-08 00:47:40 +02:00
Karsten Hassel
16bbb42b8d remove chai from unit tests 2021-06-08 00:47:15 +02:00
Kevin G
b85ac91e6c Corrected bullet for Updated list
Wording/location corrected for update to WEATHER module
2021-06-07 09:28:33 -04:00
Karsten Hassel
66759a33fa unit tests 2021-06-07 00:16:53 +02:00
Kevin G
bf5e83861c Highlight new custom precipitation unit-of-measure for WEATHER
With the new Environment Canada provider in WEATHER, it is now possible for provider to set a customized precipitation unit-of-measure
2021-06-05 11:39:21 -04:00
Robert Ewald
d56a6fb06f disabled by default. 2021-06-03 12:45:08 +02:00
Robert Ewald
5e7aa8e16d changelog updated. 2021-06-03 12:42:39 +02:00
Robert Ewald
bace0ad339 indentation in template fixed. 2021-06-03 12:38:54 +02:00
Robert Ewald
6014eaf8eb Merge remote-tracking branch 'upstream/develop' into feature/newsfeed-show-as-list 2021-06-03 12:27:26 +02:00
Robert Ewald
3e96e8b3f5 newsfeed: showAsList implemented. 2021-06-03 11:40:18 +02:00
Kevin G
95d1b8a6d0 Corrected formating 2021-06-02 16:25:53 -04:00
Kevin G
0ecb66c99e Fixed precipitation unit conversion logic
Found an error that precipitation amount was not being calculated correctly when config.js is asking for Imperial units. This has been fixed.
2021-06-02 16:15:44 -04:00
Kevin G
af52b91799 Fix to precipitation logic
Found a really dumb error I made that broke compatibility with OpenWeatherMap hourly forecast under certain conditions. This is now fixed.
2021-06-02 11:43:56 -04:00
veeck
3c50d6c30a Update CHANGELOG 2021-05-29 16:23:53 +02:00
veeck
2722c72c43 Update issue template 2021-05-29 16:23:41 +02:00
rejas
d5ab3101c6 Update links to config documentation 2021-05-29 16:23:41 +02:00
Michael Teeuw
32df76bdff Merge pull request #2577 from rejas/prettier 2021-05-29 16:22:52 +02:00
rejas
68a06c3d1d Update CHANGELOG 2021-05-29 16:13:24 +02:00
rejas
1b42dc779b Run prettier again 2021-05-29 16:11:39 +02:00
rejas
cedffd40f2 Lint all files, exclude others 2021-05-29 16:11:25 +02:00
Michael Teeuw
cdc8db4837 Merge pull request #2576 from rejas/calendar_fix 2021-05-29 14:02:13 +02:00
Michael Teeuw
a0ee23d84e Merge pull request #2568 from Crazylegstoo/envcanada 2021-05-29 14:01:04 +02:00
Michael Teeuw
49d2d8c9d0 Merge pull request #2574 from daniel-windsor/forecase-ignore-today 2021-05-29 13:59:55 +02:00
rejas
63620aa811 Update CHANGELOG 2021-05-29 13:33:41 +02:00
rejas
d5b11a1dba Update dependencies 2021-05-29 13:32:24 +02:00
rejas
4a63af0490 Set error to null once the events are coming again 2021-05-29 13:22:31 +02:00
Michael Teeuw
a68019293f Merge pull request #2567 from khassel/fix_husky 2021-05-27 07:14:56 +02:00
Karsten Hassel
6b1c91f0dd Merge branch 'develop' into fix_husky
# Conflicts:
#	CHANGELOG.md
2021-05-26 20:20:55 +02:00
Michael Teeuw
ea93785581 Merge pull request #2552 from rejas/error_handling 2021-05-26 20:12:17 +02:00
Karsten Hassel
32819c4fd5 Merge pull request #1 from rejas/fix_pretty_quick_too
Fix call to pretty quick, make husky script executable
2021-05-26 19:49:36 +02:00
Daniel Windsor
fc5a438cdc Add flag that removes today entry in forecast 2021-05-26 21:45:24 +12:00
veeck
57fe94f945 Fix call to pretty quick, make husky script executable 2021-05-26 11:39:29 +02:00
Robert Ewald
aa3a3bdf16 server data for newsfeed list. 2021-05-25 09:29:48 +02:00
Kevin G
db89da3daa Ran prettier 2021-05-21 12:11:58 -04:00
Kevin G
d6ba5796ce ran prettier... 2021-05-21 11:48:56 -04:00
Kevin G
3968743b28 ran prettier... 2021-05-21 11:45:54 -04:00
Kevin G
20c6226b84 Update for new weather provider 2021-05-21 10:20:16 -04:00
Kevin G
463ce394fe ran prettier... 2021-05-21 10:13:39 -04:00
Kevin G
1faefebe42 Formatting correction for min/max display 2021-05-21 10:06:54 -04:00
Karsten Hassel
e2c9339ec4 fix husky setup in prod environment 2021-05-19 20:58:29 +02:00
rejas
4b1c7da171 Update jsdoc 2021-05-19 11:12:56 +02:00
rejas
bdfd6e5e9f Fix calendar test 2021-05-19 11:12:56 +02:00
rejas
4c8508b0a9 Fix newsfeed test 2021-05-19 11:12:56 +02:00
veeck
06b3f92963 Refaktor calendar error once again for better messaging to the user 2021-05-19 11:12:56 +02:00
veeck
d43a57af36 Refaktor newsfeed error once again for better messaging to the user 2021-05-19 11:12:56 +02:00
rejas
aeefe28710 Update jsdoc 2021-05-19 11:12:56 +02:00
rejas
e9de961a23 Cleanup prohibited words filter code and test data 2021-05-19 11:12:56 +02:00
rejas
dcec778e02 Update CHANGELOG 2021-05-19 11:12:56 +02:00
rejas
b212641069 Move checkFetchStatus into NodelHelper 2021-05-19 11:12:42 +02:00
rejas
90aa50bb11 Add fetcher_helper for calendar and newsfeed 2021-05-19 11:12:42 +02:00
rejas
a6879e853b Fix tests 2021-05-19 11:12:42 +02:00
rejas
37fab7ac63 Update error handling for newsfeed and calendar 2021-05-19 11:12:42 +02:00
Michael Teeuw
8b01ae08c5 Merge pull request #2566 from khassel/update_deps 2021-05-19 11:07:43 +02:00
Michael Teeuw
b1cdf42790 Merge pull request #2565 from rejas/css_gap 2021-05-19 11:07:10 +02:00
Karsten Hassel
974968d238 update CHANGELOG.md 2021-05-18 20:44:43 +02:00
karsten13
bf467cbba5 add package-lock.json 2021-05-18 20:42:06 +02:00
Karsten Hassel
536aa2e96e update dependencies and migrate husky to v6 2021-05-18 20:36:56 +02:00
rejas
3d84344b75 Revert "Update dependencies, run linter"
This reverts commit 1054ba3b1e.
2021-05-14 22:06:55 +02:00
rejas
1054ba3b1e Update dependencies, run linter 2021-05-14 21:46:18 +02:00
rejas
aa8ddb9a92 Update CHANGELOG 2021-05-14 21:20:33 +02:00
rejas
fcfe57e5e2 Add more properties for finer body margins 2021-05-14 17:42:23 +02:00
Kevin G
c4fd4e0317 New provider - Environment Canada
Added a new provider that pulls weather data - current, forecast, and hourly - from Environment Canada (Canadian gov weather service). This provider supports Canadian locations only. Documentation will be provided that outlines specific behaviours of this provider and required 'weather' modules parms for Env Canada.
2021-05-13 11:12:30 -04:00
Kevin G
3c76933824 Edit Update
Formatting
2021-05-13 11:00:43 -04:00
Kevin G
fa83819bee Edit update
Formatting
2021-05-13 10:59:45 -04:00
Kevin G
b65ae88879 Edit update
Formatting
2021-05-13 10:58:08 -04:00
Kevin G
96db21f9bf Updates to support Environment Canada provider
Code updates to support a new weatherobject element called precipitationUnits. For the 'forecast' and 'hourly' UI, the weather module will use this new element if it is not null (i.e. a provider has pushed some value into this object element). If the element is null, then default 'units' processing will still occur. This allows a provider to have a customn unit of measure for precipitation - e.g. Env Canada will use mm for rain and cm for snow.
2021-05-13 10:56:30 -04:00
veeck
6d356ff770 Introduce font-size-small property and use it in calendar too (clean it up too) 2021-05-08 18:29:53 +02:00
veeck
21790b32bf Use hex notation instead of verbose name like everywhere else 2021-05-08 18:07:38 +02:00
rejas
159f3d0aa2 Replace deprecated symbol with modern solution 2021-05-08 18:05:30 +02:00
veeck
012a7b0678 Convert one px to rem 2021-05-08 18:04:14 +02:00
veeck
6595c85671 Remove useless(?) margins 2021-05-08 18:04:14 +02:00
veeck
cf5c0464fe Use custom property for gaps between modules 2021-05-08 18:04:14 +02:00
Michael Teeuw
e31450f731 Merge pull request #2560 from foundations-design/contribute 2021-05-08 08:50:00 +02:00
Michael Teeuw
3653984a95 Merge pull request #2562 from rejas/config_check 2021-05-08 08:49:10 +02:00
Michael Teeuw
0c0b856c37 Merge pull request #2561 from khassel/develop 2021-05-08 08:48:43 +02:00
Michael Teeuw
69f1b153ea Merge pull request #2559 from rejas/issue_2547 2021-05-08 08:46:44 +02:00
veeck
43ba4bd00e Fix calendar debug 2021-05-07 12:28:55 +02:00
veeck
bf5edcaac6 Fix failing config check when es6 notation is used 2021-05-07 12:23:29 +02:00
Karsten Hassel
ac51709211 update dependencies in package.json, require node >= v12, remove rrule and rrule-alt 2021-05-04 21:49:16 +02:00
Earl Man
e1a578e819 add myself to CHANGELOG.md 2021-05-04 09:25:36 -05:00
rejas
591c9e53b0 Update CHANGELOG 2021-05-02 15:07:49 +02:00
rejas
87d543eb3a Finish test case 2021-05-02 15:07:02 +02:00
rejas
40c1521591 Add test data 2021-05-02 14:55:45 +02:00
Michael Teeuw
b31c2a6264 Add contributors list.
The full list will be checked during release.
2021-04-29 10:32:20 +02:00
Michael Teeuw
66a42f13f1 Merge pull request #2546 from rejas/move 2021-04-27 16:27:55 +02:00
veeck
8de6ebbbd1 Update dependencies 2021-04-27 13:14:27 +02:00
rejas
649de694ed Update CHANGELOG 2021-04-27 13:14:27 +02:00
veeck
8a52fde8fc Move codecov.yml into github dir to other configs 2021-04-27 13:14:07 +02:00
rejas
fb8bd657de Move weatherforecast mock data in better suited directory 2021-04-27 13:14:07 +02:00
Michael Teeuw
b04a0a6b61 Merge pull request #2556 from khassel/updatenotification 2021-04-27 07:21:06 +02:00
Michael Teeuw
f29c911a0f Merge pull request #2557 from khassel/node16 2021-04-27 07:20:31 +02:00
Karsten Hassel
bd908123c2 replace node v10 with v16 in github workflow 2021-04-26 22:29:23 +02:00
Karsten Hassel
cbdb0b67ab fix updatenotification.js increasing timeout 2021-04-26 22:24:07 +02:00
Michael Teeuw
aa3848f420 Merge pull request #2544 from rejas/es6_conversion 2021-04-18 18:14:25 +02:00
rejas
6cf0748172 Update CHANGELOG 2021-04-18 18:07:26 +02:00
rejas
11122d3f81 Use es6 notation in clientonly 2021-04-18 15:36:00 +02:00
rejas
2dea9398f2 Use es6 notation in updatenotification module 2021-04-18 15:35:41 +02:00
rejas
de93b3294f Use es6 notation in main.js 2021-04-18 15:29:10 +02:00
rejas
7accb84eb9 Use es6 notation in config sample 2021-04-18 15:06:28 +02:00
rejas
ea90ed04d6 Use es6 notation in vendor 2021-04-18 15:06:21 +02:00
rejas
838eed2630 Use es6 notation in weatherprovider 2021-04-18 15:06:06 +02:00
rejas
376b65c749 Use es6 notation in serveronly 2021-04-18 14:52:04 +02:00
rejas
3b4432cb00 Use es6 notation in defaults 2021-04-18 14:51:57 +02:00
rejas
7bc71029de Use es6 notation in socketclient 2021-04-18 14:51:50 +02:00
rejas
d736dd92be Use es6 notation in tests 2021-04-18 14:51:28 +02:00
rejas
6eba8d681c Use es6 notation in module 2021-04-17 16:29:38 +02:00
rejas
5fe654c19d Use es6 notation in loader 2021-04-17 16:29:38 +02:00
rejas
dd366f35a8 Use es6 notation in test configs 2021-04-17 16:29:38 +02:00
rejas
2ababa521d Use es6 notation in weather module and ukmet provider 2021-04-17 16:29:38 +02:00
rejas
bda8f26511 Use es6 notation in compliments module and cleanup jsdoc 2021-04-17 16:29:38 +02:00
rejas
7f1a3df25b Use es6 notation in font tests 2021-04-17 16:29:38 +02:00
rejas
ef2ff50089 Use es6 notation in clock module 2021-04-17 16:29:38 +02:00
rejas
0b3964c827 Use es6 notation in helloworld tests 2021-04-17 16:29:38 +02:00
Michael Teeuw
ccf5bb9342 Merge pull request #2540 from rejas/patch-1 2021-04-17 15:52:38 +02:00
Michael Teeuw
4303882c6a Merge pull request #2542 from rejas/codecov 2021-04-17 15:50:49 +02:00
Michael Teeuw
c34028d549 Merge pull request #2541 from rejas/css 2021-04-17 15:50:18 +02:00
rejas
8e76cdcb57 Revert "Remove some tests to see if it still errors"
This reverts commit 0abebc1e32.
2021-04-17 13:26:16 +02:00
rejas
5eb66106b9 Rename yaml file 2021-04-17 13:17:24 +02:00
rejas
0abebc1e32 Remove some tests to see if it still errors 2021-04-17 13:05:06 +02:00
rejas
552e82f44d Update CHANGELOG 2021-04-17 12:35:46 +02:00
rejas
ada40e36db Add codecov yaml to set informational mode 2021-04-17 12:35:10 +02:00
rejas
480f734a06 Update CHANGELOG, undo accidental commit in clock.js 2021-04-17 12:20:25 +02:00
rejas
20bee9c334 Update dependencies 2021-04-17 11:19:39 +02:00
rejas
de8267f41e Fix css comment style 2021-04-17 10:27:36 +02:00
rejas
7bfaf07980 Use custom properties colors more often 2021-04-17 09:50:12 +02:00
rejas
a42fa8e9f9 Adjust default css to match styles from befor custom properties merge 2021-04-17 09:38:33 +02:00
Veeck
5facad683a Add link to subforum to README
As propose din #2539
2021-04-17 06:46:30 +02:00
Michael Teeuw
fd1913a72e Merge pull request #2538 from codac/master 2021-04-15 16:16:51 +02:00
Michael Teeuw
54c98b4250 Merge pull request #2537 from rejas/custom-properties 2021-04-15 16:15:27 +02:00
config
5d99baac21 Fixing fetch option httpsAgent to agent in calendar module (#466) 2021-04-15 14:51:33 +02:00
veeck
cb95bdf6d7 Undo script changes, Update CHANGELOG 2021-04-14 17:03:09 +02:00
veeck
25e803abfc Update custom.css.sample 2021-04-14 16:56:29 +02:00
veeck
7c6073e4ef Undo README changes 2021-04-14 16:45:38 +02:00
veeck
09bcbe8dfc Update dependencies and lock files 2021-04-14 16:45:07 +02:00
veeck
e0a9c7d0bb Merge branch 'develop' into contribute 2021-04-14 16:42:54 +02:00
Michael Teeuw
ac27d05fd5 Merge pull request #2532 from ezeholz/develop
Alert Module not recognizing multiple alerts
2021-04-14 16:17:45 +02:00
Ezequiel Holzweissig
d6ab56252f Merge branch 'develop' into develop 2021-04-13 20:56:32 -03:00
Michael Teeuw
dab178ed50 Merge pull request #2536 from B1gG/develop
Develop
2021-04-13 14:38:17 +02:00
B1gG
6ed50b6a75 updating changelog 2021-04-13 07:39:14 +01:00
B1gG
ee559ec650 to use dateFormat when timeFormat is relative 2021-04-13 07:29:11 +01:00
Michael Teeuw
4310238418 Merge pull request #2527 from rejas/patch-1 2021-04-12 21:03:27 +02:00
veeck
10c47a6c38 Really run prettier 2021-04-12 19:33:23 +02:00
veeck
4b8043086e Merge branch 'develop' into patch-1 2021-04-12 19:28:51 +02:00
Michael Teeuw
fd952b88bf Merge pull request #2535 from rejas/issue_2530 2021-04-12 19:23:07 +02:00
Ezequiel Holzweissig
e262d463c5 Merge branch 'develop' into develop 2021-04-12 14:20:33 -03:00
veeck
b02bce2510 Merge branch 'develop' into issue_2530 2021-04-12 19:11:24 +02:00
Michael Teeuw
58094531c0 Merge pull request #2534 from KristjanESPERANTO/develop 2021-04-12 19:08:36 +02:00
Michael Teeuw
d937e3ca7c Merge pull request #2533 from rejas/alert_test 2021-04-12 19:07:55 +02:00
Kristjan SCHMIDT
9c58413209 Put decimalSymbol at the end of the row like in the Current part 2021-04-12 18:34:33 +02:00
rejas
db65ff12d7 Update CHANGELOG 2021-04-11 22:49:26 +02:00
rejas
f93b819ea6 Fix missing await calls in tests 2021-04-11 22:42:21 +02:00
rejas
e6fea297e5 Add test for decimalSymbol in weatherforecast 2021-04-11 22:25:17 +02:00
Kristjan SCHMIDT
5c2a0e5634 Fix decimalSymbol for forcast 2021-04-11 22:25:15 +02:00
rejas
71aa21a82d Update dependencies 2021-04-11 21:07:26 +02:00
rejas
23821360c7 Update CHANGELOG 2021-04-11 21:03:35 +02:00
rejas
79f5b938f5 Add first test for alert module 2021-04-11 20:59:53 +02:00
Michael Teeuw
f8769fcc2a Merge pull request #2531 from KristjanESPERANTO/patch-1 2021-04-11 12:45:23 +02:00
Ezequiel Holzweissig
a3ed24c766 Prettifier 2021-04-10 20:38:33 -03:00
Ezequiel Holzweissig
82727b825c Fix alerts
Fixes #2522
2021-04-10 20:15:32 -03:00
Kristjan Esperanto
16e98496af Remove duplicate codecov badge
The second badge doesn't make sense, does it?
2021-04-10 23:55:11 +02:00
rejas
62896ce1a3 Merge branch 'develop' into patch-1 2021-04-10 20:37:47 +02:00
Michael Teeuw
7ea5b1ecbf Merge pull request #2526 from FrancoisRmn/add-some-translations 2021-04-10 20:36:11 +02:00
rejas
eecc95f8fb Run linter 2021-04-10 20:34:27 +02:00
FrancoisRmn
85808d85c4 add some translations 2021-04-10 17:56:47 +02:00
Gerardo Gonzalez
12cc670642 Merge branch 'develop' into patch-1 2021-04-10 14:04:53 +01:00
Michael Teeuw
13fcb55df6 Merge pull request #2518 from oemel09/hide-newsfeed-description 2021-04-10 14:56:49 +02:00
oemel09
a4dfd15888 Fixes tests for newsfeed description 2021-04-10 09:13:14 +02:00
Gerardo Gonzalez
7fbd326298 Update CHANGELOG.md
updating the changelog for the fix Fix wrong treatment of `appendLocationNameToHeader` when using `ukmetofficedatahub`
2021-04-10 01:30:12 +01:00
Gerardo Gonzalez
331d147d50 Update ukmetofficedatahub.js
There is a wrong treatment to appendLocationNameToHeader.
The location should be always returned and leave the weater.js to decide if is included or not in the header.
2021-04-10 01:20:52 +01:00
oemel09
acdcdc55bc Adjusts parenthesis in template to be consistent 2021-04-09 08:07:37 +02:00
oemel09
fdb0c0acb3 Adds test to check that newsfeed description does not show up 2021-04-09 08:05:19 +02:00
Earl Man
564bf47fb2 update sample css 2021-04-08 14:56:01 -05:00
Earl Man
eca35b2371 change --base to --font-size 2021-04-08 14:51:07 -05:00
Earl Man
f97d2e2644 revert 'dimming' method 2021-04-08 14:48:23 -05:00
Earl Man
fdd6659139 delete useless file 2021-04-08 14:39:21 -05:00
Earl Man
0d698fb659 Merge remote-tracking branch 'upstream/develop' into contribute 2021-04-08 14:35:39 -05:00
Earl Man
c68b39dda8 Merge branch 'css-properties' 2021-04-08 14:24:39 -05:00
oemel09
cb67286bc3 Adds test to validate that description is shown 2021-04-08 17:00:15 +02:00
oemel09
a49962b8de Moves validation into template 2021-04-08 17:00:15 +02:00
oemel09
256d5ae14f Rebases onto develop 2021-04-08 16:59:46 +02:00
oemel09
799ee8bcfa Check showDescription setting for newsfeed module 2021-04-08 16:59:03 +02:00
Michael Teeuw
fe8a317ef9 Merge pull request #2514 from jupadin/log-consistency
Log consistency
2021-04-08 11:04:11 +02:00
Michael Teeuw
b6d6ee45e0 Fix linter issue. 2021-04-08 10:40:31 +02:00
Michael Teeuw
0151466a28 Merge branch 'develop' into log-consistency 2021-04-08 10:37:18 +02:00
earlman
3588875e28 Update .gitignore 2021-04-04 13:52:18 -05:00
earlman
e8031aec39 add original main.css bak 2021-04-04 13:34:52 -05:00
earlman
1cabe107e6 run prettier 2021-04-04 13:28:56 -05:00
earlman
ba0c59744b update CHANGELOG.md 2021-04-04 13:28:34 -05:00
earlman
50c702c00a adjust new opacities 2021-04-04 13:24:59 -05:00
earlman
ff8c2fe227 add custom.css.sample 2021-04-04 13:22:33 -05:00
earlman
f9c78a5263 use variables for fonts 2021-04-04 13:18:33 -05:00
earlman
d46784a4d5 base font sizes on rem and var(--base) 2021-04-04 13:05:41 -05:00
earlman
ff43d082f2 base line-height on scale 2021-04-04 12:53:35 -05:00
earlman
83801736d7 add color variables 2021-04-04 12:38:17 -05:00
earlman
e16986cf71 Merge branch 'develop' of https://github.com/MichMich/MagicMirror into contribute 2021-04-04 11:55:03 -05:00
earlman
7ad12d954c add original main.css bak 2021-04-04 11:51:04 -05:00
Julian Dinter
fb74fadec2 Run 'npm run lint:prettier'. 2021-04-04 15:37:47 +02:00
Julian Dinter
c00bdf910e Clean up code by adding comments. 2021-04-04 15:22:12 +02:00
Julian Dinter
50cc1c56e5 Consistent log text. 2021-04-04 15:17:42 +02:00
Julian Dinter
0006099758 Changed log type of starting function of updatenotification module. 2021-04-04 14:52:24 +02:00
earlman
162dcec331 Merge branch 'master' of https://github.com/foundations-design/WhiteLight into develop 2021-04-03 11:15:33 -05:00
earlman
3e8bd022e2 add custom variables 2021-04-02 11:28:22 -05:00
earlman
01e5936671 install valid-url 2021-04-02 10:11:38 -05:00
Michael Teeuw
d85e1c70d6 Merge pull request #2512 from jupadin/log-consistency 2021-04-02 12:56:28 +02:00
Julian Dinter
f329770194 Added fix in CHANGELOG. 2021-04-02 12:42:31 +02:00
Julian Dinter
08194925cb Changed log type of starting function of calendar module. 2021-04-02 12:26:34 +02:00
earlman
2b7aa3e810 use yarn 2021-04-01 15:59:48 -05:00
earlman
549417f106 yarn 2021-04-01 15:00:12 -05:00
earlman
f0f370cc7d add *.env to .gitignore 2021-04-01 15:00:06 -05:00
earlman
b842241f8c fix broken start scripts & yarn 2021-04-01 14:08:48 -05:00
earlman
93aa4b7440 update README.md 2021-04-01 14:03:38 -05:00
Michael Teeuw
9e2eef7818 Merge pull request #2511 from khassel/update-node-ical 2021-04-01 20:12:12 +02:00
Karsten Hassel
b29b65851e Bump node-ical to v0.13.0 (now last runtime dependency using deprecated request package is removed). 2021-04-01 20:02:08 +02:00
Michael Teeuw
a6829bff4f Prepare develop: v2.16.0-develop 2021-04-01 14:36:50 +02:00
139 changed files with 15304 additions and 2721 deletions

View File

@@ -1,10 +1,10 @@
{
"extends": ["eslint:recommended", "plugin:prettier/recommended", "plugin:jsdoc/recommended"],
"plugins": ["prettier", "jsdoc"],
"plugins": ["prettier", "jsdoc", "jest"],
"env": {
"browser": true,
"es6": true,
"mocha": true,
"jest/globals": true,
"node": true
},
"globals": {

View File

@@ -1,3 +1,7 @@
Hello and thank you for opening an issue.
**Please make sure that you have read the following lines before submitting your Issue:**
## I'm not sure if this is a bug
If you're not sure if it's a real bug or if it's just you, please open a topic on the forum: [https://forum.magicmirror.builders/category/15/bug-hunt](https://forum.magicmirror.builders/category/15/bug-hunt)

6
.github/codecov.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
coverage:
status:
project:
default:
# advanced settings
informational: true

View File

@@ -11,6 +11,7 @@ on:
jobs:
run-and-upload-coverage-report:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v2
- run: |

View File

@@ -9,6 +9,7 @@ on:
jobs:
check:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v2
- uses: dangoslen/changelog-enforcer@v1.6.1

View File

@@ -12,9 +12,10 @@ on:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
node-version: [12.x, 14.x, 16.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
@@ -28,5 +29,5 @@ jobs:
npm run test:prettier
npm run test:js
npm run test:css
npm run test:e2e
npm run test:unit
npm run test:e2e

1
.husky/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
_

4
.husky/pre-commit Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint:staged

View File

@@ -1,5 +1,8 @@
package-lock.json
/config/**/*
/vendor/**/*
/config
/coverage
/vendor
!/vendor/vendor.js
.github/**/*
.github
.nyc_output
package-lock.json
*.ts

View File

@@ -5,6 +5,61 @@ This project adheres to [Semantic Versioning](https://semver.org/).
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror²
## [2.16.0] - 2021-07-01
Special thanks to the following contributors: @210954, @B1gG, @codac, @Crazylegstoo, @daniel, @earlman, @ezeholz, @FrancoisRmn, @jupadin, @khassel, @KristjanESPERANTO, @njwilliams, @oemel09, @r3wald, @rejas, @rico24, Faizan Ahmed.
### Added
- Added French translations for "MODULE_CONFIG_ERROR" and "PRECIP".
- Added German translation for "PRECIP".
- Added Dutch translation for "WEEK", "PRECIP", "MODULE_CONFIG_CHANGED" and "MODULE_CONFIG_ERROR".
- Added first test for Alert module.
- Added support for `dateFormat` when not using `timeFormat: "absolute"`.
- Added custom-properties for colors and fonts for improved styling experience, see `custom.css.sample` file.
- Added custom-properties for gaps around body and between modules.
- Added test case for recurring calendar events.
- Added new Environment Canada provider for default WEATHER module (weather data for Canadian locations only).
- Added list view for newsfeed module.
- Added dev dependency jest, switching from mocha to jest.
### Updated
- Bump node-ical to v0.13.0 (now last runtime dependency using deprecated `request` package is removed).
- Use codecov in informational mode.
- Refactor code into es6 where possible (e.g. var -> let/const).
- Use node v16 in github workflow (replacing node v10).
- Moved some files into better suited directories.
- Update dependencies in package.json, require node >= v12, remove `rrule-alt` and `rrule`.
- Update dependencies in package.json and migrate husky to v6, fix husky setup in prod environment.
- Cleaned up error handling in newsfeed and calendar modules for real.
- Updated default WEATHER module such that a provider can optionally set a custom unit-of-measure for precipitation (`weatherObject.precipitationUnits`).
- Update documentation.
- Update jest tests: Reset changes on js/logger.js, mock logger.js in global_vars tests.
- Update dependencies in package.json.
### Removed
- Switching from mocha to jest so removed following dev dependencies: chai, chai-as-promised, mocha, mocha-each, mocha-logger.
### Fixed
- Fix calendar start function logging inconsistency.
- Fix updatenotification start function logging inconsistency.
- Checks and applies the showDescription setting for the newsfeed module again.
- Fix issue with openweathermap not showing current or forecast info when using onecall API.
- Fix tests in weather module and add one for decimalPoint in forecast.
- Fix decimalSymbol in the forecast part of the new weather module (#2530).
- Fix wrong treatment of `appendLocationNameToHeader` when using `ukmetofficedatahub`.
- Fix alert not recognizing multiple alerts (#2522).
- Fix fetch option httpsAgent to agent in calendar module (#466).
- Fix module updatenotification which did not work for repos with many refs (#1907).
- Fix config check failing when encountering let syntax ("Parsing error: Unexpected token config").
- Fix calendar debug check.
- Really run prettier over all files.
- Fix logger.js after jest changes, use --forceExit running jest.
- Workaround for dev_console test using getWindowCount.
## [2.15.0] - 2021-04-01
Special thanks to the following contributors: @EdgardosReis, @MystaraTheGreat, @TheDuffman85, @ashishtank, @buxxi, @codac, @fewieden, @khassel, @klaernie, @qu1que, @rejas, @sdetweil & @thomasrockhu.

View File

@@ -7,7 +7,6 @@
<a href="https://codecov.io/gh/MichMich/MagicMirror"><img src="https://codecov.io/gh/MichMich/MagicMirror/branch/master/graph/badge.svg?token=LEG1KitZR6" alt="CodeCov Status"/></a>
<a href="https://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
<a href="https://github.com/MichMich/MagicMirror/actions?query=workflow%3A%22Automated+Tests%22"><img src="https://github.com/MichMich/MagicMirror/workflows/Automated%20Tests/badge.svg" alt="Tests"></a>
<a href="https://codecov.io/gh/MichMich/MagicMirror"><img src="https://codecov.io/gh/MichMich/MagicMirror/branch/master/graph/badge.svg" /></a>
</p>
**MagicMirror²** is an open source modular smart mirror platform. With a growing list of installable modules, the **MagicMirror²** allows you to convert your hallway or bathroom mirror into your personal assistant. **MagicMirror²** is built by the creator of [the original MagicMirror](https://michaelteeuw.nl/tagged/magicmirror) with the incredible help of a [growing community of contributors](https://github.com/MichMich/MagicMirror/graphs/contributors).
@@ -23,6 +22,7 @@ For the full documentation including **[installation instructions](https://docs.
- Website: [https://magicmirror.builders](https://magicmirror.builders)
- Documentation: [https://docs.magicmirror.builders](https://docs.magicmirror.builders)
- Forum: [https://forum.magicmirror.builders](https://forum.magicmirror.builders)
- Technical discussions: https://forum.magicmirror.builders/category/11/core-system
- Discord: [https://discord.gg/J5BAtvx](https://discord.gg/J5BAtvx)
- Blog: [https://michaelteeuw.nl/tagged/magicmirror](https://michaelteeuw.nl/tagged/magicmirror)
- Donations: [https://magicmirror.builders/#donate](https://magicmirror.builders/#donate)

View File

@@ -2,7 +2,7 @@
// Use separate scope to prevent global scope pollution
(function () {
var config = {};
const config = {};
/**
* Helper function to get server address/hostname from either the commandline or env
@@ -17,8 +17,8 @@
* @returns {string} the value of the parameter
*/
function getCommandLineParameter(key, defaultValue = undefined) {
var index = process.argv.indexOf(`--${key}`);
var value = index > -1 ? process.argv[index + 1] : undefined;
const index = process.argv.indexOf(`--${key}`);
const value = index > -1 ? process.argv[index + 1] : undefined;
return value !== undefined ? String(value) : defaultValue;
}
@@ -43,7 +43,7 @@
// Select http or https module, depending on requested url
const lib = url.startsWith("https") ? require("https") : require("http");
const request = lib.get(url, (response) => {
var configData = "";
let configData = "";
// Gather incoming data
response.on("data", function (chunk) {
@@ -79,15 +79,15 @@
getServerAddress();
(config.address && config.port) || fail();
var prefix = config.tls ? "https://" : "http://";
const prefix = config.tls ? "https://" : "http://";
// Only start the client if a non-local server was provided
if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) === -1) {
getServerConfig(`${prefix}${config.address}:${config.port}/config/`)
.then(function (configReturn) {
// Pass along the server config via an environment variable
var env = Object.create(process.env);
var options = { env: env };
const env = Object.create(process.env);
const options = { env: env };
configReturn.address = config.address;
configReturn.port = config.port;
configReturn.tls = config.tls;

View File

@@ -4,11 +4,10 @@
* MIT Licensed.
*
* For more information on how you can configure this file
* See https://github.com/MichMich/MagicMirror#configuration
*
* see https://docs.magicmirror.builders/getting-started/configuration.html#general
* and https://docs.magicmirror.builders/modules/configuration.html
*/
var config = {
let 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

31
css/custom.css.sample Normal file
View File

@@ -0,0 +1,31 @@
/* Magic Mirror Custom CSS Sample
*
* Change color and fonts here.
*
* Beware that properties cannot be unitless, so for example write '--gap-body: 0px;' instead of just '--gap-body: 0;'
*
* MIT Licensed.
*/
/* Uncomment and adjust accordingly if you want to import another font from the google-fonts-api: */
/* @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;300;400;700&display=swap'); */
:root {
--color-text: #999;
--color-text-dimmed: #666;
--color-text-bright: #fff;
--color-background: black;
--font-primary: "Roboto Condensed";
--font-secondary: "Roboto";
--font-size: 20px;
--font-size-small: 0.75rem;
--gap-body-top: 60px;
--gap-body-right: 60px;
--gap-body-bottom: 60px;
--gap-body-left: 60px;
--gap-modules: 30px;
}

View File

@@ -1,8 +1,29 @@
:root {
--color-text: #999;
--color-text-dimmed: #666;
--color-text-bright: #fff;
--color-background: #000;
--font-primary: "Roboto Condensed";
--font-secondary: "Roboto";
--font-size: 20px;
--font-size-small: 0.75rem;
--gap-body-top: 60px;
--gap-body-right: 60px;
--gap-body-bottom: 60px;
--gap-body-left: 60px;
--gap-modules: 30px;
}
html {
cursor: none;
overflow: hidden;
background: #000;
background: var(--color-background);
user-select: none;
font-size: var(--font-size);
}
::-webkit-scrollbar {
@@ -10,16 +31,15 @@ html {
}
body {
margin: 60px;
margin: var(--gap-body-top) var(--gap-body-right) var(--gap-body-bottom) var(--gap-body-left);
position: absolute;
height: calc(100% - 120px);
width: calc(100% - 120px);
background: #000;
color: #aaa;
font-family: "Roboto Condensed", sans-serif;
height: calc(100% - var(--gap-body-top) - var(--gap-body-bottom));
width: calc(100% - var(--gap-body-right) - var(--gap-body-left));
background: var(--color-background);
color: var(--color-text);
font-family: var(--font-primary), sans-serif;
font-weight: 400;
font-size: 2em;
line-height: 1.5em;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
@@ -28,60 +48,60 @@ body {
*/
.dimmed {
color: #666;
color: var(--color-text-dimmed);
}
.normal {
color: #999;
color: var(--color-text);
}
.bright {
color: #fff;
color: var(--color-text-bright);
}
.xsmall {
font-size: 15px;
line-height: 20px;
font-size: var(--font-size-small);
line-height: 1.275;
}
.small {
font-size: 20px;
line-height: 25px;
font-size: 1rem;
line-height: 1.25;
}
.medium {
font-size: 30px;
line-height: 35px;
font-size: 1.5rem;
line-height: 1.225;
}
.large {
font-size: 65px;
line-height: 65px;
font-size: 3.25rem;
line-height: 1;
}
.xlarge {
font-size: 75px;
line-height: 75px;
font-size: 3.75rem;
line-height: 1;
letter-spacing: -3px;
}
.thin {
font-family: Roboto, sans-serif;
font-family: var(--font-secondary), sans-serif;
font-weight: 100;
}
.light {
font-family: "Roboto Condensed", sans-serif;
font-family: var(--font-primary), sans-serif;
font-weight: 300;
}
.regular {
font-family: "Roboto Condensed", sans-serif;
font-family: var(--font-primary), sans-serif;
font-weight: 400;
}
.bold {
font-family: "Roboto Condensed", sans-serif;
font-family: var(--font-primary), sans-serif;
font-weight: 700;
}
@@ -95,14 +115,14 @@ body {
header {
text-transform: uppercase;
font-size: 15px;
font-family: "Roboto Condensed", Arial, Helvetica, sans-serif;
font-size: var(--font-size-small);
font-family: var(--font-primary), Arial, Helvetica, sans-serif;
font-weight: 400;
border-bottom: 1px solid #666;
border-bottom: 1px solid var(--color-text-dimmed);
line-height: 15px;
padding-bottom: 5px;
margin-bottom: 10px;
color: #999;
color: var(--color-text);
}
sup {
@@ -115,11 +135,11 @@ sup {
*/
.module {
margin-bottom: 30px;
margin-bottom: var(--gap-modules);
}
.region.bottom .module {
margin-top: 30px;
margin-top: var(--gap-modules);
margin-bottom: 0;
}
@@ -143,10 +163,10 @@ sup {
.region.fullscreen {
position: absolute;
top: -60px;
left: -60px;
right: -60px;
bottom: -60px;
top: calc(-1 * var(--gap-body-top));
left: calc(-1 * var(--gap-body-left));
right: calc(-1 * var(--gap-body-right));
bottom: calc(-1 * var(--gap-body-bottom));
pointer-events: none;
}
@@ -163,18 +183,6 @@ sup {
top: 0;
}
.region.top .container {
margin-bottom: 25px;
}
.region.bottom .container {
margin-top: 25px;
}
.region.top .container:empty {
margin-bottom: 0;
}
.region.top.center,
.region.bottom.center {
left: 50%;
@@ -191,10 +199,6 @@ sup {
bottom: 0;
}
.region.bottom .container:empty {
margin-top: 0;
}
.region.bottom.right,
.region.bottom.center,
.region.bottom.left {

View File

@@ -1,12 +1,26 @@
{
"name": "magicmirror-fonts",
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"roboto-fontface": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz",
"integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g=="
}
}
"name": "magicmirror-fonts",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "magicmirror-fonts",
"license": "MIT",
"dependencies": {
"roboto-fontface": "^0.10.0"
}
},
"node_modules/roboto-fontface": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz",
"integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g=="
}
},
"dependencies": {
"roboto-fontface": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz",
"integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g=="
}
}
}

View File

@@ -1,55 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>MagicMirror²</title>
<meta name="google" content="notranslate" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<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">
<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. -->
<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>
<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>

View File

@@ -180,7 +180,6 @@ function App() {
*
* @param {string} a Version number a.
* @param {string} b Version number b.
*
* @returns {number} A positive number if a is larger than b, a negative
* number if a is smaller and 0 if they are the same
*/

View File

@@ -52,7 +52,13 @@ function checkConfigFile() {
// I'm not sure if all ever is utf-8
const configFile = fs.readFileSync(configFileName, "utf-8");
const errors = linter.verify(configFile);
// Explicitly tell linter that he might encounter es6 syntax ("let config = {...}")
const errors = linter.verify(configFile, {
env: {
es6: true
}
});
if (errors.length === 0) {
Log.info(Utils.colors.pass("Your configuration file doesn't contain syntax errors :)"));
} else {

View File

@@ -84,7 +84,6 @@
* Define the clone method for later use. Helper Method.
*
* @param {object} obj Object to be cloned
*
* @returns {object} the cloned object
*/
function cloneObject(obj) {

View File

@@ -6,12 +6,12 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
var address = "localhost";
var port = 8080;
const address = "localhost";
let port = 8080;
if (typeof mmPort !== "undefined") {
port = mmPort;
}
var defaults = {
const defaults = {
address: address,
port: port,
basePath: "/",

View File

@@ -63,6 +63,11 @@ function createWindow() {
// Open the DevTools if run with "npm start dev"
if (process.argv.includes("dev")) {
if (process.env.JEST_WORKER_ID !== undefined) {
// if we are running with jest
var devtools = new BrowserWindow(electronOptions);
mainWindow.webContents.setDevToolsWebContents(devtools.webContents);
}
mainWindow.webContents.openDevTools();
}

View File

@@ -6,24 +6,24 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
var Loader = (function () {
const Loader = (function () {
/* Create helper variables */
var loadedModuleFiles = [];
var loadedFiles = [];
var moduleObjects = [];
const loadedModuleFiles = [];
const loadedFiles = [];
const moduleObjects = [];
/* Private Methods */
/**
* Loops thru all modules and requests load for every module.
*/
var loadModules = function () {
var moduleData = getModuleData();
const loadModules = function () {
let moduleData = getModuleData();
var loadNextModule = function () {
const loadNextModule = function () {
if (moduleData.length > 0) {
var nextModule = moduleData[0];
const nextModule = moduleData[0];
loadModule(nextModule, function () {
moduleData = moduleData.slice(1);
loadNextModule();
@@ -46,9 +46,8 @@ var Loader = (function () {
/**
* Loops thru all modules and requests start for every module.
*/
var startModules = function () {
for (var m in moduleObjects) {
var module = moduleObjects[m];
const startModules = function () {
for (const module of moduleObjects) {
module.start();
}
@@ -56,7 +55,7 @@ var Loader = (function () {
MM.modulesStarted(moduleObjects);
// Starting modules also hides any modules that have requested to be initially hidden
for (let thisModule of moduleObjects) {
for (const thisModule of moduleObjects) {
if (thisModule.data.hiddenOnStartup) {
Log.info("Initially hiding " + thisModule.name);
thisModule.hide();
@@ -69,7 +68,7 @@ var Loader = (function () {
*
* @returns {object[]} module data as configured in config
*/
var getAllModules = function () {
const getAllModules = function () {
return config.modules;
};
@@ -78,29 +77,28 @@ var Loader = (function () {
*
* @returns {object[]} Module information.
*/
var getModuleData = function () {
var modules = getAllModules();
var moduleFiles = [];
const getModuleData = function () {
const modules = getAllModules();
const moduleFiles = [];
for (var m in modules) {
var moduleData = modules[m];
var module = moduleData.module;
modules.forEach(function (moduleData, index) {
const module = moduleData.module;
var elements = module.split("/");
var moduleName = elements[elements.length - 1];
var moduleFolder = config.paths.modules + "/" + module;
const elements = module.split("/");
const moduleName = elements[elements.length - 1];
let moduleFolder = config.paths.modules + "/" + module;
if (defaultModules.indexOf(moduleName) !== -1) {
moduleFolder = config.paths.modules + "/default/" + module;
}
if (moduleData.disabled === true) {
continue;
return;
}
moduleFiles.push({
index: m,
identifier: "module_" + m + "_" + module,
index: index,
identifier: "module_" + index + "_" + module,
name: moduleName,
path: moduleFolder + "/",
file: moduleName + ".js",
@@ -111,7 +109,7 @@ var Loader = (function () {
config: moduleData.config,
classes: typeof moduleData.classes !== "undefined" ? moduleData.classes + " " + module : module
});
}
});
return moduleFiles;
};
@@ -122,11 +120,11 @@ var Loader = (function () {
* @param {object} module Information about the module we want to load.
* @param {Function} callback Function called when done.
*/
var loadModule = function (module, callback) {
var url = module.path + module.file;
const loadModule = function (module, callback) {
const url = module.path + module.file;
var afterLoad = function () {
var moduleObject = Module.create(module.name);
const afterLoad = function () {
const moduleObject = Module.create(module.name);
if (moduleObject) {
bootstrapModule(module, moduleObject, function () {
callback();
@@ -153,7 +151,7 @@ var Loader = (function () {
* @param {Module} mObj Modules instance.
* @param {Function} callback Function called when done.
*/
var bootstrapModule = function (module, mObj, callback) {
const bootstrapModule = function (module, mObj, callback) {
Log.info("Bootstrapping module: " + module.name);
mObj.setData(module);
@@ -177,13 +175,14 @@ var Loader = (function () {
* @param {string} fileName Path of the file we want to load.
* @param {Function} callback Function called when done.
*/
var loadFile = function (fileName, callback) {
var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
const loadFile = function (fileName, callback) {
const extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
let script, stylesheet;
switch (extension.toLowerCase()) {
case "js":
Log.log("Load script: " + fileName);
var script = document.createElement("script");
script = document.createElement("script");
script.type = "text/javascript";
script.src = fileName;
script.onload = function () {
@@ -202,7 +201,7 @@ var Loader = (function () {
break;
case "css":
Log.log("Load stylesheet: " + fileName);
var stylesheet = document.createElement("link");
stylesheet = document.createElement("link");
stylesheet.rel = "stylesheet";
stylesheet.type = "text/css";
stylesheet.href = fileName;

View File

@@ -6,25 +6,25 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
var MM = (function () {
var modules = [];
const MM = (function () {
let modules = [];
/* Private Methods */
/**
* Create dom objects for all modules that are configured for a specific position.
*/
var createDomObjects = function () {
var domCreationPromises = [];
const createDomObjects = function () {
const domCreationPromises = [];
modules.forEach(function (module) {
if (typeof module.data.position !== "string") {
return;
}
var wrapper = selectWrapper(module.data.position);
const wrapper = selectWrapper(module.data.position);
var dom = document.createElement("div");
const dom = document.createElement("div");
dom.id = module.identifier;
dom.className = module.name;
@@ -35,7 +35,7 @@ var MM = (function () {
dom.opacity = 0;
wrapper.appendChild(dom);
var moduleHeader = document.createElement("header");
const moduleHeader = document.createElement("header");
moduleHeader.innerHTML = module.getHeader();
moduleHeader.className = "module-header";
dom.appendChild(moduleHeader);
@@ -46,11 +46,11 @@ var MM = (function () {
moduleHeader.style.display = "block;";
}
var moduleContent = document.createElement("div");
const moduleContent = document.createElement("div");
moduleContent.className = "module-content";
dom.appendChild(moduleContent);
var domCreationPromise = updateDom(module, 0);
const domCreationPromise = updateDom(module, 0);
domCreationPromises.push(domCreationPromise);
domCreationPromise
.then(function () {
@@ -70,14 +70,13 @@ var MM = (function () {
* Select the wrapper dom object for a specific position.
*
* @param {string} position The name of the position.
*
* @returns {HTMLElement} the wrapper element
*/
var selectWrapper = function (position) {
var classes = position.replace("_", " ");
var parentWrapper = document.getElementsByClassName(classes);
const selectWrapper = function (position) {
const classes = position.replace("_", " ");
const parentWrapper = document.getElementsByClassName(classes);
if (parentWrapper.length > 0) {
var wrapper = parentWrapper[0].getElementsByClassName("container");
const wrapper = parentWrapper[0].getElementsByClassName("container");
if (wrapper.length > 0) {
return wrapper[0];
}
@@ -92,9 +91,9 @@ var MM = (function () {
* @param {Module} sender The module that sent the notification.
* @param {Module} [sendTo] The (optional) module to send the notification to.
*/
var sendNotification = function (notification, payload, sender, sendTo) {
for (var m in modules) {
var module = modules[m];
const sendNotification = function (notification, payload, sender, sendTo) {
for (const m in modules) {
const module = modules[m];
if (module !== sender && (!sendTo || module === sendTo)) {
module.notificationReceived(notification, payload, sender);
}
@@ -106,13 +105,12 @@ var MM = (function () {
*
* @param {Module} module The module that needs an update.
* @param {number} [speed] The (optional) number of microseconds for the animation.
*
* @returns {Promise} Resolved when the dom is fully updated.
*/
var updateDom = function (module, speed) {
const updateDom = function (module, speed) {
return new Promise(function (resolve) {
var newContentPromise = module.getDom();
var newHeader = module.getHeader();
const newHeader = module.getHeader();
let newContentPromise = module.getDom();
if (!(newContentPromise instanceof Promise)) {
// convert to a promise if not already one to avoid if/else's everywhere
@@ -121,7 +119,7 @@ var MM = (function () {
newContentPromise
.then(function (newContent) {
var updatePromise = updateDomWithContent(module, speed, newHeader, newContent);
const updatePromise = updateDomWithContent(module, speed, newHeader, newContent);
updatePromise.then(resolve).catch(Log.error);
})
@@ -136,10 +134,9 @@ var MM = (function () {
* @param {number} [speed] The (optional) number of microseconds for the animation.
* @param {string} newHeader The new header that is generated.
* @param {HTMLElement} newContent The new content that is generated.
*
* @returns {Promise} Resolved when the module dom has been updated.
*/
var updateDomWithContent = function (module, speed, newHeader, newContent) {
const updateDomWithContent = function (module, speed, newHeader, newContent) {
return new Promise(function (resolve) {
if (module.hidden || !speed) {
updateModuleContent(module, newHeader, newContent);
@@ -174,26 +171,25 @@ var MM = (function () {
* @param {Module} module The module to check.
* @param {string} newHeader The new header that is generated.
* @param {HTMLElement} newContent The new content that is generated.
*
* @returns {boolean} True if the module need an update, false otherwise
*/
var moduleNeedsUpdate = function (module, newHeader, newContent) {
var moduleWrapper = document.getElementById(module.identifier);
const moduleNeedsUpdate = function (module, newHeader, newContent) {
const moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper === null) {
return false;
}
var contentWrapper = moduleWrapper.getElementsByClassName("module-content");
var headerWrapper = moduleWrapper.getElementsByClassName("module-header");
const contentWrapper = moduleWrapper.getElementsByClassName("module-content");
const headerWrapper = moduleWrapper.getElementsByClassName("module-header");
var headerNeedsUpdate = false;
var contentNeedsUpdate = false;
let headerNeedsUpdate = false;
let contentNeedsUpdate;
if (headerWrapper.length > 0) {
headerNeedsUpdate = newHeader !== headerWrapper[0].innerHTML;
}
var tempContentWrapper = document.createElement("div");
const tempContentWrapper = document.createElement("div");
tempContentWrapper.appendChild(newContent);
contentNeedsUpdate = tempContentWrapper.innerHTML !== contentWrapper[0].innerHTML;
@@ -207,13 +203,13 @@ var MM = (function () {
* @param {string} newHeader The new header that is generated.
* @param {HTMLElement} newContent The new content that is generated.
*/
var updateModuleContent = function (module, newHeader, newContent) {
var moduleWrapper = document.getElementById(module.identifier);
const updateModuleContent = function (module, newHeader, newContent) {
const moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper === null) {
return;
}
var headerWrapper = moduleWrapper.getElementsByClassName("module-header");
var contentWrapper = moduleWrapper.getElementsByClassName("module-content");
const headerWrapper = moduleWrapper.getElementsByClassName("module-header");
const contentWrapper = moduleWrapper.getElementsByClassName("module-content");
contentWrapper[0].innerHTML = "";
contentWrapper[0].appendChild(newContent);
@@ -234,7 +230,7 @@ var MM = (function () {
* @param {Function} callback Called when the animation is done.
* @param {object} [options] Optional settings for the hide method.
*/
var hideModule = function (module, speed, callback, options) {
const hideModule = function (module, speed, callback, options) {
options = options || {};
// set lockString if set in options.
@@ -245,7 +241,7 @@ var MM = (function () {
}
}
var moduleWrapper = document.getElementById(module.identifier);
const moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper !== null) {
moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
moduleWrapper.style.opacity = 0;
@@ -280,12 +276,12 @@ var MM = (function () {
* @param {Function} callback Called when the animation is done.
* @param {object} [options] Optional settings for the show method.
*/
var showModule = function (module, speed, callback, options) {
const showModule = function (module, speed, callback, options) {
options = options || {};
// remove lockString if set in options.
if (options.lockString) {
var index = module.lockStrings.indexOf(options.lockString);
const index = module.lockStrings.indexOf(options.lockString);
if (index !== -1) {
module.lockStrings.splice(index, 1);
}
@@ -309,7 +305,7 @@ var MM = (function () {
module.lockStrings = [];
}
var moduleWrapper = document.getElementById(module.identifier);
const moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper !== null) {
moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
// Restore the position. See hideModule() for more info.
@@ -318,7 +314,7 @@ var MM = (function () {
updateWrapperStates();
// Waiting for DOM-changes done in updateWrapperStates before we can start the animation.
var dummy = moduleWrapper.parentElement.parentElement.offsetHeight;
const dummy = moduleWrapper.parentElement.parentElement.offsetHeight;
moduleWrapper.style.opacity = 1;
clearTimeout(module.showHideTimer);
@@ -346,14 +342,14 @@ var MM = (function () {
* 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"];
const updateWrapperStates = function () {
const 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");
const wrapper = selectWrapper(position);
const moduleWrappers = wrapper.getElementsByClassName("module");
var showWrapper = false;
let showWrapper = false;
Array.prototype.forEach.call(moduleWrappers, function (moduleWrapper) {
if (moduleWrapper.style.position === "" || moduleWrapper.style.position === "static") {
showWrapper = true;
@@ -367,7 +363,7 @@ var MM = (function () {
/**
* Loads the core config and combines it with the system defaults.
*/
var loadConfig = function () {
const loadConfig = function () {
// FIXME: Think about how to pass config around without breaking tests
/* eslint-disable */
if (typeof config === "undefined") {
@@ -385,15 +381,14 @@ var MM = (function () {
*
* @param {Module[]} modules Array of modules.
*/
var setSelectionMethodsForModules = function (modules) {
const setSelectionMethodsForModules = function (modules) {
/**
* Filter modules with the specified classes.
*
* @param {string|string[]} className one or multiple classnames (array or space divided).
*
* @returns {Module[]} Filtered collection of modules.
*/
var withClass = function (className) {
const withClass = function (className) {
return modulesByClass(className, true);
};
@@ -401,10 +396,9 @@ var MM = (function () {
* Filter modules without the specified classes.
*
* @param {string|string[]} className one or multiple classnames (array or space divided).
*
* @returns {Module[]} Filtered collection of modules.
*/
var exceptWithClass = function (className) {
const exceptWithClass = function (className) {
return modulesByClass(className, false);
};
@@ -413,20 +407,18 @@ var MM = (function () {
*
* @param {string|string[]} className one or multiple classnames (array or space divided).
* @param {boolean} include if the filter should include or exclude the modules with the specific classes.
*
* @returns {Module[]} Filtered collection of modules.
*/
var modulesByClass = function (className, include) {
var searchClasses = className;
const modulesByClass = function (className, include) {
let searchClasses = className;
if (typeof className === "string") {
searchClasses = className.split(" ");
}
var newModules = modules.filter(function (module) {
var classes = module.data.classes.toLowerCase().split(" ");
const newModules = modules.filter(function (module) {
const classes = module.data.classes.toLowerCase().split(" ");
for (var c in searchClasses) {
var searchClass = searchClasses[c];
for (const searchClass of searchClasses) {
if (classes.indexOf(searchClass.toLowerCase()) !== -1) {
return include;
}
@@ -445,8 +437,8 @@ var MM = (function () {
* @param {object} module The module instance to remove from the collection.
* @returns {Module[]} Filtered collection of modules.
*/
var exceptModule = function (module) {
var newModules = modules.filter(function (mod) {
const exceptModule = function (module) {
const newModules = modules.filter(function (mod) {
return mod.identifier !== module.identifier;
});
@@ -459,7 +451,7 @@ var MM = (function () {
*
* @param {Function} callback The function to execute with the module as an argument.
*/
var enumerate = function (callback) {
const enumerate = function (callback) {
modules.map(function (module) {
callback(module);
});
@@ -604,11 +596,11 @@ if (typeof Object.assign !== "function") {
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];
const output = Object(target);
for (let index = 1; index < arguments.length; index++) {
const source = arguments[index];
if (source !== undefined && source !== null) {
for (var nextKey in source) {
for (const nextKey in source) {
if (source.hasOwnProperty(nextKey)) {
output[nextKey] = source[nextKey];
}

View File

@@ -6,7 +6,6 @@
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*
*/
var Module = Class.extend({
/*********************************************************
@@ -82,16 +81,15 @@ var Module = Class.extend({
* @returns {HTMLElement|Promise} The dom or a promise with the dom to display.
*/
getDom: function () {
var self = this;
return new Promise(function (resolve) {
var div = document.createElement("div");
var template = self.getTemplate();
var templateData = self.getTemplateData();
return new Promise((resolve) => {
const div = document.createElement("div");
const template = this.getTemplate();
const 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
self.nunjucksEnvironment().render(template, templateData, function (err, res) {
this.nunjucksEnvironment().render(template, templateData, function (err, res) {
if (err) {
Log.error(err);
}
@@ -102,7 +100,7 @@ var Module = Class.extend({
});
} else {
// the template is a template string.
div.innerHTML = self.nunjucksEnvironment().renderString(template, templateData);
div.innerHTML = this.nunjucksEnvironment().renderString(template, templateData);
resolve(div);
}
@@ -168,15 +166,13 @@ var Module = Class.extend({
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, variables) {
return nunjucks.runtime.markSafe(self.translate(str, variables));
this._nunjucksEnvironment.addFilter("translate", (str, variables) => {
return nunjucks.runtime.markSafe(this.translate(str, variables));
});
return this._nunjucksEnvironment;
@@ -192,14 +188,14 @@ var Module = Class.extend({
Log.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
},
/*
/**
* Called when the module is hidden.
*/
suspend: function () {
Log.log(this.name + " is suspended.");
},
/*
/**
* Called when the module is shown.
*/
resume: function () {
@@ -213,7 +209,7 @@ var Module = Class.extend({
/**
* Set the module data.
*
* @param {Module} data The module data
* @param {object} data The module data
*/
setData: function (data) {
this.data = data;
@@ -245,9 +241,8 @@ var Module = Class.extend({
this._socket = new MMSocket(this.name);
}
var self = this;
this._socket.setNotificationCallback(function (notification, payload) {
self.socketNotificationReceived(notification, payload);
this._socket.setNotificationCallback((notification, payload) => {
this.socketNotificationReceived(notification, payload);
});
return this._socket;
@@ -288,13 +283,12 @@ var Module = Class.extend({
* @param {Function} callback Function called when done.
*/
loadDependencies: function (funcName, callback) {
var self = this;
var dependencies = this[funcName]();
let dependencies = this[funcName]();
var loadNextDependency = function () {
const loadNextDependency = () => {
if (dependencies.length > 0) {
var nextDependency = dependencies[0];
Loader.loadFile(nextDependency, self, function () {
const nextDependency = dependencies[0];
Loader.loadFile(nextDependency, this, () => {
dependencies = dependencies.slice(1);
loadNextDependency();
});
@@ -400,12 +394,11 @@ var Module = Class.extend({
callback = callback || function () {};
options = options || {};
var self = this;
MM.hideModule(
self,
this,
speed,
function () {
self.suspend();
() => {
this.suspend();
callback();
},
options
@@ -464,9 +457,9 @@ var Module = Class.extend({
* @returns {object} the merged config
*/
function configMerge(result) {
var stack = Array.prototype.slice.call(arguments, 1);
var item;
var key;
const stack = Array.prototype.slice.call(arguments, 1);
let item, key;
while (stack.length) {
item = stack.shift();
for (key in item) {
@@ -494,11 +487,11 @@ Module.create = function (name) {
return;
}
var moduleDefinition = Module.definitions[name];
var clonedDefinition = cloneObject(moduleDefinition);
const moduleDefinition = Module.definitions[name];
const clonedDefinition = cloneObject(moduleDefinition);
// Note that we clone the definition. Otherwise the objects are shared, which gives problems.
var ModuleClass = Module.extend(clonedDefinition);
const ModuleClass = Module.extend(clonedDefinition);
return new ModuleClass();
};
@@ -526,14 +519,13 @@ Module.register = function (name, moduleDefinition) {
* number if a is smaller and 0 if they are the same
*/
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);
const regExStrip0 = /(\.0+)+$/;
const segmentsA = a.replace(regExStrip0, "").split(".");
const segmentsB = b.replace(regExStrip0, "").split(".");
const l = Math.min(segmentsA.length, segmentsB.length);
for (i = 0; i < l; i++) {
diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
for (let i = 0; i < l; i++) {
let diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
if (diff) {
return diff;
}

View File

@@ -113,6 +113,32 @@ const NodeHelper = Class.extend({
}
});
NodeHelper.checkFetchStatus = function (response) {
// response.status >= 200 && response.status < 300
if (response.ok) {
return response;
} else {
throw Error(response.statusText);
}
};
/**
* Look at the specified error and return an appropriate error type, that
* can be translated to a detailed error message
*
* @param {Error} error the error from fetching something
* @returns {string} the string of the detailed error message in the translations
*/
NodeHelper.checkFetchError = function (error) {
let error_type = "MODULE_ERROR_UNSPECIFIED";
if (error.code === "EAI_AGAIN") {
error_type = "MODULE_ERROR_NO_CONNECTION";
} else if (error.message === "Unauthorized") {
error_type = "MODULE_ERROR_UNAUTHORIZED";
}
return error_type;
};
NodeHelper.create = function (moduleDefinition) {
return NodeHelper.extend(moduleDefinition);
};

View File

@@ -6,49 +6,48 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
var MMSocket = function (moduleName) {
var self = this;
const MMSocket = function (moduleName) {
if (typeof moduleName !== "string") {
throw new Error("Please set the module name for the MMSocket.");
}
self.moduleName = moduleName;
this.moduleName = moduleName;
// Private Methods
var base = "/";
let base = "/";
if (typeof config !== "undefined" && typeof config.basePath !== "undefined") {
base = config.basePath;
}
self.socket = io("/" + self.moduleName, {
this.socket = io("/" + this.moduleName, {
path: base + "socket.io"
});
var notificationCallback = function () {};
var onevent = self.socket.onevent;
self.socket.onevent = function (packet) {
var args = packet.data || [];
onevent.call(this, packet); // original call
let notificationCallback = function () {};
const onevent = this.socket.onevent;
this.socket.onevent = (packet) => {
const args = packet.data || [];
onevent.call(this.socket, packet); // original call
packet.data = ["*"].concat(args);
onevent.call(this, packet); // additional call to catch-all
onevent.call(this.socket, packet); // additional call to catch-all
};
// register catch all.
self.socket.on("*", function (notification, payload) {
this.socket.on("*", (notification, payload) => {
if (notification !== "*") {
notificationCallback(notification, payload);
}
});
// Public Methods
this.setNotificationCallback = function (callback) {
this.setNotificationCallback = (callback) => {
notificationCallback = callback;
};
this.sendNotification = function (notification, payload) {
this.sendNotification = (notification, payload) => {
if (typeof payload === "undefined") {
payload = {};
}
self.socket.emit(notification, payload);
this.socket.emit(notification, payload);
};
};

View File

@@ -79,7 +79,7 @@ Module.register("alert", {
//If module already has an open alert close it
if (this.alerts[sender.name]) {
this.hide_alert(sender);
this.hide_alert(sender, false);
}
//Display title and message only if they are provided in notification parameters
@@ -114,10 +114,10 @@ Module.register("alert", {
}, params.timer);
}
},
hide_alert: function (sender) {
hide_alert: function (sender, close = true) {
//Dismiss alert and remove from this.alerts
if (this.alerts[sender.name]) {
this.alerts[sender.name].dismiss();
this.alerts[sender.name].dismiss(close);
this.alerts[sender.name] = null;
//Remove overlay
const overlay = document.getElementById("overlay");

View File

@@ -6,7 +6,6 @@
line-height: 1.4;
margin-bottom: 10px;
z-index: 1;
color: black;
font-size: 70%;
position: relative;
display: table;
@@ -15,17 +14,17 @@
border-width: 1px;
border-radius: 5px;
border-style: solid;
border-color: #666;
border-color: var(--color-text-dimmed);
}
.ns-alert {
border-style: solid;
border-color: #fff;
border-color: var(--color-text-bright);
padding: 17px;
line-height: 1.4;
margin-bottom: 10px;
z-index: 3;
color: white;
color: var(--color-text-bright);
font-size: 70%;
position: fixed;
text-align: center;

View File

@@ -122,8 +122,10 @@
/**
* Dismiss the notification
*
* @param {boolean} [close] call the onClose callback at the end
*/
NotificationFx.prototype.dismiss = function () {
NotificationFx.prototype.dismiss = function (close = true) {
this.active = false;
clearTimeout(this.dismissttl);
this.ntf.classList.remove("ns-show");
@@ -131,7 +133,7 @@
this.ntf.classList.add("ns-hide");
// callback
this.options.onClose();
if (close) this.options.onClose();
}, 25);
// after animation ends remove ntf from the DOM

View File

@@ -1,13 +1,14 @@
.calendar .symbol {
display: flex;
flex-direction: row;
justify-content: flex-end;
padding-left: 0;
padding-right: 10px;
font-size: 80%;
vertical-align: top;
font-size: var(--font-size-small);
}
.calendar .symbol span {
display: inline-block;
transform: translate(0, 2px);
padding-top: 4px;
}
.calendar .title {

View File

@@ -84,7 +84,7 @@ Module.register("calendar", {
// Override start method.
start: function () {
Log.log("Starting module: " + this.name);
Log.info("Starting module: " + this.name);
// Set locale.
moment.updateLocale(config.language, this.getLocaleSpecification(config.timeFormat));
@@ -140,17 +140,17 @@ Module.register("calendar", {
if (notification === "CALENDAR_EVENTS") {
if (this.hasCalendarURL(payload.url)) {
this.calendarData[payload.url] = payload.events;
this.error = null;
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 === "CALENDAR_ERROR") {
let error_message = this.translate(payload.error_type);
this.error = this.translate("MODULE_CONFIG_ERROR", { MODULE_NAME: this.name, ERROR: error_message });
this.loaded = true;
} else if (notification === "INCORRECT_URL") {
Log.error("Calendar Error. Incorrect url: " + payload.url);
}
this.updateDom(this.config.animationSpeed);
@@ -168,6 +168,12 @@ Module.register("calendar", {
const wrapper = document.createElement("table");
wrapper.className = this.config.tableClass;
if (this.error) {
wrapper.innerHTML = this.error;
wrapper.className = this.config.tableClass + " dimmed";
return wrapper;
}
if (events.length === 0) {
wrapper.innerHTML = this.loaded ? this.translate("EMPTY") : this.translate("LOADING");
wrapper.className = this.config.tableClass + " dimmed";
@@ -305,15 +311,14 @@ Module.register("calendar", {
if (this.config.timeFormat === "dateheaders") {
if (event.fullDayEvent) {
titleWrapper.colSpan = "2";
titleWrapper.align = "left";
titleWrapper.classList.add("align-left");
} else {
const timeWrapper = document.createElement("td");
timeWrapper.className = "time light " + this.timeClassForUrl(event.url);
timeWrapper.align = "left";
timeWrapper.className = "time light align-left " + this.timeClassForUrl(event.url);
timeWrapper.style.paddingLeft = "2px";
timeWrapper.innerHTML = moment(event.startDate, "x").format("LT");
eventWrapper.appendChild(timeWrapper);
titleWrapper.align = "right";
titleWrapper.classList.add("align-right");
}
eventWrapper.appendChild(titleWrapper);
@@ -366,13 +371,14 @@ Module.register("calendar", {
if (event.startDate >= now) {
// Use relative time
if (!this.config.hideTime) {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar());
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").calendar(null, { sameElse: this.config.dateFormat }));
} else {
timeWrapper.innerHTML = this.capFirst(
moment(event.startDate, "x").calendar(null, {
sameDay: "[" + this.translate("TODAY") + "]",
nextDay: "[" + this.translate("TOMORROW") + "]",
nextWeek: "dddd"
nextWeek: "dddd",
sameElse: this.config.dateFormat
})
);
}

View File

@@ -6,6 +6,7 @@
*/
const CalendarUtils = require("./calendarutils");
const Log = require("logger");
const NodeHelper = require("node_helper");
const ical = require("node-ical");
const fetch = require("node-fetch");
const digest = require("digest-fetch");
@@ -52,27 +53,17 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
if (auth.method === "bearer") {
headers.Authorization = "Bearer " + auth.pass;
} else if (auth.method === "digest") {
fetcher = new digest(auth.user, auth.pass).fetch(url, { headers: headers, httpsAgent: httpsAgent });
fetcher = new digest(auth.user, auth.pass).fetch(url, { headers: headers, agent: httpsAgent });
} else {
headers.Authorization = "Basic " + Buffer.from(auth.user + ":" + auth.pass).toString("base64");
}
}
if (fetcher === null) {
fetcher = fetch(url, { headers: headers, httpsAgent: httpsAgent });
fetcher = fetch(url, { headers: headers, agent: httpsAgent });
}
fetcher
.catch((error) => {
fetchFailedCallback(this, error);
scheduleTimer();
})
.then((response) => {
if (response.status !== 200) {
fetchFailedCallback(this, response.statusText);
scheduleTimer();
}
return response;
})
.then(NodeHelper.checkFetchStatus)
.then((response) => response.text())
.then((responseData) => {
let data = [];
@@ -87,12 +78,16 @@ const CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEn
maximumNumberOfDays
});
} catch (error) {
fetchFailedCallback(this, error.message);
fetchFailedCallback(this, error);
scheduleTimer();
return;
}
this.broadcastEvents();
scheduleTimer();
})
.catch((error) => {
fetchFailedCallback(this, error);
scheduleTimer();
});
};

View File

@@ -18,8 +18,8 @@ const CalendarUtils = {
* Calculate the time correction, either dst/std or full day in cases where
* utc time is day before plus offset
*
* @param {object} event
* @param {Date} date
* @param {object} event the event which needs adjustement
* @param {Date} date the date on which this event happens
* @returns {number} the necessary adjustment in hours
*/
calculateTimezoneAdjustment: function (event, date) {
@@ -117,6 +117,13 @@ const CalendarUtils = {
return adjustHours;
},
/**
* Filter the events from ical according to the given config
*
* @param {object} data the calendar data from ical
* @param {object} config The configuration object
* @returns {string[]} the filtered events
*/
filterEvents: function (data, config) {
const newEvents = [];
@@ -500,8 +507,8 @@ const CalendarUtils = {
/**
* Lookup iana tz from windows
*
* @param msTZName
* @returns {*|null}
* @param {string} msTZName the timezone name to lookup
* @returns {string|null} the iana name or null of none is found
*/
getIanaTZFromMS: function (msTZName) {
// Get hash entry
@@ -571,12 +578,13 @@ const CalendarUtils = {
},
/**
* Determines if the user defined title filter should apply
*
* @param title
* @param filter
* @param useRegex
* @param regexFlags
* @returns {boolean|*}
* @param {string} title the title of the event
* @param {string} filter the string to look for, can be a regex also
* @param {boolean} useRegex true if a regex should be used, otherwise it just looks for the filter as a string
* @param {string} regexFlags flags that should be applied to the regex
* @returns {boolean} True if the title should be filtered out, false otherwise
*/
titleFilterApplies: function (title, filter, useRegex, regexFlags) {
if (useRegex) {

View File

@@ -5,6 +5,9 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
// Alias modules mentioned in package.js under _moduleAliases.
require("module-alias/register");
const CalendarFetcher = require("./calendarfetcher.js");
const url = "https://calendar.google.com/calendar/ical/pkm1t2uedjbp0uvq1o7oj1jouo%40group.calendar.google.com/private-08ba559f89eec70dd74bbd887d0a3598/basic.ics"; // Standard test URL
@@ -26,11 +29,13 @@ const fetcher = new CalendarFetcher(url, fetchInterval, [], maximumEntries, maxi
fetcher.onReceive(function (fetcher) {
console.log(fetcher.events());
console.log("------------------------------------------------------------");
process.exit(0);
});
fetcher.onError(function (fetcher, error) {
console.log("Fetcher error:");
console.log(error);
process.exit(1);
});
fetcher.startFetch();

View File

@@ -40,13 +40,14 @@ module.exports = NodeHelper.create({
try {
new URL(url);
} catch (error) {
this.sendSocketNotification("INCORRECT_URL", { id: identifier, url: url });
Log.error("Calendar Error. Malformed calendar url: ", url, error);
this.sendSocketNotification("CALENDAR_ERROR", { error_type: "MODULE_ERROR_MALFORMED_URL" });
return;
}
let fetcher;
if (typeof this.fetchers[identifier + url] === "undefined") {
Log.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval);
Log.log("Create new calendarfetcher for url: " + url + " - Interval: " + fetchInterval);
fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents, selfSignedCert);
fetcher.onReceive((fetcher) => {
@@ -55,16 +56,16 @@ module.exports = NodeHelper.create({
fetcher.onError((fetcher, error) => {
Log.error("Calendar Error. Could not fetch calendar: ", fetcher.url(), error);
this.sendSocketNotification("FETCH_ERROR", {
let error_type = NodeHelper.checkFetchError(error);
this.sendSocketNotification("CALENDAR_ERROR", {
id: identifier,
url: fetcher.url(),
error: error
error_type
});
});
this.fetchers[identifier + url] = fetcher;
} else {
Log.log("Use existing calendar fetcher for url: " + url);
Log.log("Use existing calendarfetcher for url: " + url);
fetcher = this.fetchers[identifier + url];
fetcher.broadcastEvents();
}

View File

@@ -46,62 +46,61 @@ Module.register("clock", {
Log.info("Starting module: " + this.name);
// Schedule update interval.
var self = this;
self.second = moment().second();
self.minute = moment().minute();
this.second = moment().second();
this.minute = moment().minute();
//Calculate how many ms should pass until next update depending on if seconds is displayed or not
var delayCalculator = function (reducedSeconds) {
var EXTRA_DELAY = 50; //Deliberate imperceptable delay to prevent off-by-one timekeeping errors
// Calculate how many ms should pass until next update depending on if seconds is displayed or not
const delayCalculator = (reducedSeconds) => {
const EXTRA_DELAY = 50; // Deliberate imperceptible delay to prevent off-by-one timekeeping errors
if (self.config.displaySeconds) {
if (this.config.displaySeconds) {
return 1000 - moment().milliseconds() + EXTRA_DELAY;
} else {
return (60 - reducedSeconds) * 1000 - moment().milliseconds() + EXTRA_DELAY;
}
};
//A recursive timeout function instead of interval to avoid drifting
var notificationTimer = function () {
self.updateDom();
// A recursive timeout function instead of interval to avoid drifting
const notificationTimer = () => {
this.updateDom();
//If seconds is displayed CLOCK_SECOND-notification should be sent (but not when CLOCK_MINUTE-notification is sent)
if (self.config.displaySeconds) {
self.second = moment().second();
if (self.second !== 0) {
self.sendNotification("CLOCK_SECOND", self.second);
// If seconds is displayed CLOCK_SECOND-notification should be sent (but not when CLOCK_MINUTE-notification is sent)
if (this.config.displaySeconds) {
this.second = moment().second();
if (this.second !== 0) {
this.sendNotification("CLOCK_SECOND", this.second);
setTimeout(notificationTimer, delayCalculator(0));
return;
}
}
//If minute changed or seconds isn't displayed send CLOCK_MINUTE-notification
self.minute = moment().minute();
self.sendNotification("CLOCK_MINUTE", self.minute);
// If minute changed or seconds isn't displayed send CLOCK_MINUTE-notification
this.minute = moment().minute();
this.sendNotification("CLOCK_MINUTE", this.minute);
setTimeout(notificationTimer, delayCalculator(0));
};
//Set the initial timeout with the amount of seconds elapsed as reducedSeconds so it will trigger when the minute changes
setTimeout(notificationTimer, delayCalculator(self.second));
// Set the initial timeout with the amount of seconds elapsed as reducedSeconds so it will trigger when the minute changes
setTimeout(notificationTimer, delayCalculator(this.second));
// Set locale.
moment.locale(config.language);
},
// Override dom generator.
getDom: function () {
var wrapper = document.createElement("div");
const wrapper = document.createElement("div");
/************************************
* Create wrappers for DIGITAL clock
*/
var dateWrapper = document.createElement("div");
var timeWrapper = document.createElement("div");
var secondsWrapper = document.createElement("sup");
var periodWrapper = document.createElement("span");
var sunWrapper = document.createElement("div");
var moonWrapper = document.createElement("div");
var weekWrapper = document.createElement("div");
const dateWrapper = document.createElement("div");
const timeWrapper = document.createElement("div");
const secondsWrapper = document.createElement("sup");
const periodWrapper = document.createElement("span");
const sunWrapper = document.createElement("div");
const moonWrapper = document.createElement("div");
const weekWrapper = document.createElement("div");
// Style Wrappers
dateWrapper.className = "date normal medium";
timeWrapper.className = "time bright large light";
@@ -114,14 +113,13 @@ Module.register("clock", {
// The moment().format("h") method has a bug on the Raspberry Pi.
// So we need to generate the timestring manually.
// See issue: https://github.com/MichMich/MagicMirror/issues/181
var timeString;
var now = moment();
this.lastDisplayedMinute = now.minute();
let timeString;
const now = moment();
if (this.config.timezone) {
now.tz(this.config.timezone);
}
var hourSymbol = "HH";
let hourSymbol = "HH";
if (this.config.timeFormat !== 24) {
hourSymbol = "h";
}
@@ -160,7 +158,7 @@ Module.register("clock", {
* @returns {string} The formatted time string
*/
function formatTime(config, time) {
var formatString = hourSymbol + ":mm";
let formatString = hourSymbol + ":mm";
if (config.showPeriod && config.timeFormat !== 24) {
formatString += config.showPeriodUpper ? "A" : "a";
}
@@ -170,7 +168,7 @@ Module.register("clock", {
if (this.config.showSunTimes) {
const sunTimes = SunCalc.getTimes(now, this.config.lat, this.config.lon);
const isVisible = now.isBetween(sunTimes.sunrise, sunTimes.sunset);
var nextEvent;
let nextEvent;
if (now.isBefore(sunTimes.sunrise)) {
nextEvent = sunTimes.sunrise;
} else if (now.isBefore(sunTimes.sunset)) {
@@ -198,7 +196,7 @@ Module.register("clock", {
const moonIllumination = SunCalc.getMoonIllumination(now.toDate());
const moonTimes = SunCalc.getMoonTimes(now, this.config.lat, this.config.lon);
const moonRise = moonTimes.rise;
var moonSet;
let moonSet;
if (moment(moonTimes.set).isAfter(moonTimes.rise)) {
moonSet = moonTimes.set;
} else {
@@ -224,6 +222,7 @@ Module.register("clock", {
/****************************************************************
* Create wrappers for ANALOG clock, only if specified in config
*/
const clockCircle = document.createElement("div");
if (this.config.displayType !== "digital") {
// If it isn't 'digital', then an 'analog' clock was also requested
@@ -232,12 +231,11 @@ Module.register("clock", {
if (this.config.timezone) {
now.tz(this.config.timezone);
}
var second = now.seconds() * 6,
const second = now.seconds() * 6,
minute = now.minute() * 6 + second / 60,
hour = ((now.hours() % 12) / 12) * 360 + 90 + minute / 12;
// Create wrappers
var clockCircle = document.createElement("div");
clockCircle.className = "clockCircle";
clockCircle.style.width = this.config.analogSize;
clockCircle.style.height = this.config.analogSize;
@@ -252,14 +250,14 @@ Module.register("clock", {
} else if (this.config.analogFace !== "none") {
clockCircle.style.border = "2px solid white";
}
var clockFace = document.createElement("div");
const clockFace = document.createElement("div");
clockFace.className = "clockFace";
var clockHour = document.createElement("div");
const clockHour = document.createElement("div");
clockHour.id = "clockHour";
clockHour.style.transform = "rotate(" + hour + "deg)";
clockHour.className = "clockHour";
var clockMinute = document.createElement("div");
const clockMinute = document.createElement("div");
clockMinute.id = "clockMinute";
clockMinute.style.transform = "rotate(" + minute + "deg)";
clockMinute.className = "clockMinute";
@@ -269,7 +267,7 @@ Module.register("clock", {
clockFace.appendChild(clockMinute);
if (this.config.displaySeconds) {
var clockSecond = document.createElement("div");
const clockSecond = document.createElement("div");
clockSecond.id = "clockSecond";
clockSecond.style.transform = "rotate(" + second + "deg)";
clockSecond.className = "clockSecond";
@@ -312,14 +310,14 @@ Module.register("clock", {
}
} else {
// Both clocks have been configured, check position
var placement = this.config.analogPlacement;
const placement = this.config.analogPlacement;
var analogWrapper = document.createElement("div");
const analogWrapper = document.createElement("div");
analogWrapper.id = "analog";
analogWrapper.style.cssFloat = "none";
analogWrapper.appendChild(clockCircle);
var digitalWrapper = document.createElement("div");
const digitalWrapper = document.createElement("div");
digitalWrapper.id = "digital";
digitalWrapper.style.cssFloat = "none";
digitalWrapper.appendChild(dateWrapper);
@@ -328,8 +326,8 @@ Module.register("clock", {
digitalWrapper.appendChild(moonWrapper);
digitalWrapper.appendChild(weekWrapper);
var appendClocks = function (condition, pos1, pos2) {
var padding = [0, 0, 0, 0];
const appendClocks = (condition, pos1, pos2) => {
const padding = [0, 0, 0, 0];
padding[placement === condition ? pos1 : pos2] = "20px";
analogWrapper.style.padding = padding.join(" ");
if (placement === condition) {

View File

@@ -17,7 +17,7 @@
width: 6px;
height: 6px;
margin: -3px 0 0 -3px;
background: white;
background: var(--color-text-bright);
border-radius: 3px;
content: "";
display: block;
@@ -29,9 +29,9 @@
position: absolute;
top: 50%;
left: 50%;
margin: -2px 0 -2px -25%; /* numbers much match negative length & thickness */
margin: -2px 0 -2px -25%; /* numbers must match negative length & thickness */
padding: 2px 0 2px 25%; /* indicator length & thickness */
background: white;
background: var(--color-text-bright);
transform-origin: 100% 50%;
border-radius: 3px 0 0 3px;
}
@@ -44,7 +44,7 @@
left: 50%;
margin: -35% -2px 0; /* numbers must match negative length & thickness */
padding: 35% 2px 0; /* indicator length & thickness */
background: white;
background: var(--color-text-bright);
transform-origin: 50% 100%;
border-radius: 3px 0 0 3px;
}
@@ -57,7 +57,7 @@
left: 50%;
margin: -38% -1px 0 0; /* numbers must match negative length & thickness */
padding: 38% 1px 0 0; /* indicator length & thickness */
background: #888;
background: var(--color-text);
transform-origin: 50% 100%;
}

View File

@@ -39,37 +39,35 @@ Module.register("compliments", {
this.lastComplimentIndex = -1;
var self = this;
if (this.config.remoteFile !== null) {
this.complimentFile(function (response) {
self.config.compliments = JSON.parse(response);
self.updateDom();
this.complimentFile((response) => {
this.config.compliments = JSON.parse(response);
this.updateDom();
});
}
// Schedule update timer.
setInterval(function () {
self.updateDom(self.config.fadeSpeed);
setInterval(() => {
this.updateDom(this.config.fadeSpeed);
}, this.config.updateInterval);
},
/* randomIndex(compliments)
/**
* Generate a random index for a list of compliments.
*
* argument compliments Array<String> - Array with compliments.
*
* return Number - Random index.
* @param {string[]} compliments Array with compliments.
* @returns {number} a random index of given array
*/
randomIndex: function (compliments) {
if (compliments.length === 1) {
return 0;
}
var generate = function () {
const generate = function () {
return Math.floor(Math.random() * compliments.length);
};
var complimentIndex = generate();
let complimentIndex = generate();
while (complimentIndex === this.lastComplimentIndex) {
complimentIndex = generate();
@@ -80,15 +78,15 @@ Module.register("compliments", {
return complimentIndex;
},
/* complimentArray()
/**
* Retrieve an array of compliments for the time of the day.
*
* return compliments Array<String> - Array with compliments for the time of the day.
* @returns {string[]} array with compliments for the time of the day.
*/
complimentArray: function () {
var hour = moment().hour();
var date = this.config.mockDate ? this.config.mockDate : moment().format("YYYY-MM-DD");
var compliments;
const hour = moment().hour();
const date = this.config.mockDate ? this.config.mockDate : moment().format("YYYY-MM-DD");
let compliments;
if (hour >= this.config.morningStartTime && hour < this.config.morningEndTime && this.config.compliments.hasOwnProperty("morning")) {
compliments = this.config.compliments.morning.slice(0);
@@ -99,7 +97,7 @@ Module.register("compliments", {
}
if (typeof compliments === "undefined") {
compliments = new Array();
compliments = [];
}
if (this.currentWeatherType in this.config.compliments) {
@@ -108,7 +106,7 @@ Module.register("compliments", {
compliments.push.apply(compliments, this.config.compliments.anytime);
for (var entry in this.config.compliments) {
for (let entry in this.config.compliments) {
if (new RegExp(entry).test(date)) {
compliments.push.apply(compliments, this.config.compliments[entry]);
}
@@ -117,11 +115,13 @@ Module.register("compliments", {
return compliments;
},
/* complimentFile(callback)
/**
* Retrieve a file from the local filesystem
*
* @param {Function} callback Called when the file is retrieved.
*/
complimentFile: function (callback) {
var xobj = new XMLHttpRequest(),
const xobj = new XMLHttpRequest(),
isRemote = this.config.remoteFile.indexOf("http://") === 0 || this.config.remoteFile.indexOf("https://") === 0,
path = isRemote ? this.config.remoteFile : this.file(this.config.remoteFile);
xobj.overrideMimeType("application/json");
@@ -134,16 +134,16 @@ Module.register("compliments", {
xobj.send(null);
},
/* complimentArray()
/**
* Retrieve a random compliment.
*
* return compliment string - A compliment.
* @returns {string} a compliment
*/
randomCompliment: function () {
// get the current time of day compliments list
var compliments = this.complimentArray();
const compliments = this.complimentArray();
// variable for index to next message to display
let index = 0;
let index;
// are we randomizing
if (this.config.random) {
// yes
@@ -159,16 +159,16 @@ Module.register("compliments", {
// Override dom generator.
getDom: function () {
var wrapper = document.createElement("div");
const wrapper = document.createElement("div");
wrapper.className = this.config.classes ? this.config.classes : "thin xlarge bright pre-line";
// get the compliment text
var complimentText = this.randomCompliment();
const complimentText = this.randomCompliment();
// split it into parts on newline text
var parts = complimentText.split("\n");
const parts = complimentText.split("\n");
// create a span to hold it all
var compliment = document.createElement("span");
const compliment = document.createElement("span");
// process all the parts of the compliment text
for (var part of parts) {
for (const part of parts) {
// create a text element for each part
compliment.appendChild(document.createTextNode(part));
// add a break `

View File

@@ -1,13 +1,10 @@
/* Magic Mirror
* Default Modules List
/* Magic Mirror Default Modules List
* Modules listed below can be loaded without the 'default/' prefix. Omitting the default folder name.
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
// Modules listed below can be loaded without the 'default/' prefix. Omitting the default folder name.
var defaultModules = ["alert", "calendar", "clock", "compliments", "currentweather", "helloworld", "newsfeed", "weatherforecast", "updatenotification", "weather"];
const defaultModules = ["alert", "calendar", "clock", "compliments", "currentweather", "helloworld", "newsfeed", "weatherforecast", "updatenotification", "weather"];
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {

View File

@@ -12,3 +12,12 @@ iframe.newsfeed-fullarticle {
bottom: inherit;
top: -90px;
}
.newsfeed-list {
list-style: none;
}
.newsfeed-list li {
text-align: justify;
margin-bottom: 0.5em;
}

View File

@@ -14,6 +14,7 @@ Module.register("newsfeed", {
encoding: "UTF-8" //ISO-8859-1
}
],
showAsList: false,
showSourceTitle: true,
showPublishDate: true,
broadcastNewsFeeds: true,
@@ -89,8 +90,8 @@ Module.register("newsfeed", {
this.loaded = true;
this.error = null;
} else if (notification === "INCORRECT_URL") {
this.error = `Incorrect url: ${payload.url}`;
} else if (notification === "NEWSFEED_ERROR") {
this.error = this.translate(payload.error_type);
this.scheduleUpdateInterval();
}
},
@@ -128,6 +129,10 @@ Module.register("newsfeed", {
}
const item = this.newsItems[this.activeItem];
const items = this.newsItems.map(function (item) {
item.publishDate = moment(new Date(item.pubdate)).fromNow();
return item;
});
return {
loaded: true,
@@ -135,7 +140,8 @@ Module.register("newsfeed", {
sourceTitle: item.sourceTitle,
publishDate: moment(new Date(item.pubdate)).fromNow(),
title: item.title,
description: item.description
description: item.description,
items: items
};
},
@@ -183,16 +189,15 @@ Module.register("newsfeed", {
}
if (this.config.prohibitedWords.length > 0) {
newsItems = newsItems.filter(function (value) {
newsItems = newsItems.filter(function (item) {
for (let word of this.config.prohibitedWords) {
if (value["title"].toLowerCase().indexOf(word.toLowerCase()) > -1) {
if (item.title.toLowerCase().indexOf(word.toLowerCase()) > -1) {
return false;
}
}
return true;
}, this);
}
newsItems.forEach((item) => {
//Remove selected tags from the beginning of rss feed items (title or description)
if (this.config.removeStartTags === "title" || this.config.removeStartTags === "both") {

View File

@@ -1,26 +1,59 @@
{% if loaded %}
<div>
{% if (config.showSourceTitle and sourceTitle) or config.showPublishDate %}
<div class="newsfeed-source light small dimmed">
{% if sourceTitle and config.showSourceTitle %}
{{ sourceTitle }}{% if config.showPublishDate %}, {% else %}: {% endif %}
{% endif %}
{% if config.showPublishDate %}
{{ publishDate }}:
{% endif %}
{% if config.showAsList %}
<ul class="newsfeed-list">
{% for item in items %}
<li>
{% if (config.showSourceTitle and item.sourceTitle) or config.showPublishDate %}
<div class="newsfeed-source light small dimmed">
{% if item.sourceTitle and config.showSourceTitle %}
{{ item.sourceTitle }}{% if config.showPublishDate %}, {% else %}: {% endif %}
{% endif %}
{% if config.showPublishDate %}
{{ item.publishDate }}:
{% endif %}
</div>
{% endif %}
<div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}">
{{ item.title }}
</div>
{% if config.showDescription %}
<div class="newsfeed-desc small light{{ ' no-wrap' if not config.wrapDescription }}">
{% if config.truncDescription %}
{{ item.description | truncate(config.lengthDescription) }}
{% else %}
{{ item.description }}
{% endif %}
</div>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<div>
{% if (config.showSourceTitle and sourceTitle) or config.showPublishDate %}
<div class="newsfeed-source light small dimmed">
{% if sourceTitle and config.showSourceTitle %}
{{ sourceTitle }}{% if config.showPublishDate %}, {% else %}: {% endif %}
{% endif %}
{% if config.showPublishDate %}
{{ publishDate }}:
{% endif %}
</div>
{% endif %}
<div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}">
{{ title }}
</div>
{% endif %}
<div class="newsfeed-title bright medium light{{ ' no-wrap' if not config.wrapTitle }}">
{{ title }}
</div>
<div class="newsfeed-desc small light{{ ' no-wrap' if not config.wrapDescription }}">
{% if config.truncDescription %}
{{ description | truncate(config.lengthDescription) }}
{% else %}
{{ description }}
{% if config.showDescription %}
<div class="newsfeed-desc small light{{ ' no-wrap' if not config.wrapDescription }}">
{% if config.truncDescription %}
{{ description | truncate(config.lengthDescription) }}
{% else %}
{{ description }}
{% endif %}
</div>
{% endif %}
</div>
</div>
{% endif %}
{% elseif error %}
<div class="small dimmed">
{{ "MODULE_CONFIG_ERROR" | translate({MODULE_NAME: "Newsfeed", ERROR: error}) | safe }}

View File

@@ -6,6 +6,7 @@
*/
const Log = require("logger");
const FeedMe = require("feedme");
const NodeHelper = require("node_helper");
const fetch = require("node-fetch");
const iconv = require("iconv-lite");
@@ -84,12 +85,13 @@ const NewsfeedFetcher = function (url, reloadInterval, encoding, logFeedWarnings
};
fetch(url, { headers: headers })
.then(NodeHelper.checkFetchStatus)
.then((response) => {
response.body.pipe(iconv.decodeStream(encoding)).pipe(parser);
})
.catch((error) => {
fetchFailedCallback(this, error);
scheduleTimer();
})
.then((res) => {
res.body.pipe(iconv.decodeStream(encoding)).pipe(parser);
});
};

View File

@@ -27,8 +27,8 @@ module.exports = NodeHelper.create({
* Creates a fetcher for a new feed if it doesn't exist yet.
* Otherwise it reuses the existing one.
*
* @param {object} feed The feed object.
* @param {object} config The configuration object.
* @param {object} feed The feed object
* @param {object} config The configuration object
*/
createFetcher: function (feed, config) {
const url = feed.url || "";
@@ -38,13 +38,14 @@ module.exports = NodeHelper.create({
try {
new URL(url);
} catch (error) {
this.sendSocketNotification("INCORRECT_URL", { url: url });
Log.error("Newsfeed Error. Malformed newsfeed url: ", url, error);
this.sendSocketNotification("NEWSFEED_ERROR", { error_type: "MODULE_ERROR_MALFORMED_URL" });
return;
}
let fetcher;
if (typeof this.fetchers[url] === "undefined") {
Log.log("Create new news fetcher for url: " + url + " - Interval: " + reloadInterval);
Log.log("Create new newsfetcher for url: " + url + " - Interval: " + reloadInterval);
fetcher = new NewsfeedFetcher(url, reloadInterval, encoding, config.logFeedWarnings);
fetcher.onReceive(() => {
@@ -52,15 +53,16 @@ module.exports = NodeHelper.create({
});
fetcher.onError((fetcher, error) => {
this.sendSocketNotification("FETCH_ERROR", {
url: fetcher.url(),
error: error
Log.error("Newsfeed Error. Could not fetch newsfeed: ", url, error);
let error_type = NodeHelper.checkFetchError(error);
this.sendSocketNotification("NEWSFEED_ERROR", {
error_type
});
});
this.fetchers[url] = fetcher;
} else {
Log.log("Use existing news fetcher for url: " + url);
Log.log("Use existing newsfetcher for url: " + url);
fetcher = this.fetchers[url];
fetcher.setReloadInterval(reloadInterval);
fetcher.broadcastItems();

View File

@@ -5,33 +5,35 @@
* MIT Licensed.
*/
Module.register("updatenotification", {
// Define module defaults
defaults: {
updateInterval: 10 * 60 * 1000, // every 10 minutes
refreshInterval: 24 * 60 * 60 * 1000, // one day
ignoreModules: [],
timeout: 1000
timeout: 5000
},
suspended: false,
moduleList: {},
// Override start method.
start: function () {
var self = this;
Log.log("Start updatenotification");
Log.info("Starting module: " + this.name);
setInterval(() => {
self.moduleList = {};
self.updateDom(2);
}, self.config.refreshInterval);
this.moduleList = {};
this.updateDom(2);
}, this.config.refreshInterval);
},
notificationReceived: function (notification, payload, sender) {
if (notification === "DOM_OBJECTS_CREATED") {
this.sendSocketNotification("CONFIG", this.config);
this.sendSocketNotification("MODULES", Module.definitions);
//this.hide(0, { lockString: self.identifier });
//this.hide(0, { lockString: this.identifier });
}
},
// Override socket notification handler.
socketNotificationReceived: function (notification, payload) {
if (notification === "STATUS") {
this.updateUI(payload);
@@ -39,13 +41,12 @@ Module.register("updatenotification", {
},
updateUI: function (payload) {
var self = this;
if (payload && payload.behind > 0) {
// if we haven't seen info for this module
if (this.moduleList[payload.module] === undefined) {
// save it
this.moduleList[payload.module] = payload;
self.updateDom(2);
this.updateDom(2);
}
//self.show(1000, { lockString: self.identifier });
} else if (payload && payload.behind === 0) {
@@ -53,41 +54,41 @@ Module.register("updatenotification", {
if (this.moduleList[payload.module] !== undefined) {
// remove it
delete this.moduleList[payload.module];
self.updateDom(2);
this.updateDom(2);
}
}
},
diffLink: function (module, text) {
var localRef = module.hash;
var remoteRef = module.tracking.replace(/.*\//, "");
const localRef = module.hash;
const remoteRef = module.tracking.replace(/.*\//, "");
return '<a href="https://github.com/MichMich/MagicMirror/compare/' + localRef + "..." + remoteRef + '" ' + 'class="xsmall dimmed" ' + 'style="text-decoration: none;" ' + 'target="_blank" >' + text + "</a>";
},
// Override dom generator.
getDom: function () {
var wrapper = document.createElement("div");
const wrapper = document.createElement("div");
if (this.suspended === false) {
// process the hash of module info found
for (var key of Object.keys(this.moduleList)) {
for (const key of Object.keys(this.moduleList)) {
let m = this.moduleList[key];
var message = document.createElement("div");
const message = document.createElement("div");
message.className = "small bright";
var icon = document.createElement("i");
const icon = document.createElement("i");
icon.className = "fa fa-exclamation-circle";
icon.innerHTML = "&nbsp;";
message.appendChild(icon);
var updateInfoKeyName = m.behind === 1 ? "UPDATE_INFO_SINGLE" : "UPDATE_INFO_MULTIPLE";
const updateInfoKeyName = m.behind === 1 ? "UPDATE_INFO_SINGLE" : "UPDATE_INFO_MULTIPLE";
var subtextHtml = this.translate(updateInfoKeyName, {
let subtextHtml = this.translate(updateInfoKeyName, {
COMMIT_COUNT: m.behind,
BRANCH_NAME: m.current
});
var text = document.createElement("span");
const text = document.createElement("span");
if (m.module === "default") {
text.innerHTML = this.translate("UPDATE_NOTIFICATION");
subtextHtml = this.diffLink(m, subtextHtml);
@@ -100,7 +101,7 @@ Module.register("updatenotification", {
wrapper.appendChild(message);
var subtext = document.createElement("div");
const subtext = document.createElement("div");
subtext.innerHTML = subtextHtml;
subtext.className = "xsmall dimmed";
wrapper.appendChild(subtext);

View File

@@ -5,24 +5,30 @@
{% set forecast = forecast.slice(0, numSteps) %}
{% for f in forecast %}
<tr {% if config.colored %}class="colored"{% endif %} {% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}>
{% if (currentStep == 0) %}
{% if (currentStep == 0) and config.ignoreToday == false %}
<td class="day">{{ "TODAY" | translate }}</td>
{% elif (currentStep == 1) %}
{% elif (currentStep == 1) and config.ignoreToday == false %}
<td class="day">{{ "TOMORROW" | translate }}</td>
{% else %}
<td class="day">{{ f.date.format('ddd') }}</td>
{% endif %}
<td class="bright weather-icon"><span class="wi weathericon wi-{{ f.weatherType }}"></span></td>
<td class="align-right bright max-temp">
{{ f.maxTemperature | roundValue | unit("temperature") }}
{{ f.maxTemperature | roundValue | unit("temperature") | decimalSymbol }}
</td>
<td class="align-right min-temp">
{{ f.minTemperature | roundValue | unit("temperature") }}
{{ f.minTemperature | roundValue | unit("temperature") | decimalSymbol }}
</td>
{% if config.showPrecipitationAmount %}
<td class="align-right bright precipitation">
{{ f.precipitation | unit("precip") }}
</td>
{% if f.precipitationUnits %}
<td class="align-right bright precipitation">
{{ f.precipitation }}{{ f.precipitationUnits }}
</td>
{% else %}
<td class="align-right bright precipitation">
{{ f.precipitation | unit("precip") }}
</td>
{% endif %}
{% endif %}
</tr>
{% set currentStep = currentStep + 1 %}

View File

@@ -11,9 +11,15 @@
{{ hour.temperature | roundValue | unit("temperature") }}
</td>
{% if config.showPrecipitationAmount %}
<td class="align-right bright precipitation">
{{ hour.precipitation | unit("precip") }}
</td>
{% if hour.precipitationUnits %}
<td class="align-right bright precipitation">
{{ hour.precipitation }}{{ hour.precipitationUnits }}
</td>
{% else %}
<td class="align-right bright precipitation">
{{ hour.precipitation | unit("precip") }}
</td>
{% endif %}
{% endif %}
</tr>
{% set currentStep = currentStep + 1 %}

View File

@@ -0,0 +1,649 @@
/* global WeatherProvider, WeatherObject */
/* Magic Mirror
* Module: Weather
* Provider: Environment Canada (EC)
*
* This class is a provider for Environment Canada MSC Datamart
* Note that this is only for Canadian locations and does not require an API key (access is anonymous)
*
* EC Documentation at following links:
* https://dd.weather.gc.ca/citypage_weather/schema/
* https://eccc-msc.github.io/open-data/msc-datamart/readme_en/
*
* This module supports Canadian locations only and requires 2 additional config parms:
*
* siteCode - the city/town unique identifier for which weather is to be displayed. Format is 's0000000'.
*
* provCode - the 2-character province code for the selected city/town.
*
* Example: for Toronto, Ontario, the following parms would be used
*
* siteCode: 's0000458',
* provCode: 'ON'
*
* To determine the siteCode and provCode values for a Canadian city/town, look at the Environment Canada document
* at https://dd.weather.gc.ca/citypage_weather/docs/site_list_en.csv (or site_list_fr.csv). There you will find a table
* with locations you can search under column B (English Names), with the corresponding siteCode under
* column A (Codes) and provCode under column C (Province).
*
* Original by Kevin Godin
*
* License to use Environment Canada (EC) data is detailed here:
* https://eccc-msc.github.io/open-data/licence/readme_en/
*
*/
WeatherProvider.register("envcanada", {
// Set the name of the provider for debugging and alerting purposes (eg. provide eye-catcher)
providerName: "Environment Canada",
// Set the default config properties that is specific to this provider
defaults: {
siteCode: "s1234567",
provCode: "ON"
},
//
// Set config values (equates to weather module config values). Also set values pertaining to caching of
// Today's temperature forecast (for use in the Forecast functions below)
//
setConfig: function (config) {
this.config = config;
this.todayTempCacheMin = 0;
this.todayTempCacheMax = 0;
this.todayCached = false;
this.cacheCurrentTemp = 999;
},
//
// Called when the weather provider is started
//
start: function () {
Log.info(`Weather provider: ${this.providerName} started.`);
this.setFetchedLocation(this.config.location);
// Ensure kmH are ignored since these are custom-handled by this Provider
this.config.useKmh = false;
},
//
// Override the fetchCurrentWeather method to query EC and construct a Current weather object
//
fetchCurrentWeather() {
this.fetchData(this.getUrl(), "GET")
.then((data) => {
if (!data) {
// Did not receive usable new data.
return;
}
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
this.setCurrentWeather(currentWeather);
})
.catch(function (request) {
Log.error("Could not load EnvCanada site data ... ", request);
})
.finally(() => this.updateAvailable());
},
//
// Override the fetchWeatherForecast method to query EC and construct Forecast weather objects
//
fetchWeatherForecast() {
this.fetchData(this.getUrl(), "GET")
.then((data) => {
if (!data) {
// Did not receive usable new data.
return;
}
const forecastWeather = this.generateWeatherObjectsFromForecast(data);
this.setWeatherForecast(forecastWeather);
})
.catch(function (request) {
Log.error("Could not load EnvCanada forecast data ... ", request);
})
.finally(() => this.updateAvailable());
},
//
// Override the fetchWeatherHourly method to query EC and construct Forecast weather objects
//
fetchWeatherHourly() {
this.fetchData(this.getUrl(), "GET")
.then((data) => {
if (!data) {
// Did not receive usable new data.
return;
}
const hourlyWeather = this.generateWeatherObjectsFromHourly(data);
this.setWeatherHourly(hourlyWeather);
})
.catch(function (request) {
Log.error("Could not load EnvCanada hourly data ... ", request);
})
.finally(() => this.updateAvailable());
},
//
// Override fetchData function to handle XML document (base function assumes JSON)
//
fetchData: function (url, method = "GET", data = null) {
return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
request.open(method, url, true);
request.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status === 200) {
resolve(this.responseXML);
} else {
reject(request);
}
}
};
request.send();
});
},
//////////////////////////////////////////////////////////////////////////////////
//
// Environment Canada methods - not part of the standard Provider methods
//
//////////////////////////////////////////////////////////////////////////////////
//
// Build the EC URL based on the Site Code and Province Code specified in the config parms. Note that the
// URL defaults to the Englsih version simply because there is no language dependancy in the data
// being accessed. This is only pertinent when using the EC data elements that contain a textual forecast.
//
// Also note that access is supported through a proxy service (thingproxy.freeboard.io) to mitigate
// CORS errors when accessing EC
//
getUrl() {
var path = "https://thingproxy.freeboard.io/fetch/https://dd.weather.gc.ca/citypage_weather/xml/" + this.config.provCode + "/" + this.config.siteCode + "_e.xml";
return path;
},
//
// Generate a WeatherObject based on current EC weather conditions
//
generateWeatherObjectFromCurrentWeather(ECdoc) {
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
// There are instances where EC will update weather data and current temperature will not be
// provided. While this is a defect in the EC systems, we need to accommodate to avoid a current temp
// of NaN being displayed. Therefore... whenever we get a valid current temp from EC, we will cache
// the value. Whenever EC data is missing current temp, we will provide the cached value
// instead. This is reasonable since the cached value will typically be accurate within the previous
// hour. The only time this does not work as expected is when MM is restarted and the first query to
// EC finds no current temp. In this scenario, MM will end up displaying a current temp of null;
if (ECdoc.querySelector("siteData currentConditions temperature").textContent) {
currentWeather.temperature = this.convertTemp(ECdoc.querySelector("siteData currentConditions temperature").textContent);
this.cacheCurrentTemp = currentWeather.temperature;
} else {
currentWeather.temperature = this.cacheCurrentTemp;
}
currentWeather.windSpeed = this.convertWind(ECdoc.querySelector("siteData currentConditions wind speed").textContent);
currentWeather.windDirection = ECdoc.querySelector("siteData currentConditions wind bearing").textContent;
currentWeather.humidity = ECdoc.querySelector("siteData currentConditions relativeHumidity").textContent;
// Ensure showPrecipitationAmount is forced to false. EC does not really provide POP for current day
// and this feature for the weather module (current only) is sort of broken in that it wants
// to say POP but will display precip as an accumulated amount vs. a percentage.
this.config.showPrecipitationAmount = false;
//
// If the module config wants to showFeelsLike... default to the current temperature.
// Check for EC wind chill and humidex values and overwrite the feelsLikeTemp value.
// This assumes that the EC current conditions will never contain both a wind chill
// and humidex temperature.
//
if (this.config.showFeelsLike) {
currentWeather.feelsLikeTemp = currentWeather.temperature;
if (ECdoc.querySelector("siteData currentConditions windChill")) {
currentWeather.feelsLikeTemp = this.convertTemp(ECdoc.querySelector("siteData currentConditions windChill").textContent);
}
if (ECdoc.querySelector("siteData currentConditions humidex")) {
currentWeather.feelsLikeTemp = this.convertTemp(ECdoc.querySelector("siteData currentConditions humidex").textContent);
}
}
//
// Need to map EC weather icon to MM weatherType values
//
currentWeather.weatherType = this.convertWeatherType(ECdoc.querySelector("siteData currentConditions iconCode").textContent);
//
// Capture the sunrise and sunset values from EC data
//
var sunList = ECdoc.querySelectorAll("siteData riseSet dateTime");
currentWeather.sunrise = moment(sunList[1].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss");
currentWeather.sunset = moment(sunList[3].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss");
return currentWeather;
},
//
// Generate an array of WeatherObjects based on EC weather forecast
//
generateWeatherObjectsFromForecast(ECdoc) {
// Declare an array to hold each day's forecast object
const days = [];
var weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
var foreBaseDates = ECdoc.querySelectorAll("siteData forecastGroup dateTime");
var baseDate = foreBaseDates[1].querySelector("timeStamp").textContent;
weather.date = moment(baseDate, "YYYYMMDDhhmmss");
var foreGroup = ECdoc.querySelectorAll("siteData forecastGroup forecast");
// For simplicity, we will only accumulate precipitation and will not try to break out
// rain vs snow accumulations
weather.rain = null;
weather.snow = null;
weather.precipitation = null;
//
// The EC forecast is held in a 12-element array - Elements 0 to 11 - with each day encompassing
// 2 elements. the first element for a day details the Today (daytime) forecast while the second
// element details the Tonight (nightime) forecast. Element 0 is always for the current day.
//
// However... the forecast is somewhat 'rolling'.
//
// If the EC forecast is queried in the morning, then Element 0 will contain Current
// Today and Element 1 will contain Current Tonight. From there, the next 5 days of forecast will be
// contained in Elements 2/3, 4/5, 6/7, 8/9, and 10/11. This module will create a 6-day forecast using
// all of these Elements.
//
// But, if the EC forecast is queried in late afternoon, the Current Today forecast will be rolled
// off and Element 0 will contain Current Tonight. From there, the next 5 days will be contained in
// Elements 1/2, 3/4, 5/6, 7/8, and 9/10. As well, Elelement 11 will contain a forecast for a 6th day,
// but only for the Today portion (not Tonight). This module will create a 6-day forecast using
// Elements 0 to 11, and will ignore the additional Todat forecast in Element 11.
//
// We need to determine if Element 0 is showing the forecast for Current Today or Current Tonight.
// This is required to understand how Min and Max temperature will be determined, and to understand
// where the next day's (aka Tomorrow's) forecast is located in the forecast array.
//
var nextDay = 0;
var lastDay = 0;
var currentTemp = ECdoc.querySelector("siteData currentConditions temperature").textContent;
//
// If the first Element is Current Today, look at Current Today and Current Tonight for the current day.
//
if (foreGroup[0].querySelector("period[textForecastName='Today']")) {
this.todaytempCacheMin = 0;
this.todaytempCacheMax = 0;
this.todayCached = true;
this.setMinMaxTemps(weather, foreGroup, 0, true, currentTemp);
this.setPrecipitation(weather, foreGroup, 0);
//
// Set the Element number that will reflect where the next day's forecast is located. Also set
// the Element number where the end of the forecast will be. This is important because of the
// rolling nature of the EC forecast. In the current scenario (Today and Tonight are present
// in elements 0 and 11, we know that we will have 6 full days of forecasts and we will use
// them. We will set lastDay such that we iterate through all 12 elements of the forecast.
//
nextDay = 2;
lastDay = 12;
}
//
// If the first Element is Current Tonight, look at Tonight only for the current day.
//
if (foreGroup[0].querySelector("period[textForecastName='Tonight']")) {
this.setMinMaxTemps(weather, foreGroup, 0, false, currentTemp);
this.setPrecipitation(weather, foreGroup, 0);
//
// Set the Element number that will reflect where the next day's forecast is located. Also set
// the Element number where the end of the forecast will be. This is important because of the
// rolling nature of the EC forecast. In the current scenario (only Current Tonight is present
// in Element 0, we know that we will have 6 full days of forecasts PLUS a half-day and
// forecast in the final element. Because we will only use full day forecasts, we set the
// lastDay number to ensure we ignore that final half-day (in forecast Element 11).
//
nextDay = 1;
lastDay = 11;
}
//
// Need to map EC weather icon to MM weatherType values. Always pick the first Element's icon to
// reflect either Today or Tonight depending on what the forecast is showing in Element 0.
//
weather.weatherType = this.convertWeatherType(foreGroup[0].querySelector("abbreviatedForecast iconCode").textContent);
// Push the weather object into the forecast array.
days.push(weather);
//
// Now do the the rest of the forecast starting at nextDay. We will process each day using 2 EC
// forecast Elements. This will address the fact that the EC forecast always includes Today and
// Tonight for each day. This is why we iterate through the forecast by a a count of 2, with each
// iteration looking at the current Element and the next Element.
//
var lastDate = moment(baseDate, "YYYYMMDDhhmmss");
for (var stepDay = nextDay; stepDay < lastDay; stepDay += 2) {
var weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
// Add 1 to the date to reflect the current forecast day we are building
lastDate = lastDate.add(1, "day");
weather.date = moment(lastDate, "X");
// Capture the temperatures for the current Element and the next Element in order to set
// the Min and Max temperatures for the forecast
this.setMinMaxTemps(weather, foreGroup, stepDay, true, currentTemp);
weather.rain = null;
weather.snow = null;
weather.precipitation = null;
this.setPrecipitation(weather, foreGroup, stepDay);
//
// Need to map EC weather icon to MM weatherType values. Always pick the first Element icon.
//
weather.weatherType = this.convertWeatherType(foreGroup[stepDay].querySelector("abbreviatedForecast iconCode").textContent);
// Push the weather object into the forecast array.
days.push(weather);
}
return days;
},
//
// Generate an array of WeatherObjects based on EC hourly weather forecast
//
generateWeatherObjectsFromHourly(ECdoc) {
// Declare an array to hold each hour's forecast object
const hours = [];
// Get local timezone UTC offset so that each hourly time can be calculated properly
var baseHours = ECdoc.querySelectorAll("siteData hourlyForecastGroup dateTime");
var hourOffset = baseHours[1].getAttribute("UTCOffset");
//
// The EC hourly forecast is held in a 24-element array - Elements 0 to 23 - with Element 0 holding
// the forecast for the next 'on the hour' timeslot. This means the array is a rolling 24 hours.
//
var hourGroup = ECdoc.querySelectorAll("siteData hourlyForecastGroup hourlyForecast");
for (var stepHour = 0; stepHour < 24; stepHour += 1) {
var weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
// Determine local time by applying UTC offset to the forecast timestamp
var foreTime = moment(hourGroup[stepHour].getAttribute("dateTimeUTC"), "YYYYMMDDhhmmss");
var currTime = foreTime.add(hourOffset, "hours");
weather.date = moment(currTime, "X");
// Capture the temperature
weather.temperature = this.convertTemp(hourGroup[stepHour].querySelector("temperature").textContent);
// Capture Likelihood of Precipitation (LOP) and unit-of-measure values
var precipLOP = hourGroup[stepHour].querySelector("lop").textContent * 1.0;
if (precipLOP > 0) {
weather.precipitation = precipLOP;
weather.precipitationUnits = hourGroup[stepHour].querySelector("lop").getAttribute("units");
}
//
// Need to map EC weather icon to MM weatherType values. Always pick the first Element icon.
//
weather.weatherType = this.convertWeatherType(hourGroup[stepHour].querySelector("iconCode").textContent);
// Push the weather object into the forecast array.
hours.push(weather);
}
return hours;
},
//
// Determine Min and Max temp based on a supplied Forecast Element index and a boolen that denotes if
// the next Forecast element should be considered - i.e. look at Today *and* Tonight vs.Tonight-only
//
setMinMaxTemps(weather, foreGroup, today, fullDay, currentTemp) {
var todayTemp = foreGroup[today].querySelector("temperatures temperature").textContent;
var todayClass = foreGroup[today].querySelector("temperatures temperature").getAttribute("class");
//
// The following logic is largely aimed at accommodating the Current day's forecast whereby we
// can have either Current Today+Current Tonight or only Current Tonight.
//
// If fullDay is false, then we only have Tonight for the current day's forecast - meaning we have
// lost a min or max temp value for the day. Therefore, we will see if we were able to cache the the
// Today forecast for the current day. If we have, we will use them. If we do not have the cached values,
// it means that MM or the Computer has been restarted since the time EC rolled off Today from the
// forecast. In this scenario, we will simply default to the Current Conditions temperature and then
// check the Tonight temperature.
//
if (fullDay === false) {
if (this.todayCached === true) {
weather.minTemperature = this.todayTempCacheMin;
weather.maxTemperature = this.todayTempCacheMax;
} else {
weather.minTemperature = this.convertTemp(currentTemp);
weather.maxTemperature = weather.minTemperature;
}
}
//
// We will check to see if the current Element's temperature is Low or High and set weather values
// accordingly. We will also check the condition where fullDay is true *and* we are looking at forecast
// element 0. This is a special case where we will cache temperature values so that we have them later
// in the current day when the Current Today element rolls off and we have Current Tonight only.
//
if (todayClass === "low") {
weather.minTemperature = this.convertTemp(todayTemp);
if (today === 0 && fullDay === true) {
this.todayTempCacheMin = weather.minTemperature;
}
}
if (todayClass === "high") {
weather.maxTemperature = this.convertTemp(todayTemp);
if (today === 0 && fullDay === true) {
this.todayTempCacheMax = weather.maxTemperature;
}
}
var nextTemp = foreGroup[today + 1].querySelector("temperatures temperature").textContent;
var nextClass = foreGroup[today + 1].querySelector("temperatures temperature").getAttribute("class");
if (fullDay === true) {
if (nextClass === "low") {
weather.minTemperature = this.convertTemp(nextTemp);
}
if (nextClass === "high") {
weather.maxTemperature = this.convertTemp(nextTemp);
}
}
return;
},
//
// Check for a Precipitation forecast. EC can provide a forecast in 2 ways: either an accumulation figure
// or a POP percentage. If there is a POP, then that is what the module will show. If there is an accumulation,
// then it will be displayed ONLY if no POP is present.
//
// POP Logic: By default, we want to show the POP for 'daytime' since we are presuming that is what
// people are more interested in seeing. While EC provides a separate POP for daytime and nightime portions
// of each day, the weather module does not really allow for that view of a daily forecast. There we will
// ignore any nightime portion. There is an exception however! For the Current day, the EC data will only show
// the nightime forecast after a certain point in the afternoon. As such, we will be showing the nightime POP
// (if one exists) in that specific scenario.
//
// Accumulation Logic: Similar to POP, we want to show accumulation for 'daytime' since we presume that is what
// people are interested in seeing. While EC provides a separate accumulation for daytime and nightime portions
// of each day, the weather module does not really allow for that view of a daily forecast. There we will
// ignore any nightime portion. There is an exception however! For the Current day, the EC data will only show
// the nightime forecast after a certain point in that specific scenario.
//
setPrecipitation(weather, foreGroup, today) {
if (foreGroup[today].querySelector("precipitation accumulation")) {
weather.precipitation = foreGroup[today].querySelector("precipitation accumulation amount").textContent * 1.0;
weather.precipitationUnits = " " + foreGroup[today].querySelector("precipitation accumulation amount").getAttribute("units");
if (this.config.units === "imperial") {
if (weather.precipitationUnits === " cm") {
weather.precipitation = (weather.precipitation * 0.394).toFixed(2);
weather.precipitationUnits = " in";
}
if (weather.precipitationUnits === " mm") {
weather.precipitation = (weather.precipitation * 0.0394).toFixed(2);
weather.precipitationUnits = " in";
}
}
}
// Check Today element for POP
if (foreGroup[today].querySelector("abbreviatedForecast pop").textContent > 0) {
weather.precipitation = foreGroup[today].querySelector("abbreviatedForecast pop").textContent;
weather.precipitationUnits = foreGroup[today].querySelector("abbreviatedForecast pop").getAttribute("units");
}
return;
},
//
// Unit conversions
//
//
// Convert C to F temps
//
convertTemp(temp) {
if (this.config.tempUnits === "imperial") {
return 1.8 * temp + 32;
} else {
return temp;
}
},
//
// Convert km/h to mph
//
convertWind(kilo) {
if (this.config.windUnits === "imperial") {
return kilo / 1.609344;
} else {
return kilo;
}
},
//
// Convert the icons to a more usable name.
//
convertWeatherType(weatherType) {
const weatherTypes = {
"00": "day-sunny",
"01": "day-sunny",
"02": "day-sunny-overcast",
"03": "day-cloudy",
"04": "day-cloudy",
"05": "day-cloudy",
"06": "day-sprinkle",
"07": "day-showers",
"08": "day-snow",
"09": "day-thunderstorm",
10: "cloud",
11: "showers",
12: "rain",
13: "rain",
14: "sleet",
15: "sleet",
16: "snow",
17: "snow",
18: "snow",
19: "thunderstorm",
20: "cloudy",
21: "cloudy",
22: "day-cloudy",
23: "day-haze",
24: "fog",
25: "snow-wind",
26: "sleet",
27: "sleet",
28: "rain",
29: "na",
30: "night-clear",
31: "night-clear",
32: "night-partly-cloudy",
33: "night-alt-cloudy",
34: "night-alt-cloudy",
35: "night-partly-cloudy",
36: "night-alt-showers",
37: "night-rain-mix",
38: "night-alt-snow",
39: "night-thunderstorm",
40: "snow-wind",
41: "tornado",
42: "tornado",
43: "windy",
44: "smoke",
45: "sandstorm",
46: "thunderstorm",
47: "thunderstorm",
48: "tornado"
};
return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
}
});

View File

@@ -30,16 +30,14 @@ WeatherProvider.register("openweathermap", {
fetchCurrentWeather() {
this.fetchData(this.getUrl())
.then((data) => {
if (!data || !data.main || typeof data.main.temp === "undefined") {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
if (this.config.weatherEndpoint === "/onecall") {
const weatherData = this.generateWeatherObjectsFromOnecall(data);
this.setCurrentWeather(weatherData.current);
this.setFetchedLocation(`${data.timezone}`);
} else {
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
this.setCurrentWeather(currentWeather);
}
this.setFetchedLocation(`${data.name}, ${data.sys.country}`);
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
this.setCurrentWeather(currentWeather);
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
@@ -51,16 +49,15 @@ WeatherProvider.register("openweathermap", {
fetchWeatherForecast() {
this.fetchData(this.getUrl())
.then((data) => {
if (!data || !data.list || !data.list.length) {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
if (this.config.weatherEndpoint === "/onecall") {
const weatherData = this.generateWeatherObjectsFromOnecall(data);
this.setWeatherForecast(weatherData.days);
this.setFetchedLocation(`${data.timezone}`);
} else {
const forecast = this.generateWeatherObjectsFromForecast(data.list);
this.setWeatherForecast(forecast);
this.setFetchedLocation(`${data.city.name}, ${data.city.country}`);
}
this.setFetchedLocation(`${data.city.name}, ${data.city.country}`);
const forecast = this.generateWeatherObjectsFromForecast(data.list);
this.setWeatherForecast(forecast);
})
.catch(function (request) {
Log.error("Could not load data ... ", request);

View File

@@ -74,7 +74,7 @@ WeatherProvider.register("smhi", {
getClosestToCurrentTime(times) {
let now = moment();
let minDiff = undefined;
for (time of times) {
for (const time of times) {
let diff = Math.abs(moment(time.validTime).diff(now));
if (!minDiff || diff < Math.abs(moment(minDiff.validTime).diff(now))) {
minDiff = time;
@@ -149,13 +149,13 @@ WeatherProvider.register("smhi", {
* @param coordinates
*/
convertWeatherDataGroupedByDay(allWeatherData, coordinates) {
var currentWeather;
let currentWeather;
let result = [];
let allWeatherObjects = this.fillInGaps(allWeatherData).map((weatherData) => this.convertWeatherDataToObject(weatherData, coordinates));
var dayWeatherTypes = [];
let dayWeatherTypes = [];
for (weatherObject of allWeatherObjects) {
for (const weatherObject of allWeatherObjects) {
//If its the first object or if a day change we need to reset the summary object
if (!currentWeather || !currentWeather.date.isSame(weatherObject.date, "day")) {
currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
@@ -216,12 +216,12 @@ WeatherProvider.register("smhi", {
*/
fillInGaps(data) {
let result = [];
for (var i = 1; i < data.length; i++) {
for (const i = 1; i < data.length; i++) {
let to = moment(data[i].validTime);
let from = moment(data[i - 1].validTime);
let hours = moment.duration(to.diff(from)).asHours();
// For each hour add a datapoint but change the validTime
for (var j = 0; j < hours; j++) {
for (const j = 0; j < hours; j++) {
let current = Object.assign({}, data[i]);
current.validTime = from.clone().add(j, "hours").toISOString();
result.push(current);

View File

@@ -81,6 +81,7 @@ WeatherProvider.register("ukmetoffice", {
*/
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
const location = currentWeatherData.SiteRep.DV.Location;
// data times are always UTC
let nowUtc = moment.utc();
@@ -88,8 +89,8 @@ WeatherProvider.register("ukmetoffice", {
let timeInMins = nowUtc.diff(midnightUtc, "minutes");
// loop round each of the (5) periods, look for today (the first period may be yesterday)
for (var i in currentWeatherData.SiteRep.DV.Location.Period) {
let periodDate = moment.utc(currentWeatherData.SiteRep.DV.Location.Period[i].value.substr(0, 10), "YYYY-MM-DD");
for (const period of location.Period) {
const periodDate = moment.utc(period.value.substr(0, 10), "YYYY-MM-DD");
// ignore if period is before today
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
@@ -97,17 +98,17 @@ WeatherProvider.register("ukmetoffice", {
if (moment().diff(periodDate, "minutes") > 0) {
// loop round the reports looking for the one we are in
// $ value specifies the time in minutes-of-the-day: 0, 180, 360,...1260
for (var j in currentWeatherData.SiteRep.DV.Location.Period[i].Rep) {
let p = currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].$;
for (const rep of period.Rep) {
const p = rep.$;
if (timeInMins >= p && timeInMins - 180 < p) {
// finally got the one we want, so populate weather object
currentWeather.humidity = currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].H;
currentWeather.temperature = this.convertTemp(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].T);
currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].F);
currentWeather.precipitation = parseInt(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].Pp);
currentWeather.windSpeed = this.convertWindSpeed(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].S);
currentWeather.windDirection = this.convertWindDirection(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].D);
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].W);
currentWeather.humidity = rep.H;
currentWeather.temperature = this.convertTemp(rep.T);
currentWeather.feelsLikeTemp = this.convertTemp(rep.F);
currentWeather.precipitation = parseInt(rep.Pp);
currentWeather.windSpeed = this.convertWindSpeed(rep.S);
currentWeather.windDirection = this.convertWindDirection(rep.D);
currentWeather.weatherType = this.convertWeatherType(rep.W);
}
}
}
@@ -115,7 +116,7 @@ WeatherProvider.register("ukmetoffice", {
}
// determine the sunrise/sunset times - not supplied in UK Met Office data
let times = this.calcAstroData(currentWeatherData.SiteRep.DV.Location);
let times = this.calcAstroData(location);
currentWeather.sunrise = times[0];
currentWeather.sunset = times[1];
@@ -130,21 +131,21 @@ WeatherProvider.register("ukmetoffice", {
// loop round the (5) periods getting the data
// for each period array, Day is [0], Night is [1]
for (var j in forecasts.SiteRep.DV.Location.Period) {
for (const period of forecasts.SiteRep.DV.Location.Period) {
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
// data times are always UTC
const dateStr = forecasts.SiteRep.DV.Location.Period[j].value;
const dateStr = period.value;
let periodDate = moment.utc(dateStr.substr(0, 10), "YYYY-MM-DD");
// ignore if period is before today
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
// populate the weather object
weather.date = moment.utc(dateStr.substr(0, 10), "YYYY-MM-DD");
weather.minTemperature = this.convertTemp(forecasts.SiteRep.DV.Location.Period[j].Rep[1].Nm);
weather.maxTemperature = this.convertTemp(forecasts.SiteRep.DV.Location.Period[j].Rep[0].Dm);
weather.weatherType = this.convertWeatherType(forecasts.SiteRep.DV.Location.Period[j].Rep[0].W);
weather.precipitation = parseInt(forecasts.SiteRep.DV.Location.Period[j].Rep[0].PPd);
weather.minTemperature = this.convertTemp(period.Rep[1].Nm);
weather.maxTemperature = this.convertTemp(period.Rep[0].Dm);
weather.weatherType = this.convertWeatherType(period.Rep[0].W);
weather.precipitation = parseInt(period.Rep[0].PPd);
days.push(weather);
}

View File

@@ -59,9 +59,7 @@ WeatherProvider.register("ukmetofficedatahub", {
let queryStrings = "?";
queryStrings += "latitude=" + this.config.lat;
queryStrings += "&longitude=" + this.config.lon;
if (this.config.appendLocationNameToHeader) {
queryStrings += "&includeLocationName=" + true;
}
queryStrings += "&includeLocationName=" + true;
// Return URL, making sure there is a trailing "/" in the base URL.
return this.config.apiBase + (this.config.apiBase.endsWith("/") ? "" : "/") + forecastType + queryStrings;

View File

@@ -33,6 +33,7 @@ Module.register("weather", {
showIndoorHumidity: false,
maxNumberOfDays: 5,
maxEntries: 5,
ignoreToday: false,
fade: true,
fadePoint: 0.25, // Start on 1/4th of the list.
initialLoadDelay: 0, // 0 seconds delay
@@ -48,6 +49,9 @@ Module.register("weather", {
// Module properties.
weatherProvider: null,
// Can be used by the provider to display location of event if nothing else is specified
firstEvent: null,
// Define required scripts.
getStyles: function () {
return ["font-awesome.css", "weather-icons.css", "weather.css"];
@@ -88,15 +92,13 @@ Module.register("weather", {
// Override notification handler.
notificationReceived: function (notification, payload, sender) {
if (notification === "CALENDAR_EVENTS") {
var senderClasses = sender.data.classes.toLowerCase().split(" ");
const senderClasses = sender.data.classes.toLowerCase().split(" ");
if (senderClasses.indexOf(this.config.calendarClass.toLowerCase()) !== -1) {
this.firstEvent = false;
for (var e in payload) {
var event = payload[e];
this.firstEvent = null;
for (let event of payload) {
if (event.location || event.geo) {
this.firstEvent = event;
//Log.log("First upcoming event with location: ", event);
Log.debug("First upcoming event with location: ", event);
break;
}
}
@@ -114,24 +116,30 @@ Module.register("weather", {
getTemplate: function () {
switch (this.config.type.toLowerCase()) {
case "current":
return `current.njk`;
return "current.njk";
case "hourly":
return `hourly.njk`;
return "hourly.njk";
case "daily":
case "forecast":
return `forecast.njk`;
return "forecast.njk";
//Make the invalid values use the "Loading..." from forecast
default:
return `forecast.njk`;
return "forecast.njk";
}
},
// Add all the data to the template.
getTemplateData: function () {
const forecast = this.weatherProvider.weatherForecast();
if (this.config.ignoreToday) {
forecast.splice(0, 1);
}
return {
config: this.config,
current: this.weatherProvider.currentWeather(),
forecast: this.weatherProvider.weatherForecast(),
forecast: forecast,
hourly: this.weatherProvider.weatherHourly(),
indoor: {
humidity: this.indoorHumidity,
@@ -152,7 +160,7 @@ Module.register("weather", {
},
scheduleUpdate: function (delay = null) {
var nextLoad = this.config.updateInterval;
let nextLoad = this.config.updateInterval;
if (delay !== null && delay >= 0) {
nextLoad = delay;
}
@@ -176,8 +184,8 @@ Module.register("weather", {
},
roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1;
var roundValue = parseFloat(temperature).toFixed(decimals);
const decimals = this.config.roundTemp ? 0 : 1;
const roundValue = parseFloat(temperature).toFixed(decimals);
return roundValue === "-0" ? 0 : roundValue;
},
@@ -272,8 +280,8 @@ Module.register("weather", {
if (this.config.fadePoint < 0) {
this.config.fadePoint = 0;
}
var startingPoint = numSteps * this.config.fadePoint;
var numFadesteps = numSteps - startingPoint;
const startingPoint = numSteps * this.config.fadePoint;
const numFadesteps = numSteps - startingPoint;
if (currentStep >= startingPoint) {
return 1 - (currentStep - startingPoint) / numFadesteps;
} else {

View File

@@ -28,6 +28,7 @@ class WeatherObject {
this.rain = null;
this.snow = null;
this.precipitation = null;
this.precipitationUnits = null;
this.feelsLikeTemp = null;
}

View File

@@ -8,7 +8,7 @@
*
* This class is the blueprint for a weather provider.
*/
var WeatherProvider = Class.extend({
const WeatherProvider = Class.extend({
// Weather Provider Properties
providerName: null,
defaults: {},
@@ -114,7 +114,7 @@ var WeatherProvider = Class.extend({
// A convenience function to make requests. It returns a promise.
fetchData: function (url, method = "GET", data = null) {
return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
const request = new XMLHttpRequest();
request.open(method, url, true);
request.onreadystatechange = function () {
if (this.readyState === 4) {

View File

@@ -339,7 +339,9 @@ Module.register("weatherforecast", {
*
* argument data object - Weather information received form openweather.org.
*/
processWeather: function (data) {
processWeather: function (data, momenttz) {
let mom = momenttz ? momenttz : moment; // Exception last.
// Forcast16 (paid) API endpoint provides this data. Onecall endpoint
// does not.
if (data.city) {
@@ -357,8 +359,8 @@ Module.register("weatherforecast", {
var dayEnds = 17;
if (data.city && data.city.sunrise && data.city.sunset) {
dayStarts = new Date(moment.unix(data.city.sunrise).locale("en").format("YYYY/MM/DD HH:mm:ss")).getHours();
dayEnds = new Date(moment.unix(data.city.sunset).locale("en").format("YYYY/MM/DD HH:mm:ss")).getHours();
dayStarts = new Date(mom.unix(data.city.sunrise).locale("en").format("YYYY/MM/DD HH:mm:ss")).getHours();
dayEnds = new Date(mom.unix(data.city.sunset).locale("en").format("YYYY/MM/DD HH:mm:ss")).getHours();
}
// Handle different structs between forecast16 and onecall endpoints
@@ -379,11 +381,11 @@ Module.register("weatherforecast", {
var day;
var hour;
if (forecast.dt_txt) {
day = moment(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss").format("ddd");
hour = new Date(moment(forecast.dt_txt).locale("en").format("YYYY-MM-DD HH:mm:ss")).getHours();
day = mom(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss").format("ddd");
hour = new Date(mom(forecast.dt_txt).locale("en").format("YYYY-MM-DD HH:mm:ss")).getHours();
} else {
day = moment(forecast.dt, "X").format("ddd");
hour = new Date(moment(forecast.dt, "X")).getHours();
day = mom(forecast.dt, "X").format("ddd");
hour = new Date(mom(forecast.dt, "X")).getHours();
}
if (day !== lastDay) {
@@ -392,7 +394,7 @@ Module.register("weatherforecast", {
icon: this.config.iconTable[forecast.weather[0].icon],
maxTemp: this.roundValue(forecast.temp.max),
minTemp: this.roundValue(forecast.temp.min),
rain: this.processRain(forecast, forecastList)
rain: this.processRain(forecast, forecastList, mom)
};
this.forecast.push(forecastData);
lastDay = day;
@@ -482,16 +484,18 @@ Module.register("weatherforecast", {
* That object has a property "3h" which contains the amount of rain since the previous forecast in the list.
* This code finds all forecasts that is for the same day and sums the amount of rain and returns that.
*/
processRain: function (forecast, allForecasts) {
processRain: function (forecast, allForecasts, momenttz) {
let mom = momenttz ? momenttz : moment; // Exception last.
//If the amount of rain actually is a number, return it
if (!isNaN(forecast.rain)) {
return forecast.rain;
}
//Find all forecasts that is for the same day
var checkDateTime = forecast.dt_txt ? moment(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss") : moment(forecast.dt, "X");
var checkDateTime = forecast.dt_txt ? mom(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss") : mom(forecast.dt, "X");
var daysForecasts = allForecasts.filter(function (item) {
var itemDateTime = item.dt_txt ? moment(item.dt_txt, "YYYY-MM-DD hh:mm:ss") : moment(item.dt, "X");
var itemDateTime = item.dt_txt ? mom(item.dt_txt, "YYYY-MM-DD hh:mm:ss") : mom(item.dt, "X");
return itemDateTime.isSame(checkDateTime, "day") && item.rain instanceof Object;
});

14170
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "magicmirror",
"version": "2.15.0",
"version": "2.16.0",
"description": "The open source modular smart mirror platform.",
"main": "js/electron.js",
"scripts": {
@@ -10,18 +10,20 @@
"install": "echo \"Installing vendor files ...\n\" && cd vendor && npm install --loglevel=error",
"install-fonts": "echo \"Installing fonts ...\n\" && cd fonts && npm install --loglevel=error",
"postinstall": "npm run install-fonts && echo \"MagicMirror installation finished successfully! \n\"",
"test": "NODE_ENV=test mocha tests --recursive",
"test:coverage": "NODE_ENV=test nyc --reporter=lcov --reporter=text mocha tests --recursive --timeout=3000",
"test:e2e": "NODE_ENV=test mocha tests/e2e --recursive",
"test:unit": "NODE_ENV=test mocha tests/unit --recursive",
"test:prettier": "prettier --check **/*.{js,css,json,md,yml}",
"test": "NODE_ENV=test jest -i --forceExit",
"test:coverage": "NODE_ENV=test nyc --reporter=lcov --reporter=text jest -i --forceExit",
"test:e2e": "NODE_ENV=test jest --selectProjects e2e -i --forceExit",
"test:unit": "NODE_ENV=test jest --selectProjects unit -i --forceExit",
"test:prettier": "prettier . --check",
"test:js": "eslint js/**/*.js modules/default/**/*.js clientonly/*.js serveronly/*.js translations/*.js vendor/*.js tests/**/*.js config/* --config .eslintrc.json --quiet",
"test:css": "stylelint css/main.css modules/default/**/*.css --config .stylelintrc.json",
"test:calendar": "node ./modules/default/calendar/debug.js",
"config:check": "node js/check_config.js",
"lint:prettier": "prettier --write **/*.{js,css,json,md,yml}",
"lint:prettier": "prettier . --write",
"lint:js": "eslint js/**/*.js modules/default/**/*.js clientonly/*.js serveronly/*.js translations/*.js vendor/*.js tests/**/*.js config/* --config .eslintrc.json --fix",
"lint:css": "stylelint css/main.css modules/default/**/*.css --config .stylelintrc.json --fix"
"lint:css": "stylelint css/main.css modules/default/**/*.css --config .stylelintrc.json --fix",
"lint:staged": "pretty-quick --staged",
"prepare": "[ -f node_modules/.bin/husky ] && husky install || echo no husky installed."
},
"repository": {
"type": "git",
@@ -43,60 +45,74 @@
},
"homepage": "https://magicmirror.builders",
"devDependencies": {
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-jsdoc": "^32.3.0",
"eslint-plugin-prettier": "^3.3.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-jsdoc": "^35.4.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-jest": "^24.3.6",
"express-basic-auth": "^1.2.0",
"husky": "^4.3.8",
"jsdom": "^16.5.1",
"husky": "^6.0.0",
"jest": "27.0.5",
"jsdom": "^16.6.0",
"lodash": "^4.17.21",
"mocha": "^8.3.2",
"mocha-each": "^2.0.1",
"mocha-logger": "^1.0.7",
"nyc": "^15.1.0",
"prettier": "^2.2.1",
"pretty-quick": "^3.1.0",
"sinon": "^10.0.0",
"prettier": "^2.3.1",
"pretty-quick": "^3.1.1",
"sinon": "^11.1.1",
"spectron": "^13.0.0",
"stylelint": "^13.12.0",
"stylelint": "^13.13.1",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^21.0.0",
"stylelint-config-standard": "^22.0.0",
"stylelint-prettier": "^1.2.0"
},
"optionalDependencies": {
"electron": "^11.3.0"
"electron": "^11.4.9"
},
"dependencies": {
"colors": "^1.4.0",
"console-stamp": "^3.0.0-rc4.2",
"digest-fetch": "^1.1.6",
"eslint": "^7.23.0",
"console-stamp": "^3.0.2",
"digest-fetch": "^1.2.0",
"eslint": "^7.29.0",
"express": "^4.17.1",
"express-ipfilter": "^1.1.2",
"express-ipfilter": "^1.2.0",
"feedme": "^2.0.2",
"helmet": "^4.4.1",
"iconv-lite": "^0.6.2",
"helmet": "^4.6.0",
"iconv-lite": "^0.6.3",
"module-alias": "^2.2.2",
"moment": "^2.29.1",
"node-fetch": "^2.6.1",
"node-ical": "^0.12.9",
"rrule": "^2.6.8",
"rrule-alt": "^2.2.8",
"simple-git": "^2.37.0",
"socket.io": "^4.0.0"
"node-ical": "^0.13.0",
"simple-git": "^2.40.0",
"socket.io": "^4.1.2"
},
"_moduleAliases": {
"node_helper": "js/node_helper.js",
"logger": "js/logger.js"
},
"engines": {
"node": ">=10"
"node": ">=12"
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
"jest": {
"verbose": true,
"projects": [
{
"displayName": "unit",
"testMatch": [
"**/tests/unit/**/*.[jt]s?(x)"
],
"testPathIgnorePatterns": [
"<rootDir>/tests/unit/mocks"
]
},
{
"displayName": "e2e",
"testMatch": [
"**/tests/e2e/**/*.[jt]s?(x)"
],
"testPathIgnorePatterns": [
"<rootDir>/tests/e2e/modules/mocks",
"<rootDir>/tests/e2e/global-setup.js"
]
}
]
}
}

View File

@@ -1,8 +1,8 @@
const app = require("../js/app.js");
const Log = require("logger");
app.start(function (config) {
var bindAddress = config.address ? config.address : "localhost";
var httpType = config.useHttps ? "https" : "http";
app.start((config) => {
const bindAddress = config.address ? config.address : "localhost";
const httpType = config.useHttps ? "https" : "http";
Log.log("\nReady to go! Please point your browser to: " + httpType + "://" + bindAddress + ":" + config.port);
});

View File

@@ -0,0 +1,37 @@
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:xxx@gmail.com
X-WR-TIMEZONE:Europe/Zurich
BEGIN:VTIMEZONE
TZID:Etc/UTC
X-LIC-LOCATION:Etc/UTC
BEGIN:STANDARD
TZOFFSETFROM:+0000
TZOFFSETTO:+0000
TZNAME:GMT
DTSTART:19700101T00000--äüüßßß-0
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTART;VALUE=DATE:20210325
DTEND;VALUE=DATE:20210326
RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1
DTSTAMP:20210421T154106Z
UID:zzz@google.com
REATED:20200831T200244Z
DESCRIPTION:
LAST-MODIFIED:20200831T200244Z
LOCATION:
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Birthday
TRANSP:OPAQUE
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:This is an event reminder
TRIGGER:-P0DT7H0M0S
END:VALARM
END:VEVENT

View File

@@ -1,44 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
>
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
>
<channel>
<title>Rodrigo Ramírez Norambuena</title>
<atom:link href="https://rodrigoramirez.com/feed/" rel="self" type="application/rss+xml"/>
<link>https://rodrigoramirez.com</link>
<description>Temas sobre Linux, VoIP, Open Source, tecnología y lo relacionado.</description>
<lastBuildDate>Fri, 21 Oct 2016 21:30:22 +0000</lastBuildDate>
<language>es-ES</language>
<sy:updatePeriod>hourly</sy:updatePeriod>
<sy:updateFrequency>1</sy:updateFrequency>
<generator>https://wordpress.org/?v=4.7.3</generator>
<item>
<title>QPanel 0.13.0</title>
<link>https://rodrigoramirez.com/qpanel-0-13-0/</link>
<comments>https://rodrigoramirez.com/qpanel-0-13-0/#comments</comments>
<pubDate>Tue, 20 Sep 2016 11:16:08 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Software]]></category>
<category><![CDATA[app_queue]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[FreeSWITCH]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queue]]></category>
<category><![CDATA[spy]]></category>
<category><![CDATA[supervision]]></category>
<category><![CDATA[templates]]></category>
<category><![CDATA[whisper]]></category>
<channel>
<title>Rodrigo Ramírez Norambuena</title>
<atom:link href="https://rodrigoramirez.com/feed/" rel="self" type="application/rss+xml" />
<link>https://rodrigoramirez.com</link>
<description>Temas sobre Linux, VoIP, Open Source, tecnología y lo relacionado.</description>
<lastBuildDate>Fri, 21 Oct 2016 21:30:22 +0000</lastBuildDate>
<language>es-ES</language>
<sy:updatePeriod>hourly</sy:updatePeriod>
<sy:updateFrequency>1</sy:updateFrequency>
<generator>https://wordpress.org/?v=4.7.3</generator>
<item>
<title>QPanel 0.13.0</title>
<link>https://rodrigoramirez.com/qpanel-0-13-0/</link>
<comments>https://rodrigoramirez.com/qpanel-0-13-0/#comments</comments>
<pubDate>Tue, 20 Sep 2016 11:16:08 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Software]]></category>
<category><![CDATA[app_queue]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[FreeSWITCH]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queue]]></category>
<category><![CDATA[spy]]></category>
<category><![CDATA[supervision]]></category>
<category><![CDATA[templates]]></category>
<category><![CDATA[whisper]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1299</guid>
<description><![CDATA[<p>Ya está disponible la versión 0.13.0 de QPanel Para instalar esta nueva versión, la debes descargar de https://github.com/roramirez/qpanel/tree/0.13.0 En al README.md puedes encontrar las instrucciones para hacer que funcione en tu sistema. En esta nueva versión cuenta con los siguientes cambios: Se establece un limite para el reciclado del tiempo de conexión a la base [&#8230;]</p>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1299</guid>
<description><![CDATA[<p>Ya está disponible la versión 0.13.0 de QPanel Para instalar esta nueva versión, la debes descargar de https://github.com/roramirez/qpanel/tree/0.13.0 En al README.md puedes encontrar las instrucciones para hacer que funcione en tu sistema. En esta nueva versión cuenta con los siguientes cambios: Se establece un limite para el reciclado del tiempo de conexión a la base [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-13-0/">QPanel 0.13.0</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p><img class="aligncenter" src="https://raw.githubusercontent.com/roramirez/qpanel/e55aa16bbd85b579ee82e56469526270c5afa462/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="685" height="385" />Ya está disponible la versión 0.13.0 de QPanel</p>
<content:encoded><![CDATA[<p><img class="aligncenter" src="https://raw.githubusercontent.com/roramirez/qpanel/e55aa16bbd85b579ee82e56469526270c5afa462/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="685" height="385" />Ya está disponible la versión 0.13.0 de QPanel</p>
<p>Para instalar esta nueva versión, la debes descargar de</p>
<ul>
<li><a href="https://github.com/roramirez/qpanel/tree/0.13.0">https://github.com/roramirez/qpanel/tree/0.13.0</a></li>
@@ -57,25 +57,25 @@
<p>&nbsp;</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-13-0/">QPanel 0.13.0</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-0-13-0/feed/</wfw:commentRss>
<slash:comments>3</slash:comments>
</item>
<item>
<title>Problema VirtualBox &#8220;starting virtual machine&#8221; &#8230;</title>
<link>https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/</link>
<comments>https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/#respond</comments>
<pubDate>Sat, 10 Sep 2016 22:50:13 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<category><![CDATA[no arranca]]></category>
<category><![CDATA[Problema]]></category>
<category><![CDATA[VirtualBox]]></category>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-0-13-0/feed/</wfw:commentRss>
<slash:comments>3</slash:comments>
</item>
<item>
<title>Problema VirtualBox &#8220;starting virtual machine&#8221; &#8230;</title>
<link>https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/</link>
<comments>https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/#respond</comments>
<pubDate>Sat, 10 Sep 2016 22:50:13 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<category><![CDATA[no arranca]]></category>
<category><![CDATA[Problema]]></category>
<category><![CDATA[VirtualBox]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1284</guid>
<description><![CDATA[<p>Después de una actualización de Debian, de la rama stretch/sid, tuve un problema con VirtualBox.  La versión que se actualizó fue a la virtualbox 5.1.4-dfsg-1+b1. El gran problema era que ninguna maquina virtual quería arrancar, se quedaba en un largo limbo con el mensaje &#8220;starting virtual machine&#8221;, como el de la imagen de a continuación. [&#8230;]</p>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1284</guid>
<description><![CDATA[<p>Después de una actualización de Debian, de la rama stretch/sid, tuve un problema con VirtualBox.  La versión que se actualizó fue a la virtualbox 5.1.4-dfsg-1+b1. El gran problema era que ninguna maquina virtual quería arrancar, se quedaba en un largo limbo con el mensaje &#8220;starting virtual machine&#8221;, como el de la imagen de a continuación. [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/">Problema VirtualBox &#8220;starting virtual machine&#8221; &#8230;</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p>Después de una actualización de Debian, de la rama stretch/sid, tuve un problema con VirtualBox.  La versión que se actualizó fue a la virtualbox 5.1.4-dfsg-1+b1. El gran problema era que ninguna maquina virtual quería arrancar, se quedaba en un largo limbo con el mensaje &#8220;starting virtual machine&#8221;, como el de la imagen de a continuación.</p>
<content:encoded><![CDATA[<p>Después de una actualización de Debian, de la rama stretch/sid, tuve un problema con VirtualBox.  La versión que se actualizó fue a la virtualbox 5.1.4-dfsg-1+b1. El gran problema era que ninguna maquina virtual quería arrancar, se quedaba en un largo limbo con el mensaje &#8220;starting virtual machine&#8221;, como el de la imagen de a continuación.</p>
<p><a href="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-10-19-25-09.png"><img class="aligncenter wp-image-1290 size-full" src="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-10-19-25-09.png" alt="Starting virtual machine ... VirtualBox" width="648" height="554" srcset="https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-10-19-25-09.png 648w, https://rodrigoramirez.com/wp-content/uploads/Screenshot-at-2016-09-10-19-25-09-300x256.png 300w" sizes="(max-width: 648px) 100vw, 648px" /></a></p>
<p>Ninguna, pero ninguna maquina arrancó, se quedaban en ese mensaje. Fue de esos instantes en que sudas helado &#8230; <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>Con un poco de investigación fue a parar al archivo<em> ~/.VirtualBox/VBoxSVC.log </em>que indicaba</p>
@@ -85,7 +85,7 @@
<p>&nbsp;</p>
<p>Fui&#8230; algo de donde agarrarse. Mirando un poco mas se trataba de problemas con los permisos al vboxdrvu, mirando indicaba que tenía 0600.</p>
<p>&nbsp;</p>
<pre>$ ls -lh /dev/vboxdrvu
<pre>$ ls -lh /dev/vboxdrvu
crw------- 1 root root 10, 56 Sep 10 12:47 /dev/vboxdrvu</pre>
<p>&nbsp;</p>
<p>El tema es que deben estar en 0666,  le cambias los permisos y eso soluciona el problema <img src="https://s.w.org/images/core/emoji/2.2.1/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
@@ -95,24 +95,24 @@ $ ls -lh /dev/vboxdrvu
crw-rw-rw- 1 root root 10, 56 Sep 10 12:47 /dev/vboxdrvu</pre>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/">Problema VirtualBox &#8220;starting virtual machine&#8221; &#8230;</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>Mejorando la consola interactiva de Python</title>
<link>https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/</link>
<comments>https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/#comments</comments>
<pubDate>Tue, 06 Sep 2016 04:24:43 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[desarrollo]]></category>
<category><![CDATA[Desarrollo]]></category>
<category><![CDATA[Python]]></category>
<wfw:commentRss>https://rodrigoramirez.com/problema-virtualbox-starting-virtual-machine/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>Mejorando la consola interactiva de Python</title>
<link>https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/</link>
<comments>https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/#comments</comments>
<pubDate>Tue, 06 Sep 2016 04:24:43 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[desarrollo]]></category>
<category><![CDATA[Desarrollo]]></category>
<category><![CDATA[Python]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1247</guid>
<description><![CDATA[<p>Cuando estás desarrollando en Python es muy cool estar utilizando la consola interactiva para ir probando cosas antes de ponerlas dentro del archivo de código fuente. La consola de Python funciona y cumple su cometido. Solo al tipear  python  te permite entrar en modo interactivo e ir probando cosas. El punto es que a veces [&#8230;]</p>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1247</guid>
<description><![CDATA[<p>Cuando estás desarrollando en Python es muy cool estar utilizando la consola interactiva para ir probando cosas antes de ponerlas dentro del archivo de código fuente. La consola de Python funciona y cumple su cometido. Solo al tipear  python  te permite entrar en modo interactivo e ir probando cosas. El punto es que a veces [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/">Mejorando la consola interactiva de Python</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p>Cuando estás desarrollando en Python es muy <em>cool</em> estar utilizando la consola interactiva para ir probando cosas antes de ponerlas dentro del archivo de código fuente.</p>
<content:encoded><![CDATA[<p>Cuando estás desarrollando en Python es muy <em>cool</em> estar utilizando la consola interactiva para ir probando cosas antes de ponerlas dentro del archivo de código fuente.</p>
<p>La consola de Python funciona y cumple su cometido. Solo al tipear  <em>python  </em>te permite entrar en modo interactivo e ir probando cosas.</p>
<p>El punto es que a veces uno necesita ir un poco más allá. Como autocomentado de código o resaltado de sintaxis, para eso tengo dos truco que utilizo generalmente.</p>
<h2>Truco a)</h2>
@@ -139,31 +139,31 @@ $ ls -lh /dev/vboxdrvu
<p>O lo agregas a un bashrc, zshrc o la shell que ocupes.</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/">Mejorando la consola interactiva de Python</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/feed/</wfw:commentRss>
<slash:comments>4</slash:comments>
</item>
<item>
<title>QPanel 0.12.0 con estadísticas</title>
<link>https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/</link>
<comments>https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/#respond</comments>
<pubDate>Mon, 22 Aug 2016 04:19:03 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Software]]></category>
<category><![CDATA[app_queue]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[FreeSWITCH]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queue]]></category>
<category><![CDATA[spy]]></category>
<category><![CDATA[supervision]]></category>
<category><![CDATA[templates]]></category>
<category><![CDATA[whisper]]></category>
<wfw:commentRss>https://rodrigoramirez.com/mejorando-la-consola-interactiva-python/feed/</wfw:commentRss>
<slash:comments>4</slash:comments>
</item>
<item>
<title>QPanel 0.12.0 con estadísticas</title>
<link>https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/</link>
<comments>https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/#respond</comments>
<pubDate>Mon, 22 Aug 2016 04:19:03 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Software]]></category>
<category><![CDATA[app_queue]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[FreeSWITCH]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queue]]></category>
<category><![CDATA[spy]]></category>
<category><![CDATA[supervision]]></category>
<category><![CDATA[templates]]></category>
<category><![CDATA[whisper]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1268</guid>
<description><![CDATA[<p>Ya está disponible una nueva versión de QPanel, esta es la 0.12.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.12.0 En esta nueva versión las funcionalidades agregadas son: Permite remover los agentes de las cola Posibilidad de cancelar llamadas que están en espera de atención Estadísticas por rango de fecha obtenidas desde [&#8230;]</p>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1268</guid>
<description><![CDATA[<p>Ya está disponible una nueva versión de QPanel, esta es la 0.12.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.12.0 En esta nueva versión las funcionalidades agregadas son: Permite remover los agentes de las cola Posibilidad de cancelar llamadas que están en espera de atención Estadísticas por rango de fecha obtenidas desde [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/">QPanel 0.12.0 con estadísticas</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p><img class="aligncenter" src="https://raw.githubusercontent.com/roramirez/qpanel/e55aa16bbd85b579ee82e56469526270c5afa462/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="685" height="385" />Ya está disponible una nueva versión de QPanel, esta es la 0.12.0</p>
<content:encoded><![CDATA[<p><img class="aligncenter" src="https://raw.githubusercontent.com/roramirez/qpanel/e55aa16bbd85b579ee82e56469526270c5afa462/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="685" height="385" />Ya está disponible una nueva versión de QPanel, esta es la 0.12.0</p>
<p>Para instalar esta nueva versión, debes visitar la siguiente URL</p>
<ul>
<li><a href="https://github.com/roramirez/qpanel/tree/0.12.0">https://github.com/roramirez/qpanel/tree/0.12.0</a></li>
@@ -178,31 +178,31 @@ $ ls -lh /dev/vboxdrvu
<p>Si deseas colaborar con el proyecto puedes agregar nuevas sugerencias mediante un <a href="https://github.com/roramirez/qpanel/issues/new?title=[Feature]">issue</a> ó colaborar mediante <a href="https://github.com/roramirez/qpanel/blob/dd42cf0f534408505f57b0d387dffee2f3688711/README.md#how-to-contribute">mediante un Pull Request</a></p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/">QPanel 0.12.0 con estadísticas</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>QPanel 0.11.0 con Spy, Whisper y mas</title>
<link>https://rodrigoramirez.com/qpanel-spy-supervisor/</link>
<comments>https://rodrigoramirez.com/qpanel-spy-supervisor/#comments</comments>
<pubDate>Thu, 21 Jul 2016 01:53:21 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Software]]></category>
<category><![CDATA[app_queue]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[FreeSWITCH]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queue]]></category>
<category><![CDATA[spy]]></category>
<category><![CDATA[supervision]]></category>
<category><![CDATA[templates]]></category>
<category><![CDATA[whisper]]></category>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-0-12-0-estadisticas/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>QPanel 0.11.0 con Spy, Whisper y mas</title>
<link>https://rodrigoramirez.com/qpanel-spy-supervisor/</link>
<comments>https://rodrigoramirez.com/qpanel-spy-supervisor/#comments</comments>
<pubDate>Thu, 21 Jul 2016 01:53:21 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Software]]></category>
<category><![CDATA[app_queue]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[FreeSWITCH]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queue]]></category>
<category><![CDATA[spy]]></category>
<category><![CDATA[supervision]]></category>
<category><![CDATA[templates]]></category>
<category><![CDATA[whisper]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1245</guid>
<description><![CDATA[<p>Ya está disponible una nueva versión de QPanel, esta es la 0.11.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.11.0 Esta versión hemos agregado  algunas funcionalidades que los usuarios  han ido solicitando. Para esta versión es posible realizar Spy, Whisper o Barge a un canal para la supervisión de los miembros que [&#8230;]</p>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1245</guid>
<description><![CDATA[<p>Ya está disponible una nueva versión de QPanel, esta es la 0.11.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.11.0 Esta versión hemos agregado  algunas funcionalidades que los usuarios  han ido solicitando. Para esta versión es posible realizar Spy, Whisper o Barge a un canal para la supervisión de los miembros que [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-spy-supervisor/">QPanel 0.11.0 con Spy, Whisper y mas</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p><img class="aligncenter" src="https://raw.githubusercontent.com/roramirez/qpanel/e55aa16bbd85b579ee82e56469526270c5afa462/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="685" height="385" />Ya está disponible una nueva versión de QPanel, esta es la 0.11.0</p>
<content:encoded><![CDATA[<p><img class="aligncenter" src="https://raw.githubusercontent.com/roramirez/qpanel/e55aa16bbd85b579ee82e56469526270c5afa462/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="685" height="385" />Ya está disponible una nueva versión de QPanel, esta es la 0.11.0</p>
<p>Para instalar esta nueva versión, debes visitar la siguiente URL</p>
<ul>
<li><a href="https://github.com/roramirez/qpanel/tree/0.11.0">https://github.com/roramirez/qpanel/tree/0.11.0</a></li>
@@ -216,22 +216,22 @@ $ ls -lh /dev/vboxdrvu
<p>El proyecto siempre está abierto a nuevas sugerencias las cuales puedes agregar mediante un <a href="https://github.com/roramirez/qpanel/issues/new?title=[Feature]">issue</a>.</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-spy-supervisor/">QPanel 0.11.0 con Spy, Whisper y mas</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-spy-supervisor/feed/</wfw:commentRss>
<slash:comments>4</slash:comments>
</item>
<item>
<title>Añadir Swap a un sistema</title>
<link>https://rodrigoramirez.com/crear-swap/</link>
<comments>https://rodrigoramirez.com/crear-swap/#respond</comments>
<pubDate>Fri, 15 Jul 2016 05:07:43 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-spy-supervisor/feed/</wfw:commentRss>
<slash:comments>4</slash:comments>
</item>
<item>
<title>Añadir Swap a un sistema</title>
<link>https://rodrigoramirez.com/crear-swap/</link>
<comments>https://rodrigoramirez.com/crear-swap/#respond</comments>
<pubDate>Fri, 15 Jul 2016 05:07:43 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1234</guid>
<description><![CDATA[<p>Algo que me toma generalmente hacer es cuando trabajo con maquina virtuales es asignar una cantidad determinada de Swap. La  memoria swap es un espacio de intercambio en disco para cuando el sistema ya no puede utilizar más memoria RAM. El problema para mi es que algunos sistemas de maquinas virtuales no asignan por defecto [&#8230;]</p>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1234</guid>
<description><![CDATA[<p>Algo que me toma generalmente hacer es cuando trabajo con maquina virtuales es asignar una cantidad determinada de Swap. La  memoria swap es un espacio de intercambio en disco para cuando el sistema ya no puede utilizar más memoria RAM. El problema para mi es que algunos sistemas de maquinas virtuales no asignan por defecto [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/crear-swap/">Añadir Swap a un sistema</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p>Algo que me toma generalmente hacer es cuando trabajo con maquina virtuales es asignar una cantidad determinada de Swap.</p>
<content:encoded><![CDATA[<p>Algo que me toma generalmente hacer es cuando trabajo con maquina virtuales es asignar una cantidad determinada de Swap.</p>
<p>La  memoria swap es un espacio de intercambio en disco para cuando el sistema ya no puede utilizar más memoria RAM.</p>
<p>El problema para mi es que algunos sistemas de maquinas virtuales no asignan por defecto un espacio para la Swap, lo que te lleva a que el sistema pueda tener crash durante la ejecución.</p>
<p>Para comprobar la asignación de memoria, al ejecutar el comando <em>free</em> nos debería mostrar como algo similar a lo siguiente</p>
@@ -271,27 +271,27 @@ Swap:         3071          0       3071</pre>
<p>&nbsp;</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/crear-swap/">Añadir Swap a un sistema</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/crear-swap/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>QPanel 0.10.0 con vista consolidada</title>
<link>https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/</link>
<comments>https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/#respond</comments>
<pubDate>Mon, 20 Jun 2016 19:32:55 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<category><![CDATA[app_queue]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[FreeSWITCH]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queue]]></category>
<wfw:commentRss>https://rodrigoramirez.com/crear-swap/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>QPanel 0.10.0 con vista consolidada</title>
<link>https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/</link>
<comments>https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/#respond</comments>
<pubDate>Mon, 20 Jun 2016 19:32:55 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<category><![CDATA[app_queue]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[FreeSWITCH]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queue]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1227</guid>
<description><![CDATA[<p>Ya con la release numero 28 la nueva versión 0.10.0 de QPanel ya está disponible. Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.10.0 Esta versión versión nos preocupamos de realizar mejoras, refactorizaciones y agregamos una nueva funcionalidad. La nueva funcionalidad incluida es  que ahora es posible contar con una vista consolidada para [&#8230;]</p>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1227</guid>
<description><![CDATA[<p>Ya con la release numero 28 la nueva versión 0.10.0 de QPanel ya está disponible. Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.10.0 Esta versión versión nos preocupamos de realizar mejoras, refactorizaciones y agregamos una nueva funcionalidad. La nueva funcionalidad incluida es  que ahora es posible contar con una vista consolidada para [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/">QPanel 0.10.0 con vista consolidada</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p><img class="alignleft" src="https://raw.githubusercontent.com/roramirez/qpanel/0.10.0/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="403" height="227" />Ya con la release numero 28 la nueva versión 0.10.0 de QPanel ya está disponible.</p>
<content:encoded><![CDATA[<p><img class="alignleft" src="https://raw.githubusercontent.com/roramirez/qpanel/0.10.0/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="403" height="227" />Ya con la release numero 28 la nueva versión 0.10.0 de QPanel ya está disponible.</p>
<p>Para instalar esta nueva versión, debes visitar la siguiente URL</p>
<ul>
<li><a href="https://github.com/roramirez/qpanel/tree/0.10.0">https://github.com/roramirez/qpanel/tree/0.10.0</a></li>
@@ -301,29 +301,29 @@ Swap:         3071          0       3071</pre>
<p>El proyecto siempre está abierto a nuevas sugerencias las cuales puedes agregar mediante un <a href="https://github.com/roramirez/qpanel/issues/new?title=[Feature]">issue</a>.</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/">QPanel 0.10.0 con vista consolidada</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>Nerdearla 2016, WebRTC Glue</title>
<link>https://rodrigoramirez.com/nerdearla-2016/</link>
<comments>https://rodrigoramirez.com/nerdearla-2016/#respond</comments>
<pubDate>Wed, 15 Jun 2016 17:55:41 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<category><![CDATA[baires]]></category>
<category><![CDATA[charla]]></category>
<category><![CDATA[Computación]]></category>
<category><![CDATA[informatica]]></category>
<category><![CDATA[tech]]></category>
<category><![CDATA[ti]]></category>
<category><![CDATA[webrtc]]></category>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-0-10-0-vista-consolidada/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>Nerdearla 2016, WebRTC Glue</title>
<link>https://rodrigoramirez.com/nerdearla-2016/</link>
<comments>https://rodrigoramirez.com/nerdearla-2016/#respond</comments>
<pubDate>Wed, 15 Jun 2016 17:55:41 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<category><![CDATA[baires]]></category>
<category><![CDATA[charla]]></category>
<category><![CDATA[Computación]]></category>
<category><![CDATA[informatica]]></category>
<category><![CDATA[tech]]></category>
<category><![CDATA[ti]]></category>
<category><![CDATA[webrtc]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1218</guid>
<description><![CDATA[<p>Días atrás estuve participando en el evento llamado Nerdearla en Buenos Aires.  El ambiente era genial si eres de esas personas que desde niño sintio curiosidad por ver como funcionan las cosas, donde desarmabas para volver armar lo juguetes. Habían muchas cosas interesantes tanto en las presentaciones, co-working y workshop que se hubieron. Si te [&#8230;]</p>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1218</guid>
<description><![CDATA[<p>Días atrás estuve participando en el evento llamado Nerdearla en Buenos Aires.  El ambiente era genial si eres de esas personas que desde niño sintio curiosidad por ver como funcionan las cosas, donde desarmabas para volver armar lo juguetes. Habían muchas cosas interesantes tanto en las presentaciones, co-working y workshop que se hubieron. Si te [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/nerdearla-2016/">Nerdearla 2016, WebRTC Glue</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p>Días atrás estuve participando en el evento llamado <a href="https://nerdear.la/">Nerdearla</a> en Buenos Aires.  El ambiente era genial si eres de esas personas que desde niño sintio curiosidad por ver como funcionan las cosas, donde desarmabas para volver armar lo juguetes.</p>
<content:encoded><![CDATA[<p>Días atrás estuve participando en el evento llamado <a href="https://nerdear.la/">Nerdearla</a> en Buenos Aires.  El ambiente era genial si eres de esas personas que desde niño sintio curiosidad por ver como funcionan las cosas, donde desarmabas para volver armar lo juguetes.</p>
<p>Habían muchas cosas interesantes tanto en las presentaciones, co-working y workshop que se hubieron. Si te lo perdiste te recomiendo que estés pendiente para el proximo año.</p>
<p>&nbsp;</p>
<p>Te podias encontrar con una nuestra como esta<a href="https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS.jpg"><img class="aligncenter size-medium wp-image-1221" src="https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS-300x169.jpg" alt="Kaypro II" width="300" height="169" srcset="https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS-300x169.jpg 300w, https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS-768x432.jpg 768w, https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS-1024x576.jpg 1024w, https://rodrigoramirez.com/wp-content/uploads/CkhnO83XAAAfaxS.jpg 1200w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>
@@ -338,30 +338,30 @@ Swap:         3071          0       3071</pre>
&nbsp;</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/nerdearla-2016/">Nerdearla 2016, WebRTC Glue</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/nerdearla-2016/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>QPanel 0.9.0</title>
<link>https://rodrigoramirez.com/qpanel-0-9-0/</link>
<comments>https://rodrigoramirez.com/qpanel-0-9-0/#respond</comments>
<pubDate>Mon, 09 May 2016 18:40:23 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Software]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[callcenter]]></category>
<category><![CDATA[colas]]></category>
<category><![CDATA[monitor]]></category>
<category><![CDATA[monitoreo]]></category>
<category><![CDATA[panel]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queues]]></category>
<wfw:commentRss>https://rodrigoramirez.com/nerdearla-2016/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>QPanel 0.9.0</title>
<link>https://rodrigoramirez.com/qpanel-0-9-0/</link>
<comments>https://rodrigoramirez.com/qpanel-0-9-0/#respond</comments>
<pubDate>Mon, 09 May 2016 18:40:23 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Software]]></category>
<category><![CDATA[asterisk]]></category>
<category><![CDATA[callcenter]]></category>
<category><![CDATA[colas]]></category>
<category><![CDATA[monitor]]></category>
<category><![CDATA[monitoreo]]></category>
<category><![CDATA[panel]]></category>
<category><![CDATA[qpanel]]></category>
<category><![CDATA[queues]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1206</guid>
<description><![CDATA[<p>El Panel monitor callcenter para colas de Asterisk ya cuenta con una nueva versión, la 0.9.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.9.0 Esta versión versión nos preocupamos de realizar mejoras y refactorizaciones en el codigo para dar un mejor rendimiento, como también de la compatibilidad con la versión 11 de [&#8230;]</p>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1206</guid>
<description><![CDATA[<p>El Panel monitor callcenter para colas de Asterisk ya cuenta con una nueva versión, la 0.9.0 Para instalar esta nueva versión, debes visitar la siguiente URL https://github.com/roramirez/qpanel/tree/0.9.0 Esta versión versión nos preocupamos de realizar mejoras y refactorizaciones en el codigo para dar un mejor rendimiento, como también de la compatibilidad con la versión 11 de [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-9-0/">QPanel 0.9.0</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p><img class="alignleft" src="https://raw.githubusercontent.com/roramirez/qpanel/0.9.0/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="403" height="227" />El Panel monitor callcenter para colas de Asterisk ya cuenta con una nueva versión, la 0.9.0</p>
<content:encoded><![CDATA[<p><img class="alignleft" src="https://raw.githubusercontent.com/roramirez/qpanel/0.9.0/samples/animation.gif" alt="Panel monitor callcenter | Qpanel Monitor Colas" width="403" height="227" />El Panel monitor callcenter para colas de Asterisk ya cuenta con una nueva versión, la 0.9.0</p>
<p>Para instalar esta nueva versión, debes visitar la siguiente URL</p>
<ul>
<li><a href="https://github.com/roramirez/qpanel/tree/0.9.0">https://github.com/roramirez/qpanel/tree/0.9.0</a></li>
@@ -376,35 +376,35 @@ Swap:         3071          0       3071</pre>
<p>El proyecto siempre está abierto a nuevas sugerencias las cuales puedes agregar mediante un <a href="https://github.com/roramirez/qpanel/issues/new?title=[Feature]">issue</a>.</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/qpanel-0-9-0/">QPanel 0.9.0</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-0-9-0/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>Mandar un email desde la shell</title>
<link>https://rodrigoramirez.com/mandar-un-email-desde-la-shell/</link>
<comments>https://rodrigoramirez.com/mandar-un-email-desde-la-shell/#comments</comments>
<pubDate>Wed, 13 Apr 2016 13:05:13 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<category><![CDATA[mini-tips]]></category>
<category><![CDATA[bash]]></category>
<category><![CDATA[cli]]></category>
<category><![CDATA[Email]]></category>
<category><![CDATA[mail]]></category>
<category><![CDATA[sh]]></category>
<category><![CDATA[shell]]></category>
<wfw:commentRss>https://rodrigoramirez.com/qpanel-0-9-0/feed/</wfw:commentRss>
<slash:comments>0</slash:comments>
</item>
<item>
<title>Mandar un email desde la shell</title>
<link>https://rodrigoramirez.com/mandar-un-email-desde-la-shell/</link>
<comments>https://rodrigoramirez.com/mandar-un-email-desde-la-shell/#comments</comments>
<pubDate>Wed, 13 Apr 2016 13:05:13 +0000</pubDate>
<dc:creator><![CDATA[decipher]]></dc:creator>
<category><![CDATA[Linux]]></category>
<category><![CDATA[mini-tips]]></category>
<category><![CDATA[bash]]></category>
<category><![CDATA[cli]]></category>
<category><![CDATA[Email]]></category>
<category><![CDATA[mail]]></category>
<category><![CDATA[sh]]></category>
<category><![CDATA[shell]]></category>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1172</guid>
<description><![CDATA[<p>Dejo esto por acá ya que es algo que siempre me olvido como es. El tema es enviar un email mediante el comando mail en un servidor con Linux. Si usas mail a secas te va pidiendo los datos para crear el correo, principalmente el body del correo. Para automatizar esto a través de un [&#8230;]</p>
<guid isPermaLink="false">https://rodrigoramirez.com/?p=1172</guid>
<description><![CDATA[<p>Dejo esto por acá ya que es algo que siempre me olvido como es. El tema es enviar un email mediante el comando mail en un servidor con Linux. Si usas mail a secas te va pidiendo los datos para crear el correo, principalmente el body del correo. Para automatizar esto a través de un [&#8230;]</p>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/mandar-un-email-desde-la-shell/">Mandar un email desde la shell</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></description>
<content:encoded><![CDATA[<p>Dejo esto por acá ya que es algo que siempre me olvido como es. El tema es enviar un email mediante el comando <em>mail</em> en un servidor con Linux.</p>
<content:encoded><![CDATA[<p>Dejo esto por acá ya que es algo que siempre me olvido como es. El tema es enviar un email mediante el comando <em>mail</em> en un servidor con Linux.</p>
<p>Si usas mail a secas te va pidiendo los datos para crear el correo, principalmente el body del correo. Para automatizar esto a través de un <em>echo</em> le pasas por pipe a <em>mail</em></p>
<pre>echo "Cuerpo del mensaje" | mail -s Asunto a@rodrigoramirez.com</pre>
<p>La entrada <a rel="nofollow" href="https://rodrigoramirez.com/mandar-un-email-desde-la-shell/">Mandar un email desde la shell</a> aparece primero en <a rel="nofollow" href="https://rodrigoramirez.com">Rodrigo Ramírez Norambuena</a>.</p>
]]></content:encoded>
<wfw:commentRss>https://rodrigoramirez.com/mandar-un-email-desde-la-shell/feed/</wfw:commentRss>
<slash:comments>4</slash:comments>
</item>
</channel>
<wfw:commentRss>https://rodrigoramirez.com/mandar-un-email-desde-la-shell/feed/</wfw:commentRss>
<slash:comments>4</slash:comments>
</item>
</channel>
</rss>

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: [],

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -0,0 +1,34 @@
/* Magic Mirror Test config sample module alert
*
* By rejas
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [
{
module: "alert",
config: {
display_time: 1000000,
welcome_message: true
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -1,5 +1,6 @@
/* Magic Mirror Test config custom calendar
*
* By Rejas
* MIT Licensed.
*/
let config = {

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -5,8 +5,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -0,0 +1,40 @@
/* Magic Mirror Test config custom calendar
*
* By Rejas
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 12,
units: "metric",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [
{
module: "calendar",
position: "bottom_bar",
config: {
calendars: [
{
maximumEntries: 6,
maximumNumberOfDays: 3650,
url: "http://localhost:8080/tests/configs/data/calendar_test_recurring.ics"
}
]
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}

View File

@@ -3,8 +3,7 @@
* By Sergey Morozov
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -3,8 +3,7 @@
* By Sergey Morozov
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -3,8 +3,7 @@
* By Sergey Morozov
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -3,8 +3,7 @@
* By Johan Hammar
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -1,13 +1,10 @@
/* Magic Mirror Test config for default clock module
* Language es for showWeek feature
*
* By Rodrigo Ramírez Norambuena
* https://rodrigoramirez.com
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -1,10 +1,8 @@
/* Magic Mirror Test config compliments with date type
*
* By Rejas
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -1,8 +1,9 @@
/* Magic Mirror Test config for display setters module using the helloworld module
*
* By Rejas
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
@@ -37,6 +38,7 @@ var config = {
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -27,7 +27,8 @@ let config = {
url: "http://localhost:8080/tests/configs/data/feed_test_rodrigoramirez.xml"
}
],
prohibitedWords: ["QPanel"]
prohibitedWords: ["QPanel"],
showDescription: true
}
}
]

View File

@@ -1,12 +1,9 @@
/* Magic Mirror Test config for position setters module
*
* For this case is using helloworld module
/* Magic Mirror Test config for position setters module using the helloworld module
*
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
@@ -23,9 +20,9 @@ var config = {
modules:
// Using exotic content. This is why don't accept go to JSON configuration file
(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"];
var modules = Array();
for (var idx in positions) {
let 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"];
let modules = Array();
for (let idx in positions) {
modules.push({
module: "helloworld",
position: positions[idx],
@@ -37,6 +34,7 @@ var config = {
return modules;
})()
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;

View File

@@ -1,10 +1,8 @@
/* Magic Mirror Test config current weather compliments
*
* By rejas https://github.com/rejas
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -1,10 +1,8 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -1,10 +1,8 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -1,10 +1,8 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -1,10 +1,8 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -1,10 +1,8 @@
/* Magic Mirror Test config default weather
*
* By fewieden https://github.com/fewieden
*
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],

View File

@@ -0,0 +1,39 @@
/* Magic Mirror Test config default weather
*
* By rejas
* MIT Licensed.
*/
let config = {
port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
language: "en",
timeFormat: 24,
units: "imperial",
electronOptions: {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
},
modules: [
{
module: "weather",
position: "bottom_bar",
config: {
type: "forecast",
location: "Munich",
apiKey: "fake key",
weatherEndpoint: "/forecast/daily",
initialLoadDelay: 3000,
decimalSymbol: "_"
}
}
]
};
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}

View File

@@ -3,8 +3,7 @@
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed.
*/
var config = {
let config = {
port: 8080,
ipWhitelist: ["x.x.x.x"],

Some files were not shown because too many files have changed in this diff Show More