Merge branch 'develop' into calendar-module

This commit is contained in:
Kristof Rado 2020-05-29 23:50:24 +02:00
commit b179c8e2b7
199 changed files with 4724 additions and 6927 deletions

15
.editorconfig Normal file
View File

@ -0,0 +1,15 @@
# editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 250
trim_trailing_whitespace = true
[*.{js,json}]
indent_size = 4
indent_style = tab

View File

@ -1,5 +1 @@
vendor/* modules/default/calendar/vendor/*
!/vendor/vendor.js
!/modules/default/**
!/modules/node_helper
!/modules/node_helper/**

View File

@ -1,20 +1,18 @@
{ {
"rules": { "extends": ["eslint:recommended", "plugin:prettier/recommended"],
"indent": ["error", "tab"], "plugins": ["prettier"],
"quotes": ["error", "double"],
"semi": ["error"],
"max-len": ["error", 250],
"curly": "error",
"camelcase": ["error", {"properties": "never"}],
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }],
"no-multi-spaces": "error",
"no-trailing-spaces": ["error", {"ignoreComments": false }],
"no-irregular-whitespace": ["error"]
},
"env": { "env": {
"browser": true, "browser": true,
"node": true, "es6": true,
"es6": true "mocha": true,
"node": true
},
"globals": {
"config": true,
"Log": true,
"MM": true,
"Module": true,
"moment": true
}, },
"parserOptions": { "parserOptions": {
"sourceType": "module", "sourceType": "module",
@ -22,5 +20,11 @@
"ecmaFeatures": { "ecmaFeatures": {
"globalReturn": true "globalReturn": true
} }
} },
"rules": {
"prettier/prettier": "error",
"eqeqeq": "error",
"no-prototype-builtins": "off",
"no-unused-vars": "off"
}
} }

View File

@ -1,25 +1,24 @@
Contribution Policy for MagicMirror² # Contribution Policy for MagicMirror²
====================================
Thanks for contributing to MagicMirror²! Thanks for contributing to MagicMirror²!
We hold our code to standard, and these standards are documented below. We hold our code to standard, and these standards are documented below.
If you wish to run both linters, use `grunt` without any arguments. If you wish to run our linters, use `npm run lint` without any arguments.
### JavaScript: Run ESLint ### JavaScript: Run ESLint
We use [ESLint](http://eslint.org) on our JavaScript files. We use [ESLint](https://eslint.org) on our JavaScript files.
Our ESLint configuration is in our .eslintrc.json and .eslintignore files. Our ESLint configuration is in our .eslintrc.json and .eslintignore files.
To run ESLint, use `grunt eslint`. To run ESLint, use `npm run lint:js`.
### CSS: Run StyleLint ### CSS: Run StyleLint
We use [StyleLint](http://stylelint.io) to lint our CSS. Our configuration is in our .stylelintrc file. We use [StyleLint](https://stylelint.io) to lint our CSS. Our configuration is in our .stylelintrc file.
To run StyleLint, use `grunt stylelint`. To run StyleLint, use `npm run lint:style`.
### Submitting Issues ### Submitting Issues
@ -30,7 +29,7 @@ Problems installing or configuring your MagicMirror? Check out: [https://forum.m
When submitting a new issue, please supply the following information: When submitting a new issue, please supply the following information:
**Platform**: Place your platform here... give us your web browser/Electron version *and* your hardware (Raspberry Pi 2/3, Windows, Mac, Linux, System V UNIX). **Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3, Windows, Mac, Linux, System V UNIX).
**Node Version**: Make sure it's version 0.12.13 or later. **Node Version**: Make sure it's version 0.12.13 or later.

View File

@ -1,24 +1,29 @@
## I'm not sure if this is a bug ## 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) If you're not sure if it's a real bug or if it's just you, please open a topic on the forum: [https://forum.magicmirror.builders/category/15/bug-hunt](https://forum.magicmirror.builders/category/15/bug-hunt)
## I'm having troubles installing or configuring MagicMirror ## I'm having troubles installing or configuring MagicMirror
Problems installing or configuring your MagicMirror? Check out: [https://forum.magicmirror.builders/category/10/troubleshooting](https://forum.magicmirror.builders/category/10/troubleshooting) Problems installing or configuring your MagicMirror? Check out: [https://forum.magicmirror.builders/category/10/troubleshooting](https://forum.magicmirror.builders/category/10/troubleshooting)
## I found a bug in the MagicMirror installer ## I found a bug in the MagicMirror installer
If you are facing an issue or found a bug while trying to install MagicMirror via the installer please report it in the respective GitHub repository: If you are facing an issue or found a bug while trying to install MagicMirror via the installer please report it in the respective GitHub repository:
[https://github.com/sdetweil/MagicMirror_scripts](https://github.com/sdetweil/MagicMirror_scripts) [https://github.com/sdetweil/MagicMirror_scripts](https://github.com/sdetweil/MagicMirror_scripts)
## I found a bug in the MagicMirror Docker image ## I found a bug in the MagicMirror Docker image
If you are facing an issue or found a bug while running MagicMirror inside a Docker container please create an issue in the GitHub repository of the MagicMirror Docker image: If you are facing an issue or found a bug while running MagicMirror inside a Docker container please create an issue in the GitHub repository of the MagicMirror Docker image:
[https://github.com/bastilimbach/docker-MagicMirror](https://github.com/bastilimbach/docker-MagicMirror) [https://github.com/bastilimbach/docker-MagicMirror](https://github.com/bastilimbach/docker-MagicMirror)
--- ---
## I found a bug in MagicMirror ## I found a bug in MagicMirror
Please make sure to only submit reproducible issues. You can safely remove everything above the dividing line. Please make sure to only submit reproducible issues. You can safely remove everything above the dividing line.
When submitting a new issue, please supply the following information: When submitting a new issue, please supply the following information:
**Platform**: Place your platform here... give us your web browser/Electron version *and* your hardware (Raspberry Pi 2/3, Windows, Mac, Linux, System V UNIX). **Platform**: Place your platform here... give us your web browser/Electron version _and_ your hardware (Raspberry Pi 2/3, Windows, Mac, Linux, System V UNIX).
**Node Version**: Make sure it's version 8 or later. **Node Version**: Make sure it's version 8 or later.

View File

@ -7,8 +7,7 @@ pull request to send us your changes. This makes everyone's lives
easier (including yours) and helps us out on the development team. easier (including yours) and helps us out on the development team.
Thanks! Thanks!
- Does the pull request solve a **related** issue?
* Does the pull request solve a **related** issue? - If so, can you reference the issue?
* If so, can you reference the issue? - What does the pull request accomplish? Use a list if needed.
* What does the pull request accomplish? Use a list if needed. - If it includes major visual changes please add screenshots.
* If it includes major visual changes please add screenshots.

1
.gitignore vendored
View File

@ -8,7 +8,6 @@ pids
*.seed *.seed
lib-cov lib-cov
coverage coverage
.grunt
.lock-wscript .lock-wscript
build/Release build/Release
/node_modules/**/* /node_modules/**/*

5
.prettierignore Normal file
View File

@ -0,0 +1,5 @@
package-lock.json
/config/**/*
/modules/default/calendar/vendor/ical.js/**/*
/vendor/**/*
!/vendor/vendor.js

3
.prettierrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"trailingComma": "none"
}

View File

@ -1,5 +1,7 @@
{ {
"extends": "stylelint-config-standard", "extends": ["stylelint-prettier/recommended"],
"font-family-name-quotes": "double-where-recommended", "plugins": ["stylelint-prettier"],
"block-no-empty": false "rules": {
"prettier/prettier": true
}
} }

View File

@ -8,13 +8,14 @@ before_install:
- npm i -g npm - npm i -g npm
before_script: before_script:
- yarn danger ci - yarn danger ci
- npm install grunt-cli -g
- "export DISPLAY=:99.0" - "export DISPLAY=:99.0"
- "export ELECTRON_DISABLE_SANDBOX=1" - "export ELECTRON_DISABLE_SANDBOX=1"
- "sh -e /etc/init.d/xvfb start" - "sh -e /etc/init.d/xvfb start"
- sleep 5 - sleep 5
script: script:
- npm run test:lint - npm run test:prettier
- npm run test:js
- npm run test:css
- npm run test:e2e - npm run test:e2e
- npm run test:unit - npm run test:unit
after_script: after_script:

View File

@ -1,10 +1,38 @@
# MagicMirror² Change Log # MagicMirror² Change Log
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). 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² ❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror²
## [2.12.0] - Unreleased (Develop Branch)
_This release is scheduled to be released on 2020-07-01._
### Added
- Compliments Module - Add Advice API (https://api.adviceslip.com/) Option
- Added prettier for an even cleaner codebase
### Updated
- Cleaned up alert module code
- Cleaned up check_config code
- Replaced grunt-based linters with their non-grunt equivalents
- Switch to most of the eslint:recommended rules and fix warnings
- Replaced insecure links with https ones
- Cleaned up all "no-undef" warnings from eslint
### Deleted
- Removed truetype (ttf) fonts
### Fixed
- The broken modules due to Socket.io change from last release [#1973](https://github.com/MichMich/MagicMirror/issues/1973)
- Add backward compatibility for old module code in socketclient.js [#1973](https://github.com/MichMich/MagicMirror/issues/1973)
## [2.11.0] - 2020-04-01 ## [2.11.0] - 2020-04-01
🚨 READ THIS BEFORE UPDATING 🚨 🚨 READ THIS BEFORE UPDATING 🚨
@ -14,11 +42,13 @@ In the past years the project has grown a lot. This came with a huge downside: p
For more information regarding this major change, please check issue [#1860](https://github.com/MichMich/MagicMirror/issues/1860). For more information regarding this major change, please check issue [#1860](https://github.com/MichMich/MagicMirror/issues/1860).
### Deleted ### Deleted
- Remove installers. - Remove installers.
- Remove externalized scripts. - Remove externalized scripts.
- Remove jshint dependency, instead eslint checks your config file now - Remove jshint dependency, instead eslint checks your config file now
### Added ### Added
- Brazilian translation for "FEELS". - Brazilian translation for "FEELS".
- Ukrainian translation. - Ukrainian translation.
- Finnish translation for "PRECIP", "UPDATE_INFO_MULTIPLE" and "UPDATE_INFO_SINGLE". - Finnish translation for "PRECIP", "UPDATE_INFO_MULTIPLE" and "UPDATE_INFO_SINGLE".
@ -30,8 +60,10 @@ For more information regarding this major change, please check issue [#1860](htt
- Added the ability to configure a list of modules that shouldn't be update checked. - Added the ability to configure a list of modules that shouldn't be update checked.
- Run linters on git commits - Run linters on git commits
- Added date functionality to compliments: display birthday wishes or celebrate an anniversary - Added date functionality to compliments: display birthday wishes or celebrate an anniversary
- Add HTTPS support for clientonly-mode.
### Fixed ### Fixed
- Force declaration of public ip address in config file (ISSUE #1852) - Force declaration of public ip address in config file (ISSUE #1852)
- Fixes `run-start.sh`: If running in docker-container, don't check the environment, just start electron (ISSUE #1859) - Fixes `run-start.sh`: If running in docker-container, don't check the environment, just start electron (ISSUE #1859)
- Fix calendar time offset for recurring events crossing Daylight Savings Time (ISSUE #1798) - Fix calendar time offset for recurring events crossing Daylight Savings Time (ISSUE #1798)
@ -42,6 +74,7 @@ For more information regarding this major change, please check issue [#1860](htt
- Fix update checking skipping 3rd party modules the first time - Fix update checking skipping 3rd party modules the first time
### Changed ### Changed
- Remove documentation from core repository and link to new dedicated docs site: [docs.magicmirror.builders](https://docs.magicmirror.builders). - Remove documentation from core repository and link to new dedicated docs site: [docs.magicmirror.builders](https://docs.magicmirror.builders).
- Updated config.js.sample: Corrected some grammar on `config.js.sample` comment section. - Updated config.js.sample: Corrected some grammar on `config.js.sample` comment section.
- Removed `run-start.sh` script and update start commands: - Removed `run-start.sh` script and update start commands:
@ -55,6 +88,7 @@ For more information regarding this major change, please check issue [#1860](htt
## [2.10.1] - 2020-01-10 ## [2.10.1] - 2020-01-10
### Changed ### Changed
- Updated README.md: Added links to the official documentation website and remove links to broken installer. - Updated README.md: Added links to the official documentation website and remove links to broken installer.
## [2.10.0] - 2020-01-01 ## [2.10.0] - 2020-01-01
@ -64,12 +98,14 @@ Special thanks to @sdetweil for all his great contributions!
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`.
### Added ### Added
- Timestamps in log output. - Timestamps in log output.
- Padding in dateheader mode of the calendar module. - Padding in dateheader mode of the calendar module.
- New upgrade script to help users consume regular updates installers/upgrade-script.sh. - New upgrade script to help users consume regular updates installers/upgrade-script.sh.
- New script to help setup pm2, without install installers/fixuppm2.sh. - New script to help setup pm2, without install installers/fixuppm2.sh.
### Updated ### Updated
- Updated lower bound of `lodash` and `helmet` dependencies for security patches. - Updated lower bound of `lodash` and `helmet` dependencies for security patches.
- Updated compliments.js to handle newline in text, as textfields to not interpolate contents. - Updated compliments.js to handle newline in text, as textfields to not interpolate contents.
- Updated raspberry.sh installer script to handle new platform issues, split node/npm, pm2, and screen saver changes. - Updated raspberry.sh installer script to handle new platform issues, split node/npm, pm2, and screen saver changes.
@ -78,6 +114,7 @@ Special thanks to @sdetweil for all his great contributions!
- Only check for xwindows running if not on macOS. - Only check for xwindows running if not on macOS.
### Fixed ### Fixed
- Fixed issue in weatherforecast module where predicted amount of rain was not using the decimal symbol specified in config.js. - Fixed issue in weatherforecast module where predicted amount of rain was not using the decimal symbol specified in config.js.
- Module header now updates correctly, if a module need to dynamically show/hide its header based on a condition. - Module header now updates correctly, if a module need to dynamically show/hide its header based on a condition.
- Fix handling of config.js for serverOnly mode commented out. - Fix handling of config.js for serverOnly mode commented out.
@ -90,18 +127,21 @@ Special thanks to @sdetweil for all his great contributions!
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md). **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md).
### Added ### Added
- Spanish translation for "PRECIP". - Spanish translation for "PRECIP".
- Adding a Malay (Malaysian) translation for MagicMirror². - Adding a Malay (Malaysian) translation for MagicMirror².
- Add test check URLs of vendors 200 and 404 HTTP CODE. - Add test check URLs of vendors 200 and 404 HTTP CODE.
- Add tests for new weather module and helper to stub ajax requests. - Add tests for new weather module and helper to stub ajax requests.
### Updated ### Updated
- Updatenotification module: Display update notification for a limited (configurable) time. - Updatenotification module: Display update notification for a limited (configurable) time.
- Enabled e2e/vendor_spec.js tests. - Enabled e2e/vendor_spec.js tests.
- The css/custom.css will be rename after the next release. We've add into `run-start.sh` a instruction by GIT to ignore with `--skip-worktree` and `rm --cached`. [#1540](https://github.com/MichMich/MagicMirror/issues/1540) - The css/custom.css will be renamed after the next release. We've added into `run-start.sh` an instruction by GIT to ignore with `--skip-worktree` and `rm --cached`. [#1540](https://github.com/MichMich/MagicMirror/issues/1540)
- Disable sending of notification CLOCK_SECOND when displaySeconds is false. - Disable sending of notification CLOCK_SECOND when displaySeconds is false.
### Fixed ### Fixed
- Updatenotification module: Properly handle race conditions, prevent crash. - Updatenotification module: Properly handle race conditions, prevent crash.
- Send `NEWS_FEED` notification also for the first news messages which are shown. - Send `NEWS_FEED` notification also for the first news messages which are shown.
- Fixed issue where weather module would not refresh data after a network or API outage. [#1722](https://github.com/MichMich/MagicMirror/issues/1722) - Fixed issue where weather module would not refresh data after a network or API outage. [#1722](https://github.com/MichMich/MagicMirror/issues/1722)
@ -113,6 +153,7 @@ Special thanks to @sdetweil for all his great contributions!
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md). **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md).
### Added ### Added
- Option to show event location in calendar - Option to show event location in calendar
- Finnish translation for "Feels" and "Weeks" - Finnish translation for "Feels" and "Weeks"
- Russian translation for “Feels” - Russian translation for “Feels”
@ -132,13 +173,15 @@ Special thanks to @sdetweil for all his great contributions!
- Added to `newsfeed.js`: in order to design the news article better with css, three more class-names were introduced: newsfeed-desc, newsfeed-desc, newsfeed-desc - Added to `newsfeed.js`: in order to design the news article better with css, three more class-names were introduced: newsfeed-desc, newsfeed-desc, newsfeed-desc
### Updated ### Updated
- English translation for "Feels" to "Feels like" - English translation for "Feels" to "Feels like"
- Fixed the example calender url in `config.js.sample` - Fixed the example calendar url in `config.js.sample`
- Update `ical.js` to solve various calendar issues. - Update `ical.js` to solve various calendar issues.
- Update weather city list url [#1676](https://github.com/MichMich/MagicMirror/issues/1676) - Update weather city list url [#1676](https://github.com/MichMich/MagicMirror/issues/1676)
- Only update clock once per minute when seconds aren't shown - Only update clock once per minute when seconds aren't shown
### Fixed ### Fixed
- Fixed uncaught exception, race condition on module update - Fixed uncaught exception, race condition on module update
- Fixed issue [#1696](https://github.com/MichMich/MagicMirror/issues/1696), some ical files start date to not parse to date type - Fixed issue [#1696](https://github.com/MichMich/MagicMirror/issues/1696), some ical files start date to not parse to date type
- Allowance HTML5 autoplay-policy (policy is changed from Chrome 66 updates) - Allowance HTML5 autoplay-policy (policy is changed from Chrome 66 updates)
@ -150,6 +193,7 @@ Special thanks to @sdetweil for all his great contributions!
- Updated the fetchedLocationName variable in currentweather.js so that city shows up in the header - Updated the fetchedLocationName variable in currentweather.js so that city shows up in the header
### Updated installer ### Updated installer
- give non-pi2+ users (pi0, odroid, jetson nano, mac, windows, ...) option to continue install - give non-pi2+ users (pi0, odroid, jetson nano, mac, windows, ...) option to continue install
- use current username vs hardcoded 'pi' to support non-pi install - use current username vs hardcoded 'pi' to support non-pi install
- check for npm installed. node install doesn't do npm anymore - check for npm installed. node install doesn't do npm anymore
@ -166,6 +210,7 @@ Fixed `package.json` version number.
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md). **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md).
### Added ### Added
- Italian translation for "Feels" - Italian translation for "Feels"
- Basic Klingon (tlhIngan Hol) translations - Basic Klingon (tlhIngan Hol) translations
- Disabled the screensaver on raspbian with installation script - Disabled the screensaver on raspbian with installation script
@ -179,12 +224,14 @@ Fixed `package.json` version number.
- Add `name` config option for calendars to be sent along with event broadcasts - Add `name` config option for calendars to be sent along with event broadcasts
### Updated ### Updated
- Bumped the Electron dependency to v3.0.13 to support the most recent Raspbian. [#1500](https://github.com/MichMich/MagicMirror/issues/1500) - Bumped the Electron dependency to v3.0.13 to support the most recent Raspbian. [#1500](https://github.com/MichMich/MagicMirror/issues/1500)
- Updated modernizr code in alert module, fixed a small typo there too - Updated modernizr code in alert module, fixed a small typo there too
- More verbose error message on console if the config is malformed - More verbose error message on console if the config is malformed
- Updated installer script to install Node.js version 10.x - Updated installer script to install Node.js version 10.x
### Fixed ### Fixed
- Fixed temperature displays in currentweather and weatherforecast modules [#1503](https://github.com/MichMich/MagicMirror/issues/1503), [#1511](https://github.com/MichMich/MagicMirror/issues/1511). - Fixed temperature displays in currentweather and weatherforecast modules [#1503](https://github.com/MichMich/MagicMirror/issues/1503), [#1511](https://github.com/MichMich/MagicMirror/issues/1511).
- Fixed unhandled error on bad git data in updatenotification module [#1285](https://github.com/MichMich/MagicMirror/issues/1285). - Fixed unhandled error on bad git data in updatenotification module [#1285](https://github.com/MichMich/MagicMirror/issues/1285).
- Weather forecast now works with openweathermap in new weather module. Daily data are displayed, see issue [#1504](https://github.com/MichMich/MagicMirror/issues/1504). - Weather forecast now works with openweathermap in new weather module. Daily data are displayed, see issue [#1504](https://github.com/MichMich/MagicMirror/issues/1504).
@ -193,7 +240,7 @@ Fixed `package.json` version number.
- Installation script problems with raspbian - Installation script problems with raspbian
- Calendar: only show repeating count if the event is actually repeating [#1534](https://github.com/MichMich/MagicMirror/pull/1534) - Calendar: only show repeating count if the event is actually repeating [#1534](https://github.com/MichMich/MagicMirror/pull/1534)
- Calendar: Fix exdate handling when multiple values are specified (comma separated) - Calendar: Fix exdate handling when multiple values are specified (comma separated)
- Calendar: Fix relative date handling for fulldate events, calculate difference always from start of day [#1572](https://github.com/MichMich/MagicMirror/issues/1572) - Calendar: Fix relative date handling for fulldate events, calculate difference always from start of day [#1572](https://github.com/MichMich/MagicMirror/issues/1572)
- Fix null dereference in moduleNeedsUpdate when the module isn't visible - Fix null dereference in moduleNeedsUpdate when the module isn't visible
- Calendar: Fixed event end times by setting default calendarEndTime to "LT" (Local time format). [#1479] - Calendar: Fixed event end times by setting default calendarEndTime to "LT" (Local time format). [#1479]
- Calendar: Fixed missing calendar fetchers after server process restarts [#1589](https://github.com/MichMich/MagicMirror/issues/1589) - Calendar: Fixed missing calendar fetchers after server process restarts [#1589](https://github.com/MichMich/MagicMirror/issues/1589)
@ -202,6 +249,7 @@ Fixed `package.json` version number.
- Fix documentation of `useKMPHwind` option in currentweather - Fix documentation of `useKMPHwind` option in currentweather
### New weather module ### New weather module
- Fixed weather forecast table display [#1499](https://github.com/MichMich/MagicMirror/issues/1499). - Fixed weather forecast table display [#1499](https://github.com/MichMich/MagicMirror/issues/1499).
- Dimmed loading indicator for weather forecast. - Dimmed loading indicator for weather forecast.
- Implemented config option `decimalSymbol` [#1499](https://github.com/MichMich/MagicMirror/issues/1499). - Implemented config option `decimalSymbol` [#1499](https://github.com/MichMich/MagicMirror/issues/1499).
@ -219,11 +267,13 @@ Fixed `package.json` version number.
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues updating, make sure you are running the latest version of Node. **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues updating, make sure you are running the latest version of Node.
### ✨ Experimental ✨ ### ✨ Experimental ✨
- New default [module weather](modules/default/weather). This module will eventually replace the current `currentweather` and `weatherforecast` modules. The new module is still pretty experimental, but it's included so you can give it a try and help us improve this module. Please give us you feedback using [this forum post](https://forum.magicmirror.builders/topic/9335/default-weather-module-refactoring). - New default [module weather](modules/default/weather). This module will eventually replace the current `currentweather` and `weatherforecast` modules. The new module is still pretty experimental, but it's included so you can give it a try and help us improve this module. Please give us you feedback using [this forum post](https://forum.magicmirror.builders/topic/9335/default-weather-module-refactoring).
A huge, huge, huge thanks to user @fewieden for all his hard work on the new `weather` module! A huge, huge, huge thanks to user @fewieden for all his hard work on the new `weather` module!
### Added ### Added
- Possibility to add classes to the cell of symbol, title and time of the events of calendar. - Possibility to add classes to the cell of symbol, title and time of the events of calendar.
- Font-awesome 5, still has 4 for backwards compatibility. - Font-awesome 5, still has 4 for backwards compatibility.
- Missing `showEnd` in calendar documentation - Missing `showEnd` in calendar documentation
@ -238,22 +288,25 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Documentation for the existing `scale` option in the Weather Forecast module. - Documentation for the existing `scale` option in the Weather Forecast module.
### Fixed ### Fixed
- Allow to parse recurring calendar events where the start date is before 1900
- Allow parsing recurring calendar events where the start date is before 1900
- Fixed Polish translation for Single Update Info - Fixed Polish translation for Single Update Info
- Ignore entries with unparseable details in the calendar module - Ignore entries with unparseable details in the calendar module
- Bug showing FullDayEvents one day too long in calendar fixed - Bug showing FullDayEvents one day too long in calendar fixed
- Bug in newsfeed when `removeStartTags` is used on the description [#1478](https://github.com/MichMich/MagicMirror/issues/1478) - Bug in newsfeed when `removeStartTags` is used on the description [#1478](https://github.com/MichMich/MagicMirror/issues/1478)
### Updated ### Updated
- The default calendar setting `showEnd` is changed to `false`. - The default calendar setting `showEnd` is changed to `false`.
### Changed ### Changed
- The Weather Forecast module by default displays the ° symbol after every numeric value to be consistent with the Current Weather module.
- The Weather Forecast module by default displays the ° symbol after every numeric value to be consistent with the Current Weather module.
## [2.5.0] - 2018-10-01 ## [2.5.0] - 2018-10-01
### Added ### Added
- Romanian translation for "Feels" - Romanian translation for "Feels"
- Support multi-line compliments - Support multi-line compliments
- Simplified Chinese translation for "Feels" - Simplified Chinese translation for "Feels"
@ -268,6 +321,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Support for showing end of events through config parameters showEnd and dateEndFormat - Support for showing end of events through config parameters showEnd and dateEndFormat
### Fixed ### Fixed
- Fixed gzip encoded calendar loading issue #1400. - Fixed gzip encoded calendar loading issue #1400.
- Mixup between german and spanish translation for newsfeed. - Mixup between german and spanish translation for newsfeed.
- Fixed close dates to be absolute, if no configured in the config.js - module Calendar - Fixed close dates to be absolute, if no configured in the config.js - module Calendar
@ -313,11 +367,13 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Add update translations for Português Brasileiro - Add update translations for Português Brasileiro
### Changed ### Changed
- Upgrade to Electron 2.0.0. - Upgrade to Electron 2.0.0.
- Remove yarn-or-npm which breaks production builds. - Remove yarn-or-npm which breaks production builds.
- Invoke module suspend even if no dom content. [#1308](https://github.com/MichMich/MagicMirror/issues/1308) - Invoke module suspend even if no dom content. [#1308](https://github.com/MichMich/MagicMirror/issues/1308)
### Fixed ### Fixed
- Fixed issue where wind chill could not be displayed in Fahrenheit. [#1247](https://github.com/MichMich/MagicMirror/issues/1247) - Fixed issue where wind chill could not be displayed in Fahrenheit. [#1247](https://github.com/MichMich/MagicMirror/issues/1247)
- Fixed issues where a module crashes when it tries to dismiss a non existing alert. [#1240](https://github.com/MichMich/MagicMirror/issues/1240) - Fixed issues where a module crashes when it tries to dismiss a non existing alert. [#1240](https://github.com/MichMich/MagicMirror/issues/1240)
- In default module currentWeather/currentWeather.js line 296, 300, self.config.animationSpeed can not be found because the notificationReceived function does not have "self" variable. - In default module currentWeather/currentWeather.js line 296, 300, self.config.animationSpeed can not be found because the notificationReceived function does not have "self" variable.
@ -330,6 +386,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Fixed issue where heat index and wind chill were reporting incorrect values in Kelvin. [#1263](https://github.com/MichMich/MagicMirror/issues/1263) - Fixed issue where heat index and wind chill were reporting incorrect values in Kelvin. [#1263](https://github.com/MichMich/MagicMirror/issues/1263)
### Updated ### Updated
- Updated Italian translation - Updated Italian translation
- Updated German translation - Updated German translation
- Updated Dutch translation - Updated Dutch translation
@ -337,6 +394,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
## [2.3.1] - 2018-04-01 ## [2.3.1] - 2018-04-01
### Fixed ### Fixed
- Downgrade electron to 1.4.15 to solve the black screen issue.[#1243](https://github.com/MichMich/MagicMirror/issues/1243) - Downgrade electron to 1.4.15 to solve the black screen issue.[#1243](https://github.com/MichMich/MagicMirror/issues/1243)
## [2.3.0] - 2018-04-01 ## [2.3.0] - 2018-04-01
@ -347,7 +405,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Add system notification `MODULE_DOM_CREATED` for notifying each module when their Dom has been fully loaded. - Add system notification `MODULE_DOM_CREATED` for notifying each module when their Dom has been fully loaded.
- Add types for module. - Add types for module.
- Implement Danger.js to notify contributors when CHANGELOG.md is missing in PR. - Implement Danger.js to notify contributors when CHANGELOG.md is missing in PR.
- Allow to scroll in full page article view of default newsfeed module with gesture events from [MMM-Gestures](https://github.com/thobach/MMM-Gestures) - Allow scrolling in full page article view of default newsfeed module with gesture events from [MMM-Gestures](https://github.com/thobach/MMM-Gestures)
- Changed 'compliments.js' - update DOM if remote compliments are loaded instead of waiting one updateInterval to show custom compliments - Changed 'compliments.js' - update DOM if remote compliments are loaded instead of waiting one updateInterval to show custom compliments
- Automated unit tests utils, deprecated, translator, cloneObject(lockstrings) - Automated unit tests utils, deprecated, translator, cloneObject(lockstrings)
- Automated integration tests translations - Automated integration tests translations
@ -357,6 +415,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Add dc:date to parsing in newsfeed module, which allows parsing of more rss feeds. - Add dc:date to parsing in newsfeed module, which allows parsing of more rss feeds.
### Changed ### Changed
- Add link to GitHub repository which contains the respective Dockerfile. - Add link to GitHub repository which contains the respective Dockerfile.
- Optimized automated unit tests cloneObject, cmpVersions - Optimized automated unit tests cloneObject, cmpVersions
- Update notifications use now translation templates instead of normal strings. - Update notifications use now translation templates instead of normal strings.
@ -364,6 +423,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Changed Electron dependency to v1.7.13. - Changed Electron dependency to v1.7.13.
### Fixed ### Fixed
- News article in fullscreen (iframe) is now shown in front of modules. - News article in fullscreen (iframe) is now shown in front of modules.
- Forecast respects maxNumberOfDays regardless of endpoint. - Forecast respects maxNumberOfDays regardless of endpoint.
- Fix exception on translation of objects. - Fix exception on translation of objects.
@ -381,6 +441,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
## [2.2.1] - 2018-01-01 ## [2.2.1] - 2018-01-01
### Fixed ### Fixed
- Fixed linting errors. - Fixed linting errors.
## [2.2.0] - 2018-01-01 ## [2.2.0] - 2018-01-01
@ -388,10 +449,12 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install` **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`
### Changed ### Changed
- Calender week is now handled with a variable translation in order to move number language specific.
- Calendar week is now handled with a variable translation in order to move number language specific.
- Reverted the Electron dependency back to 1.4.15 since newer version don't seem to work on the Raspberry Pi very well. - Reverted the Electron dependency back to 1.4.15 since newer version don't seem to work on the Raspberry Pi very well.
### Added ### Added
- Add option to use [Nunjucks](https://mozilla.github.io/nunjucks/) templates in modules. (See `helloworld` module as an example.) - Add option to use [Nunjucks](https://mozilla.github.io/nunjucks/) templates in modules. (See `helloworld` module as an example.)
- Add Bulgarian translations for MagicMirror² and Alert module. - Add Bulgarian translations for MagicMirror² and Alert module.
- Add graceful shutdown of modules by calling `stop` function of each `node_helper` on SIGINT before exiting. - Add graceful shutdown of modules by calling `stop` function of each `node_helper` on SIGINT before exiting.
@ -405,6 +468,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Add option for decimal symbols other than the decimal point for temperature values in both default weather modules: WeatherForecast and CurrentWeather. - Add option for decimal symbols other than the decimal point for temperature values in both default weather modules: WeatherForecast and CurrentWeather.
### Fixed ### Fixed
- Fixed issue with calendar module showing more than `maximumEntries` allows - Fixed issue with calendar module showing more than `maximumEntries` allows
- WeatherForecast and CurrentWeather are now using HTTPS instead of HTTP - WeatherForecast and CurrentWeather are now using HTTPS instead of HTTP
- Correcting translation for Indonesian language - Correcting translation for Indonesian language
@ -415,9 +479,11 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install` **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`
### Changed ### Changed
- Remove Roboto fonts files inside `fonts` and these are installed by npm install command. - Remove Roboto fonts files inside `fonts` and these are installed by npm install command.
### Added ### Added
- Add `clientonly` script to start only the electron client for a remote server. - Add `clientonly` script to start only the electron client for a remote server.
- Add symbol and color properties of event when `CALENDAR_EVENTS` notification is broadcasted from `default/calendar` module. - Add symbol and color properties of event when `CALENDAR_EVENTS` notification is broadcasted from `default/calendar` module.
- Add `.vscode/` folder to `.gitignore` to keep custom Visual Studio Code config out of git. - Add `.vscode/` folder to `.gitignore` to keep custom Visual Studio Code config out of git.
@ -434,6 +500,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Add Slack badge to Readme. - Add Slack badge to Readme.
### Updated ### Updated
- Changed 'default.js' - listen on all attached interfaces by default. - Changed 'default.js' - listen on all attached interfaces by default.
- Add execution of `npm list` after the test are ran in Travis CI. - Add execution of `npm list` after the test are ran in Travis CI.
- Change hooks for the vendors e2e tests. - Change hooks for the vendors e2e tests.
@ -442,6 +509,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Set version of the `express-ipfilter` on 0.3.1. - Set version of the `express-ipfilter` on 0.3.1.
### Fixed ### Fixed
- Fixed issue with incorrect alignment of analog clock when displayed in the center column of the MM. - Fixed issue with incorrect alignment of analog clock when displayed in the center column of the MM.
- Fixed ipWhitelist behaviour to make empty whitelist ([]) allow any and all hosts access to the MM. - Fixed ipWhitelist behaviour to make empty whitelist ([]) allow any and all hosts access to the MM.
- Fixed issue with calendar module where 'excludedEvents' count towards 'maximumEntries'. - Fixed issue with calendar module where 'excludedEvents' count towards 'maximumEntries'.
@ -452,11 +520,13 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
## [2.1.2] - 2017-07-01 ## [2.1.2] - 2017-07-01
### Changed ### Changed
- Revert Docker related changes in favor of [docker-MagicMirror](https://github.com/bastilimbach/docker-MagicMirror). All Docker images are outsourced. ([#856](https://github.com/MichMich/MagicMirror/pull/856)) - Revert Docker related changes in favor of [docker-MagicMirror](https://github.com/bastilimbach/docker-MagicMirror). All Docker images are outsourced. ([#856](https://github.com/MichMich/MagicMirror/pull/856))
- Change Docker base image (Debian + Node) to an arm based distro (AlpineARM + Node) ([#846](https://github.com/MichMich/MagicMirror/pull/846)) - Change Docker base image (Debian + Node) to an arm based distro (AlpineARM + Node) ([#846](https://github.com/MichMich/MagicMirror/pull/846))
- Fix the dockerfile to have it running from the first time. - Fix the dockerfile to have it running from the first time.
### Added ### Added
- Add in option to wrap long calendar events to multiple lines using `wrapEvents` configuration option. - Add in option to wrap long calendar events to multiple lines using `wrapEvents` configuration option.
- Add test e2e `show title newsfeed` for newsfeed module. - Add test e2e `show title newsfeed` for newsfeed module.
- Add task to check configuration file. - Add task to check configuration file.
@ -475,11 +545,13 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Added Romanian translation. - Added Romanian translation.
### Updated ### Updated
- Added missing keys to Polish translation. - Added missing keys to Polish translation.
- Added missing key to German translation. - Added missing key to German translation.
- Added better translation with flexible word order to Finnish translation. - Added better translation with flexible word order to Finnish translation.
### Fixed ### Fixed
- Fix instruction in README for using automatically installer script. - Fix instruction in README for using automatically installer script.
- Bug of duplicated compliments as described in [here](https://forum.magicmirror.builders/topic/2381/compliments-module-stops-cycling-compliments). - Bug of duplicated compliments as described in [here](https://forum.magicmirror.builders/topic/2381/compliments-module-stops-cycling-compliments).
- Fix double message about port when server is starting - Fix double message about port when server is starting
@ -493,6 +565,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install` **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`
### Changed ### Changed
- Add `anytime` group for Compliments module. - Add `anytime` group for Compliments module.
- Compliments module can use remoteFile without default daytime arrays defined. - Compliments module can use remoteFile without default daytime arrays defined.
- Installer: Use init config.js from config.js.sample. - Installer: Use init config.js from config.js.sample.
@ -508,8 +581,9 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Restructured Test Suite. - Restructured Test Suite.
### Added ### Added
- Added Docker support (Pull Request [#673](https://github.com/MichMich/MagicMirror/pull/673)). - Added Docker support (Pull Request [#673](https://github.com/MichMich/MagicMirror/pull/673)).
- Calendar-specific support for `maximumEntries`, and ` maximumNumberOfDays`. - Calendar-specific support for `maximumEntries`, and `maximumNumberOfDays`.
- Add loaded function to modules, providing an async callback. - Add loaded function to modules, providing an async callback.
- Made default newsfeed module aware of gesture events from [MMM-Gestures](https://github.com/thobach/MMM-Gestures) - Made default newsfeed module aware of gesture events from [MMM-Gestures](https://github.com/thobach/MMM-Gestures)
- Add use pm2 for manager process into Installer RaspberryPi script. - Add use pm2 for manager process into Installer RaspberryPi script.
@ -550,11 +624,12 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Added a configurable Week section to the clock module. - Added a configurable Week section to the clock module.
### Fixed ### Fixed
- Update .gitignore to not ignore default modules folder. - Update .gitignore to not ignore default modules folder.
- Remove white flash on boot up. - Remove white flash on boot up.
- Added `update` in Raspberry Pi installation script. - Added `update` in Raspberry Pi installation script.
- Fix an issue where the analog clock looked scrambled. ([#611](https://github.com/MichMich/MagicMirror/issues/611)) - Fix an issue where the analog clock looked scrambled. ([#611](https://github.com/MichMich/MagicMirror/issues/611))
- If units is set to imperial, the showRainAmount option of weatherforecast will show the correct unit. - If units are set to imperial, the showRainAmount option of weatherforecast will show the correct unit.
- Module currentWeather: check if temperature received from api is defined. - Module currentWeather: check if temperature received from api is defined.
- Fix an issue with module hidden status changing to `true` although lock string prevented showing it. - Fix an issue with module hidden status changing to `true` although lock string prevented showing it.
- Fix newsfeed module bug (removeStartTags) - Fix newsfeed module bug (removeStartTags)
@ -566,6 +641,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install` **Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`
### Added ### Added
- Finnish translation. - Finnish translation.
- Danish translation. - Danish translation.
- Turkish translation. - Turkish translation.
@ -578,7 +654,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Module API: Method to overwrite the module's header. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#getheader) for more information. - Module API: Method to overwrite the module's header. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#getheader) for more information.
- Module API: Option to define the minimum MagicMirror version to run a module. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#requiresversion) for more information. - Module API: Option to define the minimum MagicMirror version to run a module. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules#requiresversion) for more information.
- Calendar module now broadcasts the event list to all other modules using the notification system. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/calendar) for more information. - Calendar module now broadcasts the event list to all other modules using the notification system. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/calendar) for more information.
- Possibility to use the the calendar feed as the source for the weather (currentweather & weatherforecast) location data. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/weatherforecast) for more information. - Possibility to use the calendar feed as the source for the weather (currentweather & weatherforecast) location data. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/weatherforecast) for more information.
- Added option to show rain amount in the weatherforecast default module - Added option to show rain amount in the weatherforecast default module
- Add module `updatenotification` to get an update whenever a new version is available. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/updatenotification) for more information. - Add module `updatenotification` to get an update whenever a new version is available. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/updatenotification) for more information.
- Add the ability to set timezone on the date display in the Clock Module - Add the ability to set timezone on the date display in the Clock Module
@ -586,7 +662,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Possibility to use currentweather for the compliments - Possibility to use currentweather for the compliments
- Added option `disabled` for modules. - Added option `disabled` for modules.
- Added option `address` to set bind address. - Added option `address` to set bind address.
- Added option `onlyTemp` for currentweather module to show show only current temperature and weather icon. - Added option `onlyTemp` for currentweather module to show only current temperature and weather icon.
- Added option `remoteFile` to compliments module to load compliment array from filesystem. - Added option `remoteFile` to compliments module to load compliment array from filesystem.
- Added option `zoom` to scale the whole mirror display with a given factor. - Added option `zoom` to scale the whole mirror display with a given factor.
- Added option `roundTemp` for currentweather and weatherforecast modules to display temperatures rounded to nearest integer. - Added option `roundTemp` for currentweather and weatherforecast modules to display temperatures rounded to nearest integer.
@ -596,6 +672,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Add root_path for global vars - Add root_path for global vars
### Updated ### Updated
- Modified translations for Frysk. - Modified translations for Frysk.
- Modified core English translations. - Modified core English translations.
- Updated package.json as a result of Snyk security update. - Updated package.json as a result of Snyk security update.
@ -606,6 +683,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Modules are now secure, and Helmet is now used to prevent abuse of the Mirror's API. - Modules are now secure, and Helmet is now used to prevent abuse of the Mirror's API.
### Fixed ### Fixed
- Solve an issue where module margins would appear when the first module of a section was hidden. - Solve an issue where module margins would appear when the first module of a section was hidden.
- Solved visual display errors on chrome, if all modules in one of the right sections are hidden. - Solved visual display errors on chrome, if all modules in one of the right sections are hidden.
- Global and Module default config values are no longer modified when setting config values. - Global and Module default config values are no longer modified when setting config values.
@ -616,6 +694,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
## [2.0.5] - 2016-09-20 ## [2.0.5] - 2016-09-20
### Added ### Added
- Added ability to remove tags from the beginning or end of newsfeed items in 'newsfeed.js'. - Added ability to remove tags from the beginning or end of newsfeed items in 'newsfeed.js'.
- Added ability to define "the day after tomorrow" for calendar events (Definition for German and Dutch already included). - Added ability to define "the day after tomorrow" for calendar events (Definition for German and Dutch already included).
- Added CII Badge (we are compliant with the CII Best Practices) - Added CII Badge (we are compliant with the CII Best Practices)
@ -623,11 +702,13 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Add the ability to turn off and on the date display in the Clock Module - Add the ability to turn off and on the date display in the Clock Module
### Fixed ### Fixed
- Fix typo in installer. - Fix typo in installer.
- Add message to unsupported Pi error to mention that Pi Zeros must use server only mode, as ARMv6 is unsupported. Closes #374. - Add message to unsupported Pi error to mention that Pi Zeros must use server only mode, as ARMv6 is unsupported. Closes #374.
- Fix API url for weather API. - Fix API url for weather API.
### Updated ### Updated
- Force fullscreen when kioskmode is active. - Force fullscreen when kioskmode is active.
- Update the .github templates and information with more modern information. - Update the .github templates and information with more modern information.
- Update the Gruntfile with a more functional StyleLint implementation. - Update the Gruntfile with a more functional StyleLint implementation.
@ -635,6 +716,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
## [2.0.4] - 2016-08-07 ## [2.0.4] - 2016-08-07
### Added ### Added
- Brazilian Portuguese Translation. - Brazilian Portuguese Translation.
- Option to enable Kiosk mode. - Option to enable Kiosk mode.
- Added ability to start the app with Dev Tools. - Added ability to start the app with Dev Tools.
@ -642,6 +724,7 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Greek Translation - Greek Translation
### Fixed ### Fixed
- Prevent `getModules()` selectors from returning duplicate entries. - Prevent `getModules()` selectors from returning duplicate entries.
- Append endpoints of weather modules with `/` to retrieve the correct data. (Issue [#337](https://github.com/MichMich/MagicMirror/issues/337)) - Append endpoints of weather modules with `/` to retrieve the correct data. (Issue [#337](https://github.com/MichMich/MagicMirror/issues/337))
- Corrected grammar in `module.js` from 'suspend' to 'suspended'. - Corrected grammar in `module.js` from 'suspend' to 'suspended'.
@ -650,55 +733,72 @@ A huge, huge, huge thanks to user @fewieden for all his hard work on the new `we
- Fix issue where translation loading prevented the UI start-up when the language was set to 'en'. (Issue [#388](https://github.com/MichMich/MagicMirror/issues/388)) - Fix issue where translation loading prevented the UI start-up when the language was set to 'en'. (Issue [#388](https://github.com/MichMich/MagicMirror/issues/388))
### Updated ### Updated
- Updated package.json to fix possible vulnerabilities. (Using Snyk) - Updated package.json to fix possible vulnerabilities. (Using Snyk)
- Updated weathericons - Updated weathericons
- Updated default weatherforecast to work with the new icons. - Updated default weatherforecast to work with the new icons.
- More detailed error message in case config file couldn't be loaded. - More detailed error message in case config file couldn't be loaded.
## [2.0.3] - 2016-07-12 ## [2.0.3] - 2016-07-12
### Added ### Added
- Add max newsitems parameter to the newsfeed module. - Add max newsitems parameter to the newsfeed module.
- Translations for Simplified Chinese, Traditional Chinese and Japanese. - Translations for Simplified Chinese, Traditional Chinese and Japanese.
- Polish Translation - Polish Translation
- Add an analog clock in addition to the digital one. - Add an analog clock in addition to the digital one.
### Fixed ### Fixed
- Edit Alert Module to display title & message if they are provided in the notification (Issue [#300](https://github.com/MichMich/MagicMirror/issues/300)) - Edit Alert Module to display title & message if they are provided in the notification (Issue [#300](https://github.com/MichMich/MagicMirror/issues/300))
- Removed 'null' reference from updateModuleContent(). This fixes recent Edge and Internet Explorer browser displays (Issue [#319](https://github.com/MichMich/MagicMirror/issues/319)) - Removed 'null' reference from updateModuleContent(). This fixes recent Edge and Internet Explorer browser displays (Issue [#319](https://github.com/MichMich/MagicMirror/issues/319))
### Changed ### Changed
- Added default string to calendar titleReplace. - Added default string to calendar titleReplace.
## [2.0.2] - 2016-06-05 ## [2.0.2] - 2016-06-05
### Added ### Added
- Norwegian Translations (nb and nn) - Norwegian Translations (nb and nn)
- Portuguese Translation - Portuguese Translation
- Swedish Translation - Swedish Translation
### Fixed ### Fixed
- Added reference to Italian Translation. - Added reference to Italian Translation.
- Added the missing NE translation to all languages. [#344](https://github.com/MichMich/MagicMirror/issues/344) - Added the missing NE translation to all languages. [#344](https://github.com/MichMich/MagicMirror/issues/344)
- Added proper User-Agent string to calendar call. - Added proper User-Agent string to calendar call.
### Changed ### Changed
- Add option to use locationID in weather modules. - Add option to use locationID in weather modules.
## [2.0.1] - 2016-05-18 ## [2.0.1] - 2016-05-18
### Added ### Added
- Changelog - Changelog
- Italian Translation - Italian Translation
### Changed ### Changed
- Improve the installer by fetching the latest Node.js without any 3rd party interferences. - Improve the installer by fetching the latest Node.js without any 3rd party interferences.
## [2.0.0] - 2016-05-03 ## [2.0.0] - 2016-05-03
### Initial release of MagicMirror² ### Initial release of MagicMirror²
It includes (but is not limited to) the following features: It includes (but is not limited to) the following features:
- Modular system allowing 3rd party plugins. - Modular system allowing 3rd party plugins.
- An Node/Electron based application taking away the need for external servers or browsers. - An Node/Electron based application taking away the need for external servers or browsers.
- A complete development API documentation. - A complete development API documentation.
- Small cute fairies that kiss you while you sleep. - Small cute fairies that kiss you while you sleep.
## [1.0.0] - 2014-02-16 ## [1.0.0] - 2014-02-16
### Initial release of MagicMirror. ### Initial release of MagicMirror.
This was part of the blogpost: [http://michaelteeuw.nl/post/83916869600/magic-mirror-part-vi-production-of-the](http://michaelteeuw.nl/post/83916869600/magic-mirror-part-vi-production-of-the)
This was part of the blogpost: [https://michaelteeuw.nl/post/83916869600/magic-mirror-part-vi-production-of-the](https://michaelteeuw.nl/post/83916869600/magic-mirror-part-vi-production-of-the)

View File

@ -1,106 +0,0 @@
module.exports = function(grunt) {
require("time-grunt")(grunt);
var fix = (grunt.option("env") || "lint") === "lint";
grunt.initConfig({
eslint: {
options: {
fix: fix,
configFile: ".eslintrc.json"
},
target: [
"js/*.js",
"modules/default/*.js",
"modules/default/*/*.js",
"serveronly/*.js",
"clientonly/*.js",
"*.js",
"tests/**/*.js",
"!modules/default/alert/notificationFx.js",
"!modules/default/alert/modernizr.custom.js",
"!modules/default/alert/classie.js",
"config/*",
"translations/translations.js",
"vendor/vendor.js"
]
},
stylelint: {
simple: {
options: {
fix: fix,
configFile: ".stylelintrc.json"
},
src: [
"css/main.css",
"modules/default/calendar/calendar.css",
"modules/default/clock/clock_styles.css",
"modules/default/currentweather/currentweather.css",
"modules/default/weatherforcast/weatherforcast.css"
]
}
},
jsonlint: {
main: {
src: [
"package.json",
".eslintrc.json",
".stylelintrc.json",
"translations/*.json",
"modules/default/*/translations/*.json",
"vendor/package.json"
],
options: {
reporter: "jshint"
}
}
},
markdownlint: {
all: {
options: {
config: {
"default": true,
"line-length": false,
"blanks-around-headers": false,
"no-duplicate-header": false,
"no-inline-html": false,
"MD010": false,
"MD001": false,
"MD031": false,
"MD040": false,
"MD002": false,
"MD029": false,
"MD041": false,
"MD032": false,
"MD036": false,
"MD037": false,
"MD009": false,
"MD018": false,
"MD012": false,
"MD026": false,
"MD038": false,
"MD047": false
}
},
src: [
"README.md",
"CHANGELOG.md",
"LICENSE.md",
"modules/README.md",
"modules/default/**/*.md",
"!modules/default/calendar/vendor/ical.js/readme.md"
]
}
},
yamllint: {
all: [
".travis.yml"
]
}
});
grunt.loadNpmTasks("grunt-eslint");
grunt.loadNpmTasks("grunt-stylelint");
grunt.loadNpmTasks("grunt-jsonlint");
grunt.loadNpmTasks("grunt-yamllint");
grunt.loadNpmTasks("grunt-markdownlint");
grunt.registerTask("default", ["eslint", "stylelint", "jsonlint", "markdownlint", "yamllint"]);
};

View File

@ -1,5 +1,4 @@
The MIT License (MIT) # The MIT License (MIT)
=====================
Copyright © 2016-2019 Michael Teeuw Copyright © 2016-2019 Michael Teeuw

View File

@ -4,19 +4,21 @@
<a href="https://david-dm.org/MichMich/MagicMirror"><img src="https://david-dm.org/MichMich/MagicMirror.svg" alt="Dependency Status"></a> <a href="https://david-dm.org/MichMich/MagicMirror"><img src="https://david-dm.org/MichMich/MagicMirror.svg" alt="Dependency Status"></a>
<a href="https://david-dm.org/MichMich/MagicMirror#info=devDependencies"><img src="https://david-dm.org/MichMich/MagicMirror/dev-status.svg" alt="devDependency Status"></a> <a href="https://david-dm.org/MichMich/MagicMirror#info=devDependencies"><img src="https://david-dm.org/MichMich/MagicMirror/dev-status.svg" alt="devDependency Status"></a>
<a href="https://bestpractices.coreinfrastructure.org/projects/347"><img src="https://bestpractices.coreinfrastructure.org/projects/347/badge"></a> <a href="https://bestpractices.coreinfrastructure.org/projects/347"><img src="https://bestpractices.coreinfrastructure.org/projects/347/badge"></a>
<a href="http://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a> <a href="https://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
<a href="https://travis-ci.com/MichMich/MagicMirror"><img src="https://travis-ci.com/MichMich/MagicMirror.svg" alt="Travis"></a> <a href="https://travis-ci.com/MichMich/MagicMirror"><img src="https://travis-ci.com/MichMich/MagicMirror.svg" alt="Travis"></a>
<a href="https://snyk.io/test/github/MichMich/MagicMirror"><img src="https://snyk.io/test/github/MichMich/MagicMirror/badge.svg" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/MichMich/MagicMirror" style="max-width:100%;"></a> <a href="https://snyk.io/test/github/MichMich/MagicMirror"><img src="https://snyk.io/test/github/MichMich/MagicMirror/badge.svg" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/MichMich/MagicMirror" style="max-width:100%;"></a>
</p> </p>
**MagicMirror²** is an open source modular smart mirror platform. With a growing list of installable modules, the **MagicMirror²** allows you to convert your hallway or bathroom mirror into your personal assistant. **MagicMirror²** is built by the creator of [the original MagicMirror](http://michaelteeuw.nl/tagged/magicmirror) with the incredible help of a [growing community of contributors](https://github.com/MichMich/MagicMirror/graphs/contributors). **MagicMirror²** is an open source modular smart mirror platform. With a growing list of installable modules, the **MagicMirror²** allows you to convert your hallway or bathroom mirror into your personal assistant. **MagicMirror²** is built by the creator of [the original MagicMirror](https://michaelteeuw.nl/tagged/magicmirror) with the incredible help of a [growing community of contributors](https://github.com/MichMich/MagicMirror/graphs/contributors).
MagicMirror² focuses on a modular plugin system and uses [Electron](http://electron.atom.io/) as an application wrapper. So no more web server or browser installs necessary! MagicMirror² focuses on a modular plugin system and uses [Electron](https://www.electronjs.org/) as an application wrapper. So no more web server or browser installs necessary!
## Documentation ## Documentation
For the full documentation including **[installation instructions](https://docs.magicmirror.builders/getting-started/installation.html)**, please visit our dedicated documentation website: [https://docs.magicmirror.builders](https://docs.magicmirror.builders). For the full documentation including **[installation instructions](https://docs.magicmirror.builders/getting-started/installation.html)**, please visit our dedicated documentation website: [https://docs.magicmirror.builders](https://docs.magicmirror.builders).
## Links ## Links
- Website: [https://magicmirror.builders](https://magicmirror.builders) - Website: [https://magicmirror.builders](https://magicmirror.builders)
- Documentation: [https://docs.magicmirror.builders](https://docs.magicmirror.builders) - Documentation: [https://docs.magicmirror.builders](https://docs.magicmirror.builders)
- Forum: [https://forum.magicmirror.builders](https://forum.magicmirror.builders) - Forum: [https://forum.magicmirror.builders](https://forum.magicmirror.builders)
@ -28,7 +30,6 @@ For the full documentation including **[installation instructions](https://docs.
Contributions of all kinds are welcome, not only in the form of code but also with regards bug reports and documentation. For the full contribution guidelines, check out: [https://docs.magicmirror.builders/getting-started/contributing.html](https://docs.magicmirror.builders/getting-started/contributing.html) Contributions of all kinds are welcome, not only in the form of code but also with regards bug reports and documentation. For the full contribution guidelines, check out: [https://docs.magicmirror.builders/getting-started/contributing.html](https://docs.magicmirror.builders/getting-started/contributing.html)
## Enjoying MagicMirror? Consider a donation! ## Enjoying MagicMirror? Consider a donation!
MagicMirror² is opensource and free. That doesn't mean we don't need any money. MagicMirror² is opensource and free. That doesn't mean we don't need any money.

View File

@ -18,6 +18,9 @@
["address", "port"].forEach((key) => { ["address", "port"].forEach((key) => {
config[key] = getCommandLineParameter(key, process.env[key.toUpperCase()]); config[key] = getCommandLineParameter(key, process.env[key.toUpperCase()]);
}); });
// determine if "--use-tls"-flag was provided
config["tls"] = process.argv.indexOf("--use-tls") > 0;
} }
function getServerConfig(url) { function getServerConfig(url) {
@ -29,16 +32,16 @@
var configData = ""; var configData = "";
// Gather incoming data // Gather incoming data
response.on("data", function(chunk) { response.on("data", function (chunk) {
configData += chunk; configData += chunk;
}); });
// Resolve promise at the end of the HTTP/HTTPS stream // Resolve promise at the end of the HTTP/HTTPS stream
response.on("end", function() { response.on("end", function () {
resolve(JSON.parse(configData)); resolve(JSON.parse(configData));
}); });
}); });
request.on("error", function(error) { request.on("error", function (error) {
reject(new Error(`Unable to read config from server (${url} (${error.message}`)); reject(new Error(`Unable to read config from server (${url} (${error.message}`));
}); });
}); });
@ -48,7 +51,7 @@
if (message !== undefined && typeof message === "string") { if (message !== undefined && typeof message === "string") {
console.log(message); console.log(message);
} else { } else {
console.log("Usage: 'node clientonly --address 192.168.1.10 --port 8080'"); console.log("Usage: 'node clientonly --address 192.168.1.10 --port 8080 [--use-tls]'");
} }
process.exit(code); process.exit(code);
} }
@ -56,16 +59,18 @@
getServerAddress(); getServerAddress();
(config.address && config.port) || fail(); (config.address && config.port) || fail();
var prefix = config.tls ? "https://" : "http://";
// Only start the client if a non-local server was provided // 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) { if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) === -1) {
getServerConfig(`http://${config.address}:${config.port}/config/`) getServerConfig(`${prefix}${config.address}:${config.port}/config/`)
.then(function (configReturn) { .then(function (configReturn) {
// Pass along the server config via an environment variable // Pass along the server config via an environment variable
var env = Object.create(process.env); var env = Object.create(process.env);
var options = { env: env }; var options = { env: env };
configReturn.address = config.address; configReturn.address = config.address;
configReturn.port = config.port; configReturn.port = config.port;
configReturn.tls = config.tls;
env.config = JSON.stringify(configReturn); env.config = JSON.stringify(configReturn);
// Spawn electron application // Spawn electron application
@ -91,7 +96,6 @@
console.log(`There something wrong. The clientonly is not running code ${code}`); console.log(`There something wrong. The clientonly is not running code ${code}`);
} }
}); });
}) })
.catch(function (reason) { .catch(function (reason) {
fail(`Unable to connect to server: (${reason})`); fail(`Unable to connect to server: (${reason})`);
@ -99,4 +103,4 @@
} else { } else {
fail(); fail();
} }
}()); })();

View File

@ -1,6 +1,6 @@
/* Magic Mirror Config Sample /* Magic Mirror Config Sample
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
* *
* For more information on how you can configure this file * For more information on how you can configure this file
@ -9,17 +9,19 @@
*/ */
var config = { var config = {
address: "localhost", // Address to listen on, can be: address: "localhost", // Address to listen on, can be:
// - "localhost", "127.0.0.1", "::1" to listen on loopback interface // - "localhost", "127.0.0.1", "::1" to listen on loopback interface
// - another specific IPv4/6 to listen on a specific interface // - another specific IPv4/6 to listen on a specific interface
// - "0.0.0.0", "::" to listen on any interface // - "0.0.0.0", "::" to listen on any interface
// Default, when address config is left out or empty, is "localhost" // Default, when address config is left out or empty, is "localhost"
port: 8080, port: 8080,
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], // Set [] to allow all IP addresses basePath: "/", // The URL path where MagicMirror is hosted. If you are using a Reverse proxy
// or add a specific IPv4 of 192.168.1.5 : // you must set the sub path here. basePath must end with a /
// ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.1.5"], ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], // Set [] to allow all IP addresses
// or IPv4 range of 192.168.3.0 --> 192.168.3.15 use CIDR format : // or add a specific IPv4 of 192.168.1.5 :
// ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.3.0/28"], // ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.1.5"],
// or IPv4 range of 192.168.3.0 --> 192.168.3.15 use CIDR format :
// ["127.0.0.1", "::ffff:127.0.0.1", "::1", "::ffff:192.168.3.0/28"],
useHttps: false, // Support HTTPS or not, default "false" will use HTTP useHttps: false, // Support HTTPS or not, default "false" will use HTTP
httpsPrivateKey: "", // HTTPS private key path, only require when useHttps is true httpsPrivateKey: "", // HTTPS private key path, only require when useHttps is true
@ -29,10 +31,10 @@ var config = {
timeFormat: 24, timeFormat: 24,
units: "metric", units: "metric",
// serverOnly: true/false/"local" , // serverOnly: true/false/"local" ,
// local for armv6l processors, default // local for armv6l processors, default
// starts serveronly and then starts chrome browser // starts serveronly and then starts chrome browser
// false, default for all NON-armv6l devices // false, default for all NON-armv6l devices
// true, force serveronly mode, because you want to.. no UI on this device // true, force serveronly mode, because you want to.. no UI on this device
modules: [ modules: [
{ {
@ -88,7 +90,7 @@ var config = {
feeds: [ feeds: [
{ {
title: "New York Times", title: "New York Times",
url: "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml" url: "https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml"
} }
], ],
showSourceTitle: true, showSourceTitle: true,
@ -98,7 +100,6 @@ var config = {
} }
}, },
] ]
}; };
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/

View File

@ -177,10 +177,6 @@ sup {
.region.top.center, .region.top.center,
.region.bottom.center { .region.bottom.center {
left: 50%; left: 50%;
-moz-transform: translateX(-50%);
-o-transform: translateX(-50%);
-webkit-transform: translateX(-50%);
-ms-transform: translateX(-50%);
transform: translateX(-50%); transform: translateX(-50%);
} }
@ -213,10 +209,6 @@ sup {
.region.middle.center { .region.middle.center {
width: 100%; width: 100%;
text-align: center; text-align: center;
-moz-transform: translateY(-50%);
-o-transform: translateY(-50%);
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%); transform: translateY(-50%);
} }

View File

@ -4,9 +4,9 @@
"lockfileVersion": 1, "lockfileVersion": 1,
"dependencies": { "dependencies": {
"roboto-fontface": { "roboto-fontface": {
"version": "0.8.0", "version": "0.10.0",
"resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.8.0.tgz", "resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz",
"integrity": "sha512-ZYzRkETgBrdEGzL5JSKimvjI2CX7ioyZCkX2BpcfyjqI+079W0wHAyj5W4rIZMcDSOHgLZtgz1IdDi/vU77KEQ==" "integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g=="
} }
} }
} }

View File

@ -1,15 +1,15 @@
{ {
"name": "magicmirror-fonts", "name": "magicmirror-fonts",
"description": "Package for fonts use by MagicMirror Core.", "description": "Package for fonts use by MagicMirror Core.",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/MichMich/MagicMirror.git" "url": "git+https://github.com/MichMich/MagicMirror.git"
}, },
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {
"url": "https://github.com/MichMich/MagicMirror/issues" "url": "https://github.com/MichMich/MagicMirror/issues"
}, },
"dependencies": { "dependencies": {
"roboto-fontface": "^0.8.0" "roboto-fontface": "^0.10.0"
} }
} }

View File

@ -2,94 +2,57 @@
font-family: Roboto; font-family: Roboto;
font-style: normal; font-style: normal;
font-weight: 100; font-weight: 100;
src: src: local("Roboto Thin"), local("Roboto-Thin"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff") format("woff");
local("Roboto Thin"),
local("Roboto-Thin"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff2") format("woff2"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff") format("woff"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.ttf") format("truetype");
} }
@font-face { @font-face {
font-family: "Roboto Condensed"; font-family: "Roboto Condensed";
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
src: src: local("Roboto Condensed Light"), local("RobotoCondensed-Light"), url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.woff2") format("woff2"),
local("Roboto Condensed Light"), url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.woff") format("woff");
local("RobotoCondensed-Light"),
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.woff2") format("woff2"),
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.woff") format("woff"),
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.ttf") format("truetype");
} }
@font-face { @font-face {
font-family: "Roboto Condensed"; font-family: "Roboto Condensed";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: src: local("Roboto Condensed"), local("RobotoCondensed-Regular"), url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.woff2") format("woff2"),
local("Roboto Condensed"), url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.woff") format("woff");
local("RobotoCondensed-Regular"),
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.woff2") format("woff2"),
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.woff") format("woff"),
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.ttf") format("truetype");
} }
@font-face { @font-face {
font-family: "Roboto Condensed"; font-family: "Roboto Condensed";
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
src: src: local("Roboto Condensed Bold"), local("RobotoCondensed-Bold"), url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.woff2") format("woff2"),
local("Roboto Condensed Bold"), url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.woff") format("woff");
local("RobotoCondensed-Bold"),
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.woff2") format("woff2"),
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.woff") format("woff"),
url("node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.ttf") format("truetype");
} }
@font-face { @font-face {
font-family: Roboto; font-family: Roboto;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: src: local("Roboto"), local("Roboto-Regular"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff") format("woff");
local("Roboto"),
local("Roboto-Regular"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2") format("woff2"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff") format("woff"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.ttf") format("truetype");
} }
@font-face { @font-face {
font-family: Roboto; font-family: Roboto;
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
src: src: local("Roboto Medium"), local("Roboto-Medium"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff") format("woff");
local("Roboto Medium"),
local("Roboto-Medium"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2") format("woff2"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff") format("woff"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.ttf") format("truetype");
} }
@font-face { @font-face {
font-family: Roboto; font-family: Roboto;
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
src: src: local("Roboto Bold"), local("Roboto-Bold"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff") format("woff");
local("Roboto Bold"),
local("Roboto-Bold"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2") format("woff2"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff") format("woff"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.ttf") format("truetype");
} }
@font-face { @font-face {
font-family: Roboto; font-family: Roboto;
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
src: src: local("Roboto Light"), local("Roboto-Light"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2") format("woff2"), url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff") format("woff");
local("Roboto Light"),
local("Roboto-Light"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2") format("woff2"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff") format("woff"),
url("node_modules/roboto-fontface/fonts/roboto/Roboto-Light.ttf") format("truetype");
} }

View File

@ -1,7 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
roboto-fontface@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/roboto-fontface/-/roboto-fontface-0.8.0.tgz#031a83c8f79932801a57d83bf743f37250163499"

View File

@ -1,7 +1,7 @@
/* Magic Mirror /* Magic Mirror
* The Core App (Server) * The Core App (Server)
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
var fs = require("fs"); var fs = require("fs");
@ -44,7 +44,7 @@ process.on("uncaughtException", function (err) {
/* App - The core app. /* App - The core app.
*/ */
var App = function() { var App = function () {
var nodeHelpers = []; var nodeHelpers = [];
/* loadConfig(callback) /* loadConfig(callback)
@ -53,15 +53,15 @@ var App = function() {
* *
* argument callback function - The callback function. * argument callback function - The callback function.
*/ */
var loadConfig = function(callback) { var loadConfig = function (callback) {
console.log("Loading config ..."); console.log("Loading config ...");
var defaults = require(__dirname + "/defaults.js"); var defaults = require(__dirname + "/defaults.js");
// For this check proposed to TestSuite // For this check proposed to TestSuite
// https://forum.magicmirror.builders/topic/1456/test-suite-for-magicmirror/8 // https://forum.magicmirror.builders/topic/1456/test-suite-for-magicmirror/8
var configFilename = path.resolve(global.root_path + "/config/config.js"); var configFilename = path.resolve(global.root_path + "/config/config.js");
if (typeof(global.configuration_file) !== "undefined") { if (typeof global.configuration_file !== "undefined") {
configFilename = path.resolve(global.configuration_file); configFilename = path.resolve(global.configuration_file);
} }
try { try {
@ -82,23 +82,19 @@ var App = function() {
} }
}; };
var checkDeprecatedOptions = function(userConfig) { var checkDeprecatedOptions = function (userConfig) {
var deprecated = require(global.root_path + "/js/deprecated.js"); var deprecated = require(global.root_path + "/js/deprecated.js");
var deprecatedOptions = deprecated.configs; var deprecatedOptions = deprecated.configs;
var usedDeprecated = []; var usedDeprecated = [];
deprecatedOptions.forEach(function(option) { deprecatedOptions.forEach(function (option) {
if (userConfig.hasOwnProperty(option)) { if (userConfig.hasOwnProperty(option)) {
usedDeprecated.push(option); usedDeprecated.push(option);
} }
}); });
if (usedDeprecated.length > 0) { if (usedDeprecated.length > 0) {
console.warn(Utils.colors.warn( console.warn(Utils.colors.warn("WARNING! Your config is using deprecated options: " + usedDeprecated.join(", ") + ". Check README and CHANGELOG for more up-to-date ways of getting the same functionality."));
"WARNING! Your config is using deprecated options: " +
usedDeprecated.join(", ") +
". Check README and CHANGELOG for more up-to-date ways of getting the same functionality.")
);
} }
}; };
@ -107,8 +103,7 @@ var App = function() {
* *
* argument module string - The name of the module (including subpath). * argument module string - The name of the module (including subpath).
*/ */
var loadModule = function(module, callback) { var loadModule = function (module, callback) {
var elements = module.split("/"); var elements = module.split("/");
var moduleName = elements[elements.length - 1]; var moduleName = elements[elements.length - 1];
var moduleFolder = __dirname + "/../modules/" + module; var moduleFolder = __dirname + "/../modules/" + module;
@ -156,13 +151,13 @@ var App = function() {
* *
* argument module string - The name of the module (including subpath). * argument module string - The name of the module (including subpath).
*/ */
var loadModules = function(modules, callback) { var loadModules = function (modules, callback) {
console.log("Loading module helpers ..."); console.log("Loading module helpers ...");
var loadNextModule = function() { var loadNextModule = function () {
if (modules.length > 0) { if (modules.length > 0) {
var nextModule = modules[0]; var nextModule = modules[0];
loadModule(nextModule, function() { loadModule(nextModule, function () {
modules = modules.slice(1); modules = modules.slice(1);
loadNextModule(); loadNextModule();
}); });
@ -205,9 +200,8 @@ var App = function() {
* *
* argument callback function - The callback function. * argument callback function - The callback function.
*/ */
this.start = function(callback) { this.start = function (callback) {
loadConfig(function (c) {
loadConfig(function(c) {
config = c; config = c;
var modules = []; var modules = [];
@ -219,8 +213,8 @@ var App = function() {
} }
} }
loadModules(modules, function() { loadModules(modules, function () {
var server = new Server(config, function(app, io) { var server = new Server(config, function (app, io) {
console.log("Server started ..."); console.log("Server started ...");
for (var h in nodeHelpers) { for (var h in nodeHelpers) {
@ -245,7 +239,7 @@ var App = function() {
* This calls each node_helper's STOP() function, if it exists. * This calls each node_helper's STOP() function, if it exists.
* Added to fix #1056 * Added to fix #1056
*/ */
this.stop = function() { this.stop = function () {
for (var h in nodeHelpers) { for (var h in nodeHelpers) {
var nodeHelper = nodeHelpers[h]; var nodeHelper = nodeHelpers[h];
if (typeof nodeHelper.stop === "function") { if (typeof nodeHelper.stop === "function") {
@ -262,7 +256,9 @@ var App = function() {
*/ */
process.on("SIGINT", () => { process.on("SIGINT", () => {
console.log("[SIGINT] Received. Shutting down server..."); console.log("[SIGINT] Received. Shutting down server...");
setTimeout(() => { process.exit(0); }, 3000); // Force quit after 3 seconds setTimeout(() => {
process.exit(0);
}, 3000); // Force quit after 3 seconds
this.stop(); this.stop();
process.exit(0); process.exit(0);
}); });
@ -271,7 +267,9 @@ var App = function() {
*/ */
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
console.log("[SIGTERM] Received. Shutting down server..."); console.log("[SIGTERM] Received. Shutting down server...");
setTimeout(() => { process.exit(0); }, 3000); // Force quit after 3 seconds setTimeout(() => {
process.exit(0);
}, 3000); // Force quit after 3 seconds
this.stop(); this.stop();
process.exit(0); process.exit(0);
}); });

View File

@ -11,11 +11,13 @@
const Linter = require("eslint").Linter; const Linter = require("eslint").Linter;
const linter = new Linter(); const linter = new Linter();
const config = require(__dirname + "/../../.eslintrc.json");
var path = require("path"); const path = require("path");
var fs = require("fs"); const fs = require("fs");
var Utils = require(__dirname + "/../../js/utils.js");
const rootPath = path.resolve(__dirname + "/../");
const config = require(rootPath + "/.eslintrc.json");
const Utils = require(rootPath + "/js/utils.js");
/* getConfigFile() /* getConfigFile()
* Return string with path of configuration file * Return string with path of configuration file
@ -23,8 +25,7 @@ var Utils = require(__dirname + "/../../js/utils.js");
*/ */
function getConfigFile() { function getConfigFile() {
// FIXME: This function should be in core. Do you want refactor me ;) ?, be good! // FIXME: This function should be in core. Do you want refactor me ;) ?, be good!
rootPath = path.resolve(__dirname + "/../../"); let configFileName = path.resolve(rootPath + "/config/config.js");
var configFileName = path.resolve(rootPath + "/config/config.js");
if (process.env.MM_CONFIG_FILE) { if (process.env.MM_CONFIG_FILE) {
configFileName = path.resolve(process.env.MM_CONFIG_FILE); configFileName = path.resolve(process.env.MM_CONFIG_FILE);
} }
@ -32,7 +33,7 @@ function getConfigFile() {
} }
function checkConfigFile() { function checkConfigFile() {
var configFileName = getConfigFile(); const configFileName = getConfigFile();
// Check if file is present // Check if file is present
if (fs.existsSync(configFileName) === false) { if (fs.existsSync(configFileName) === false) {
console.error(Utils.colors.error("File not found: "), configFileName); console.error(Utils.colors.error("File not found: "), configFileName);
@ -49,24 +50,22 @@ function checkConfigFile() {
// Validate syntax of the configuration file. // Validate syntax of the configuration file.
// In case the there errors show messages and // In case the there errors show messages and
// return // return
console.info(Utils.colors.info("Checking file... ", configFileName)); console.info(Utils.colors.info("Checking file... "), configFileName);
// I'm not sure if all ever is utf-8 // I'm not sure if all ever is utf-8
fs.readFile(configFileName, "utf-8", function (err, data) { fs.readFile(configFileName, "utf-8", function (err, data) {
if (err) { throw err; } if (err) {
throw err;
}
const messages = linter.verify(data, config); const messages = linter.verify(data, config);
if (messages.length === 0) { if (messages.length === 0) {
console.log("Your configuration file doesn't contain syntax errors :)"); console.log("Your configuration file doesn't contain syntax errors :)");
return true; return true;
} else { } else {
errors = messages; messages.forEach((error) => {
for (var idx in errors) {
error = errors[idx];
console.log("Line", error.line, "col", error.column, error.message); console.log("Line", error.line, "col", error.column, error.message);
} });
} }
}); });
} }
if (process.env.NODE_ENV !== "test") { checkConfigFile();
checkConfigFile();
}

View File

@ -1,15 +1,22 @@
/* global Class, xyz */
/* Simple JavaScript Inheritance /* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/ * By John Resig https://johnresig.com/
*
* Inspired by base2 and Prototype
*
* MIT Licensed. * MIT Licensed.
*/ */
// Inspired by base2 and Prototype
(function () { (function () {
var initializing = false; var initializing = false;
var fnTest = /xyz/.test(function () { xyz; }) ? /\b_super\b/ : /.*/; var fnTest = /xyz/.test(function () {
xyz;
})
? /\b_super\b/
: /.*/;
// The base Class implementation (does nothing) // The base Class implementation (does nothing)
this.Class = function () { }; this.Class = function () {};
// Create a new Class that inherits from this class // Create a new Class that inherits from this class
Class.extend = function (prop) { Class.extend = function (prop) {
@ -22,30 +29,32 @@
initializing = false; initializing = false;
// Make a copy of all prototype properties, to prevent reference issues. // Make a copy of all prototype properties, to prevent reference issues.
for (var name in prototype) { for (var p in prototype) {
prototype[name] = cloneObject(prototype[name]); prototype[p] = cloneObject(prototype[p]);
} }
// Copy the properties over onto the new prototype // Copy the properties over onto the new prototype
for (var name in prop) { for (var name in prop) {
// Check if we're overwriting an existing function // Check if we're overwriting an existing function
prototype[name] = typeof prop[name] === "function" && prototype[name] =
typeof _super[name] === "function" && fnTest.test(prop[name]) ? (function (name, fn) { typeof prop[name] === "function" && typeof _super[name] === "function" && fnTest.test(prop[name])
return function () { ? (function (name, fn) {
var tmp = this._super; return function () {
var tmp = this._super;
// Add a new ._super() method that is the same method // Add a new ._super() method that is the same method
// but on the super-class // but on the super-class
this._super = _super[name]; this._super = _super[name];
// The method only need to be bound temporarily, so we // The method only need to be bound temporarily, so we
// remove it when we're done executing // remove it when we're done executing
var ret = fn.apply(this, arguments); var ret = fn.apply(this, arguments);
this._super = tmp; this._super = tmp;
return ret; return ret;
}; };
})(name, prop[name]) : prop[name]; })(name, prop[name])
: prop[name];
} }
// The dummy class constructor // The dummy class constructor

View File

@ -1,20 +1,20 @@
/* exported defaults */ /* global mmPort */
/* Magic Mirror /* Magic Mirror
* Config Defauls * Config Defaults
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
var port = 8080;
var address = "localhost"; var address = "localhost";
if (typeof(mmPort) !== "undefined") { var port = 8080;
if (typeof mmPort !== "undefined") {
port = mmPort; port = mmPort;
} }
var defaults = { var defaults = {
address: address, address: address,
port: port, port: port,
basePath: "/",
kioskmode: false, kioskmode: false,
electronOptions: {}, electronOptions: {},
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
@ -68,14 +68,16 @@ var defaults = {
config: { config: {
text: "www.michaelteeuw.nl" text: "www.michaelteeuw.nl"
} }
}, }
], ],
paths: { paths: {
modules: "modules", modules: "modules",
vendor: "vendor" vendor: "vendor"
}, }
}; };
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {module.exports = defaults;} if (typeof module !== "undefined") {
module.exports = defaults;
}

View File

@ -1,14 +1,16 @@
/* Magic Mirror Deprecated Config Options List /* Magic Mirror Deprecated Config Options List
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
* *
* Olex S. original idea this deprecated option * Olex S. original idea this deprecated option
*/ */
var deprecated = { var deprecated = {
configs: ["kioskmode"], configs: ["kioskmode"]
}; };
/*************** DO NOT EDIT THE LINE BELOW ***************/ /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {module.exports = deprecated;} if (typeof module !== "undefined") {
module.exports = deprecated;
}

View File

@ -45,8 +45,16 @@ function createWindow() {
// and load the index.html of the app. // and load the index.html of the app.
// If config.address is not defined or is an empty string (listening on all interfaces), connect to localhost // If config.address is not defined or is an empty string (listening on all interfaces), connect to localhost
var prefix;
if (config["tls"] !== null && config["tls"]) {
prefix = "https://";
} else {
prefix = "http://";
}
var address = (config.address === void 0) | (config.address === "") ? (config.address = "localhost") : config.address; var address = (config.address === void 0) | (config.address === "") ? (config.address = "localhost") : config.address;
mainWindow.loadURL(`http://${address}:${config.port}`); mainWindow.loadURL(`${prefix}${address}:${config.port}`);
// Open the DevTools if run with "npm start dev" // Open the DevTools if run with "npm start dev"
if (process.argv.includes("dev")) { if (process.argv.includes("dev")) {
@ -54,21 +62,21 @@ function createWindow() {
} }
// Set responders for window events. // Set responders for window events.
mainWindow.on("closed", function() { mainWindow.on("closed", function () {
mainWindow = null; mainWindow = null;
}); });
if (config.kioskmode) { if (config.kioskmode) {
mainWindow.on("blur", function() { mainWindow.on("blur", function () {
mainWindow.focus(); mainWindow.focus();
}); });
mainWindow.on("leave-full-screen", function() { mainWindow.on("leave-full-screen", function () {
mainWindow.setFullScreen(true); mainWindow.setFullScreen(true);
}); });
mainWindow.on("resize", function() { mainWindow.on("resize", function () {
setTimeout(function() { setTimeout(function () {
mainWindow.reload(); mainWindow.reload();
}, 1000); }, 1000);
}); });
@ -77,17 +85,17 @@ function createWindow() {
// This method will be called when Electron has finished // This method will be called when Electron has finished
// initialization and is ready to create browser windows. // initialization and is ready to create browser windows.
app.on("ready", function() { app.on("ready", function () {
console.log("Launching application."); console.log("Launching application.");
createWindow(); createWindow();
}); });
// Quit when all windows are closed. // Quit when all windows are closed.
app.on("window-all-closed", function() { app.on("window-all-closed", function () {
createWindow(); createWindow();
}); });
app.on("activate", function() { app.on("activate", function () {
// On OS X it's common to re-create a window in the app when the // On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open. // dock icon is clicked and there are no other windows open.
if (mainWindow === null) { if (mainWindow === null) {
@ -104,7 +112,9 @@ app.on("activate", function() {
app.on("before-quit", (event) => { app.on("before-quit", (event) => {
console.log("Shutting down server..."); console.log("Shutting down server...");
event.preventDefault(); event.preventDefault();
setTimeout(() => { process.exit(0); }, 3000); // Force-quit after 3 seconds. setTimeout(() => {
process.exit(0);
}, 3000); // Force-quit after 3 seconds.
core.stop(); core.stop();
process.exit(0); process.exit(0);
}); });
@ -112,7 +122,7 @@ app.on("before-quit", (event) => {
// Start the core application if server is run on localhost // Start the core application if server is run on localhost
// This starts all node helpers and starts the webserver. // This starts all node helpers and starts the webserver.
if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) > -1) { if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) > -1) {
core.start(function(c) { core.start(function (c) {
config = c; config = c;
}); });
} }

View File

@ -1,13 +1,12 @@
/* global config, vendor, MM, Log, Module */ /* global defaultModules, vendor */
/* Magic Mirror /* Magic Mirror
* Module and File loaders. * Module and File loaders.
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
var Loader = (function () {
var Loader = (function() {
/* Create helper variables */ /* Create helper variables */
var loadedModuleFiles = []; var loadedModuleFiles = [];
@ -19,14 +18,13 @@ var Loader = (function() {
/* loadModules() /* loadModules()
* Loops thru all modules and requests load for every module. * Loops thru all modules and requests load for every module.
*/ */
var loadModules = function() { var loadModules = function () {
var moduleData = getModuleData(); var moduleData = getModuleData();
var loadNextModule = function() { var loadNextModule = function () {
if (moduleData.length > 0) { if (moduleData.length > 0) {
var nextModule = moduleData[0]; var nextModule = moduleData[0];
loadModule(nextModule, function() { loadModule(nextModule, function () {
moduleData = moduleData.slice(1); moduleData = moduleData.slice(1);
loadNextModule(); loadNextModule();
}); });
@ -35,11 +33,10 @@ var Loader = (function() {
// This is done after all the modules so we can // This is done after all the modules so we can
// overwrite all the defined styles. // overwrite all the defined styles.
loadFile(config.customCss, function() { loadFile(config.customCss, function () {
// custom.css loaded. Start all modules. // custom.css loaded. Start all modules.
startModules(); startModules();
}); });
} }
}; };
@ -49,7 +46,7 @@ var Loader = (function() {
/* startModules() /* startModules()
* Loops thru all modules and requests start for every module. * Loops thru all modules and requests start for every module.
*/ */
var startModules = function() { var startModules = function () {
for (var m in moduleObjects) { for (var m in moduleObjects) {
var module = moduleObjects[m]; var module = moduleObjects[m];
module.start(); module.start();
@ -64,7 +61,7 @@ var Loader = (function() {
* *
* return array - module data as configured in config * return array - module data as configured in config
*/ */
var getAllModules = function() { var getAllModules = function () {
return config.modules; return config.modules;
}; };
@ -73,7 +70,7 @@ var Loader = (function() {
* *
* return array - Module information. * return array - Module information.
*/ */
var getModuleData = function() { var getModuleData = function () {
var modules = getAllModules(); var modules = getAllModules();
var moduleFiles = []; var moduleFiles = [];
@ -97,12 +94,12 @@ var Loader = (function() {
index: m, index: m,
identifier: "module_" + m + "_" + module, identifier: "module_" + m + "_" + module,
name: moduleName, name: moduleName,
path: moduleFolder + "/" , path: moduleFolder + "/",
file: moduleName + ".js", file: moduleName + ".js",
position: moduleData.position, position: moduleData.position,
header: moduleData.header, header: moduleData.header,
config: moduleData.config, config: moduleData.config,
classes: (typeof moduleData.classes !== "undefined") ? moduleData.classes + " " + module : module classes: typeof moduleData.classes !== "undefined" ? moduleData.classes + " " + module : module
}); });
} }
@ -115,13 +112,13 @@ var Loader = (function() {
* argument callback function - Function called when done. * argument callback function - Function called when done.
* argument module object - Information about the module we want to load. * argument module object - Information about the module we want to load.
*/ */
var loadModule = function(module, callback) { var loadModule = function (module, callback) {
var url = module.path + "/" + module.file; var url = module.path + "/" + module.file;
var afterLoad = function() { var afterLoad = function () {
var moduleObject = Module.create(module.name); var moduleObject = Module.create(module.name);
if (moduleObject) { if (moduleObject) {
bootstrapModule(module, moduleObject, function() { bootstrapModule(module, moduleObject, function () {
callback(); callback();
}); });
} else { } else {
@ -132,7 +129,7 @@ var Loader = (function() {
if (loadedModuleFiles.indexOf(url) !== -1) { if (loadedModuleFiles.indexOf(url) !== -1) {
afterLoad(); afterLoad();
} else { } else {
loadFile(url, function() { loadFile(url, function () {
loadedModuleFiles.push(url); loadedModuleFiles.push(url);
afterLoad(); afterLoad();
}); });
@ -146,16 +143,16 @@ var Loader = (function() {
* argument mObj object - Modules instance. * argument mObj object - Modules instance.
* argument callback function - Function called when done. * argument callback function - Function called when done.
*/ */
var bootstrapModule = function(module, mObj, callback) { var bootstrapModule = function (module, mObj, callback) {
Log.info("Bootstrapping module: " + module.name); Log.info("Bootstrapping module: " + module.name);
mObj.setData(module); mObj.setData(module);
mObj.loadScripts(function() { mObj.loadScripts(function () {
Log.log("Scripts loaded for: " + module.name); Log.log("Scripts loaded for: " + module.name);
mObj.loadStyles(function() { mObj.loadStyles(function () {
Log.log("Styles loaded for: " + module.name); Log.log("Styles loaded for: " + module.name);
mObj.loadTranslations(function() { mObj.loadTranslations(function () {
Log.log("Translations loaded for: " + module.name); Log.log("Translations loaded for: " + module.name);
moduleObjects.push(mObj); moduleObjects.push(mObj);
callback(); callback();
@ -170,52 +167,58 @@ var Loader = (function() {
* argument fileName string - Path of the file we want to load. * argument fileName string - Path of the file we want to load.
* argument callback function - Function called when done. * argument callback function - Function called when done.
*/ */
var loadFile = function(fileName, callback) { var loadFile = function (fileName, callback) {
var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1); var extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
switch (extension.toLowerCase()) { switch (extension.toLowerCase()) {
case "js": case "js":
Log.log("Load script: " + fileName); Log.log("Load script: " + fileName);
var script = document.createElement("script"); var script = document.createElement("script");
script.type = "text/javascript"; script.type = "text/javascript";
script.src = fileName; script.src = fileName;
script.onload = function() { script.onload = function () {
if (typeof callback === "function") {callback();} if (typeof callback === "function") {
}; callback();
script.onerror = function() { }
console.error("Error on loading script:", fileName); };
if (typeof callback === "function") {callback();} script.onerror = function () {
}; console.error("Error on loading script:", fileName);
if (typeof callback === "function") {
callback();
}
};
document.getElementsByTagName("body")[0].appendChild(script); document.getElementsByTagName("body")[0].appendChild(script);
break; break;
case "css": case "css":
Log.log("Load stylesheet: " + fileName); Log.log("Load stylesheet: " + fileName);
var stylesheet = document.createElement("link"); var stylesheet = document.createElement("link");
stylesheet.rel = "stylesheet"; stylesheet.rel = "stylesheet";
stylesheet.type = "text/css"; stylesheet.type = "text/css";
stylesheet.href = fileName; stylesheet.href = fileName;
stylesheet.onload = function() { stylesheet.onload = function () {
if (typeof callback === "function") {callback();} if (typeof callback === "function") {
}; callback();
stylesheet.onerror = function() { }
console.error("Error on loading stylesheet:", fileName); };
if (typeof callback === "function") {callback();} stylesheet.onerror = function () {
}; console.error("Error on loading stylesheet:", fileName);
if (typeof callback === "function") {
callback();
}
};
document.getElementsByTagName("head")[0].appendChild(stylesheet); document.getElementsByTagName("head")[0].appendChild(stylesheet);
break; break;
} }
}; };
/* Public Methods */ /* Public Methods */
return { return {
/* loadModules() /* loadModules()
* Load all modules as defined in the config. * Load all modules as defined in the config.
*/ */
loadModules: function() { loadModules: function () {
loadModules(); loadModules();
}, },
@ -227,8 +230,7 @@ var Loader = (function() {
* argument module Module Object - the module that calls the loadFile function. * argument module Module Object - the module that calls the loadFile function.
* argument callback function - Function called when done. * argument callback function - Function called when done.
*/ */
loadFile: function(fileName, module, callback) { loadFile: function (fileName, module, callback) {
if (loadedFiles.indexOf(fileName.toLowerCase()) !== -1) { if (loadedFiles.indexOf(fileName.toLowerCase()) !== -1) {
Log.log("File already loaded: " + fileName); Log.log("File already loaded: " + fileName);
callback(); callback();

View File

@ -1,20 +1,15 @@
/* global console */
/* exported Log */
/* Magic Mirror /* Magic Mirror
* Logger * Logger
* This logger is very simple, but needs to be extended.
* This system can eventually be used to push the log messages to an external target.
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
const Log = (function () {
// This logger is very simple, but needs to be extended.
// This system can eventually be used to push the log messages to an external target.
var Log = (function() {
return { return {
info: Function.prototype.bind.call(console.info, console), info: Function.prototype.bind.call(console.info, console),
log: Function.prototype.bind.call(console.log, console), log: Function.prototype.bind.call(console.log, console),
error: Function.prototype.bind.call(console.error, console), error: Function.prototype.bind.call(console.error, console),
warn: Function.prototype.bind.call(console.warn, console), warn: Function.prototype.bind.call(console.warn, console),
group: Function.prototype.bind.call(console.group, console), group: Function.prototype.bind.call(console.group, console),

View File

@ -1,14 +1,12 @@
/* global Log, Loader, Module, config, defaults */ /* global Loader, defaults, Translator */
/* Magic Mirror /* Magic Mirror
* Main System * Main System
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
var MM = (function () {
var MM = (function() {
var modules = []; var modules = [];
/* Private Methods */ /* Private Methods */
@ -17,10 +15,10 @@ var MM = (function() {
* Create dom objects for all modules that * Create dom objects for all modules that
* are configured for a specific position. * are configured for a specific position.
*/ */
var createDomObjects = function() { var createDomObjects = function () {
var domCreationPromises = []; var domCreationPromises = [];
modules.forEach(function(module) { modules.forEach(function (module) {
if (typeof module.data.position !== "string") { if (typeof module.data.position !== "string") {
return; return;
} }
@ -53,14 +51,16 @@ var MM = (function() {
var domCreationPromise = updateDom(module, 0); var domCreationPromise = updateDom(module, 0);
domCreationPromises.push(domCreationPromise); domCreationPromises.push(domCreationPromise);
domCreationPromise.then(function() { domCreationPromise
sendNotification("MODULE_DOM_CREATED", null, null, module); .then(function () {
}).catch(Log.error); sendNotification("MODULE_DOM_CREATED", null, null, module);
})
.catch(Log.error);
}); });
updateWrapperStates(); updateWrapperStates();
Promise.all(domCreationPromises).then(function() { Promise.all(domCreationPromises).then(function () {
sendNotification("DOM_OBJECTS_CREATED"); sendNotification("DOM_OBJECTS_CREATED");
}); });
}; };
@ -70,8 +70,8 @@ var MM = (function() {
* *
* argument position string - The name of the position. * argument position string - The name of the position.
*/ */
var selectWrapper = function(position) { var selectWrapper = function (position) {
var classes = position.replace("_"," "); var classes = position.replace("_", " ");
var parentWrapper = document.getElementsByClassName(classes); var parentWrapper = document.getElementsByClassName(classes);
if (parentWrapper.length > 0) { if (parentWrapper.length > 0) {
var wrapper = parentWrapper[0].getElementsByClassName("container"); var wrapper = parentWrapper[0].getElementsByClassName("container");
@ -89,7 +89,7 @@ var MM = (function() {
* argument sender Module - The module that sent the notification. * argument sender Module - The module that sent the notification.
* argument sendTo Module - The module to send the notification to. (optional) * argument sendTo Module - The module to send the notification to. (optional)
*/ */
var sendNotification = function(notification, payload, sender, sendTo) { var sendNotification = function (notification, payload, sender, sendTo) {
for (var m in modules) { for (var m in modules) {
var module = modules[m]; var module = modules[m];
if (module !== sender && (!sendTo || module === sendTo)) { if (module !== sender && (!sendTo || module === sendTo)) {
@ -106,8 +106,8 @@ var MM = (function() {
* *
* return Promise - Resolved when the dom is fully updated. * return Promise - Resolved when the dom is fully updated.
*/ */
var updateDom = function(module, speed) { var updateDom = function (module, speed) {
return new Promise(function(resolve) { return new Promise(function (resolve) {
var newContentPromise = module.getDom(); var newContentPromise = module.getDom();
var newHeader = module.getHeader(); var newHeader = module.getHeader();
@ -116,11 +116,13 @@ var MM = (function() {
newContentPromise = Promise.resolve(newContentPromise); newContentPromise = Promise.resolve(newContentPromise);
} }
newContentPromise.then(function(newContent) { newContentPromise
var updatePromise = updateDomWithContent(module, speed, newHeader, newContent); .then(function (newContent) {
var updatePromise = updateDomWithContent(module, speed, newHeader, newContent);
updatePromise.then(resolve).catch(Log.error); updatePromise.then(resolve).catch(Log.error);
}).catch(Log.error); })
.catch(Log.error);
}); });
}; };
@ -134,8 +136,8 @@ var MM = (function() {
* *
* return Promise - Resolved when the module dom has been updated. * return Promise - Resolved when the module dom has been updated.
*/ */
var updateDomWithContent = function(module, speed, newHeader, newContent) { var updateDomWithContent = function (module, speed, newHeader, newContent) {
return new Promise(function(resolve) { return new Promise(function (resolve) {
if (module.hidden || !speed) { if (module.hidden || !speed) {
updateModuleContent(module, newHeader, newContent); updateModuleContent(module, newHeader, newContent);
resolve(); resolve();
@ -153,7 +155,7 @@ var MM = (function() {
return; return;
} }
hideModule(module, speed / 2, function() { hideModule(module, speed / 2, function () {
updateModuleContent(module, newHeader, newContent); updateModuleContent(module, newHeader, newContent);
if (!module.hidden) { if (!module.hidden) {
showModule(module, speed / 2); showModule(module, speed / 2);
@ -172,7 +174,7 @@ var MM = (function() {
* *
* return bool - Does the module need an update? * return bool - Does the module need an update?
*/ */
var moduleNeedsUpdate = function(module, newHeader, newContent) { var moduleNeedsUpdate = function (module, newHeader, newContent) {
var moduleWrapper = document.getElementById(module.identifier); var moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper === null) { if (moduleWrapper === null) {
return false; return false;
@ -202,9 +204,11 @@ var MM = (function() {
* argument newHeader String - The new header that is generated. * argument newHeader String - The new header that is generated.
* argument newContent Domobject - The new content that is generated. * argument newContent Domobject - The new content that is generated.
*/ */
var updateModuleContent = function(module, newHeader, newContent) { var updateModuleContent = function (module, newHeader, newContent) {
var moduleWrapper = document.getElementById(module.identifier); var moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper === null) {return;} if (moduleWrapper === null) {
return;
}
var headerWrapper = moduleWrapper.getElementsByClassName("module-header"); var headerWrapper = moduleWrapper.getElementsByClassName("module-header");
var contentWrapper = moduleWrapper.getElementsByClassName("module-content"); var contentWrapper = moduleWrapper.getElementsByClassName("module-content");
@ -222,7 +226,7 @@ var MM = (function() {
* argument speed Number - The speed of the hide animation. * argument speed Number - The speed of the hide animation.
* argument callback function - Called when the animation is done. * argument callback function - Called when the animation is done.
*/ */
var hideModule = function(module, speed, callback, options) { var hideModule = function (module, speed, callback, options) {
options = options || {}; options = options || {};
// set lockString if set in options. // set lockString if set in options.
@ -239,7 +243,7 @@ var MM = (function() {
moduleWrapper.style.opacity = 0; moduleWrapper.style.opacity = 0;
clearTimeout(module.showHideTimer); clearTimeout(module.showHideTimer);
module.showHideTimer = setTimeout(function() { module.showHideTimer = setTimeout(function () {
// To not take up any space, we just make the position absolute. // To not take up any space, we just make the position absolute.
// since it's fade out anyway, we can see it lay above or // since it's fade out anyway, we can see it lay above or
// below other modules. This works way better than adjusting // below other modules. This works way better than adjusting
@ -248,11 +252,15 @@ var MM = (function() {
updateWrapperStates(); updateWrapperStates();
if (typeof callback === "function") { callback(); } if (typeof callback === "function") {
callback();
}
}, speed); }, speed);
} else { } else {
// invoke callback even if no content, issue 1308 // invoke callback even if no content, issue 1308
if (typeof callback === "function") { callback(); } if (typeof callback === "function") {
callback();
}
} }
}; };
@ -263,13 +271,13 @@ var MM = (function() {
* argument speed Number - The speed of the show animation. * argument speed Number - The speed of the show animation.
* argument callback function - Called when the animation is done. * argument callback function - Called when the animation is done.
*/ */
var showModule = function(module, speed, callback, options) { var showModule = function (module, speed, callback, options) {
options = options || {}; options = options || {};
// remove lockString if set in options. // remove lockString if set in options.
if (options.lockString) { if (options.lockString) {
var index = module.lockStrings.indexOf(options.lockString); var index = module.lockStrings.indexOf(options.lockString);
if ( index !== -1) { if (index !== -1) {
module.lockStrings.splice(index, 1); module.lockStrings.splice(index, 1);
} }
} }
@ -302,12 +310,16 @@ var MM = (function() {
moduleWrapper.style.opacity = 1; moduleWrapper.style.opacity = 1;
clearTimeout(module.showHideTimer); clearTimeout(module.showHideTimer);
module.showHideTimer = setTimeout(function() { module.showHideTimer = setTimeout(function () {
if (typeof callback === "function") { callback(); } if (typeof callback === "function") {
callback();
}
}, speed); }, speed);
} else { } else {
// invoke callback // invoke callback
if (typeof callback === "function") { callback(); } if (typeof callback === "function") {
callback();
}
} }
}; };
@ -322,15 +334,15 @@ var MM = (function() {
* an ugly top margin. By using this function, the top bar will be hidden if the * an ugly top margin. By using this function, the top bar will be hidden if the
* update notification is not visible. * update notification is not visible.
*/ */
var updateWrapperStates = function() { 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"]; var positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
positions.forEach(function(position) { positions.forEach(function (position) {
var wrapper = selectWrapper(position); var wrapper = selectWrapper(position);
var moduleWrappers = wrapper.getElementsByClassName("module"); var moduleWrappers = wrapper.getElementsByClassName("module");
var showWrapper = false; var showWrapper = false;
Array.prototype.forEach.call(moduleWrappers, function(moduleWrapper) { Array.prototype.forEach.call(moduleWrappers, function (moduleWrapper) {
if (moduleWrapper.style.position === "" || moduleWrapper.style.position === "static") { if (moduleWrapper.style.position === "" || moduleWrapper.style.position === "static") {
showWrapper = true; showWrapper = true;
} }
@ -343,7 +355,9 @@ var MM = (function() {
/* loadConfig() /* loadConfig()
* Loads the core config and combines it with de system defaults. * Loads the core config and combines it with de system defaults.
*/ */
var loadConfig = function() { var loadConfig = function () {
// FIXME: Think about how to pass config around without breaking tests
/* eslint-disable */
if (typeof config === "undefined") { if (typeof config === "undefined") {
config = defaults; config = defaults;
Log.error("Config file is missing! Please create a config file."); Log.error("Config file is missing! Please create a config file.");
@ -351,6 +365,7 @@ var MM = (function() {
} }
config = Object.assign({}, defaults, config); config = Object.assign({}, defaults, config);
/* eslint-enable */
}; };
/* setSelectionMethodsForModules() /* setSelectionMethodsForModules()
@ -358,8 +373,7 @@ var MM = (function() {
* *
* argument modules array - Array of modules. * argument modules array - Array of modules.
*/ */
var setSelectionMethodsForModules = function(modules) { var setSelectionMethodsForModules = function (modules) {
/* withClass(className) /* withClass(className)
* calls modulesByClass to filter modules with the specified classes. * calls modulesByClass to filter modules with the specified classes.
* *
@ -367,7 +381,7 @@ var MM = (function() {
* *
* return array - Filtered collection of modules. * return array - Filtered collection of modules.
*/ */
var withClass = function(className) { var withClass = function (className) {
return modulesByClass(className, true); return modulesByClass(className, true);
}; };
@ -378,7 +392,7 @@ var MM = (function() {
* *
* return array - Filtered collection of modules. * return array - Filtered collection of modules.
*/ */
var exceptWithClass = function(className) { var exceptWithClass = function (className) {
return modulesByClass(className, false); return modulesByClass(className, false);
}; };
@ -390,13 +404,13 @@ var MM = (function() {
* *
* return array - Filtered collection of modules. * return array - Filtered collection of modules.
*/ */
var modulesByClass = function(className, include) { var modulesByClass = function (className, include) {
var searchClasses = className; var searchClasses = className;
if (typeof className === "string") { if (typeof className === "string") {
searchClasses = className.split(" "); searchClasses = className.split(" ");
} }
var newModules = modules.filter(function(module) { var newModules = modules.filter(function (module) {
var classes = module.data.classes.toLowerCase().split(" "); var classes = module.data.classes.toLowerCase().split(" ");
for (var c in searchClasses) { for (var c in searchClasses) {
@ -420,8 +434,8 @@ var MM = (function() {
* *
* return array - Filtered collection of modules. * return array - Filtered collection of modules.
*/ */
var exceptModule = function(module) { var exceptModule = function (module) {
var newModules = modules.filter(function(mod) { var newModules = modules.filter(function (mod) {
return mod.identifier !== module.identifier; return mod.identifier !== module.identifier;
}); });
@ -434,16 +448,24 @@ var MM = (function() {
* *
* argument callback function - The function to execute with the module as an argument. * argument callback function - The function to execute with the module as an argument.
*/ */
var enumerate = function(callback) { var enumerate = function (callback) {
modules.map(function(module) { modules.map(function (module) {
callback(module); callback(module);
}); });
}; };
if (typeof modules.withClass === "undefined") { Object.defineProperty(modules, "withClass", {value: withClass, enumerable: false}); } if (typeof modules.withClass === "undefined") {
if (typeof modules.exceptWithClass === "undefined") { Object.defineProperty(modules, "exceptWithClass", {value: exceptWithClass, enumerable: false}); } Object.defineProperty(modules, "withClass", { value: withClass, enumerable: false });
if (typeof modules.exceptModule === "undefined") { Object.defineProperty(modules, "exceptModule", {value: exceptModule, enumerable: false}); } }
if (typeof modules.enumerate === "undefined") { Object.defineProperty(modules, "enumerate", {value: enumerate, enumerable: false}); } if (typeof modules.exceptWithClass === "undefined") {
Object.defineProperty(modules, "exceptWithClass", { value: exceptWithClass, enumerable: false });
}
if (typeof modules.exceptModule === "undefined") {
Object.defineProperty(modules, "exceptModule", { value: exceptModule, enumerable: false });
}
if (typeof modules.enumerate === "undefined") {
Object.defineProperty(modules, "enumerate", { value: enumerate, enumerable: false });
}
}; };
return { return {
@ -452,7 +474,7 @@ var MM = (function() {
/* init() /* init()
* Main init method. * Main init method.
*/ */
init: function() { init: function () {
Log.info("Initializing MagicMirror."); Log.info("Initializing MagicMirror.");
loadConfig(); loadConfig();
Translator.loadCoreTranslations(config.language); Translator.loadCoreTranslations(config.language);
@ -464,7 +486,7 @@ var MM = (function() {
* *
* argument moduleObjects array<Module> - All module instances. * argument moduleObjects array<Module> - All module instances.
*/ */
modulesStarted: function(moduleObjects) { modulesStarted: function (moduleObjects) {
modules = []; modules = [];
for (var m in moduleObjects) { for (var m in moduleObjects) {
var module = moduleObjects[m]; var module = moduleObjects[m];
@ -484,7 +506,7 @@ var MM = (function() {
* argument payload mixed - The payload of the notification. * argument payload mixed - The payload of the notification.
* argument sender Module - The module that sent the notification. * argument sender Module - The module that sent the notification.
*/ */
sendNotification: function(notification, payload, sender) { sendNotification: function (notification, payload, sender) {
if (arguments.length < 3) { if (arguments.length < 3) {
Log.error("sendNotification: Missing arguments."); Log.error("sendNotification: Missing arguments.");
return; return;
@ -510,7 +532,7 @@ var MM = (function() {
* argument module Module - The module that needs an update. * argument module Module - The module that needs an update.
* argument speed Number - The number of microseconds for the animation. (optional) * argument speed Number - The number of microseconds for the animation. (optional)
*/ */
updateDom: function(module, speed) { updateDom: function (module, speed) {
if (!(module instanceof Module)) { if (!(module instanceof Module)) {
Log.error("updateDom: Sender should be a module."); Log.error("updateDom: Sender should be a module.");
return; return;
@ -525,7 +547,7 @@ var MM = (function() {
* *
* return array - A collection of all modules currently active. * return array - A collection of all modules currently active.
*/ */
getModules: function() { getModules: function () {
setSelectionMethodsForModules(modules); setSelectionMethodsForModules(modules);
return modules; return modules;
}, },
@ -538,7 +560,7 @@ var MM = (function() {
* argument callback function - Called when the animation is done. * argument callback function - Called when the animation is done.
* argument options object - Optional settings for the hide method. * argument options object - Optional settings for the hide method.
*/ */
hideModule: function(module, speed, callback, options) { hideModule: function (module, speed, callback, options) {
module.hidden = true; module.hidden = true;
hideModule(module, speed, callback, options); hideModule(module, speed, callback, options);
}, },
@ -551,18 +573,17 @@ var MM = (function() {
* argument callback function - Called when the animation is done. * argument callback function - Called when the animation is done.
* argument options object - Optional settings for the hide method. * argument options object - Optional settings for the hide method.
*/ */
showModule: function(module, speed, callback, options) { showModule: function (module, speed, callback, options) {
// do not change module.hidden yet, only if we really show it later // do not change module.hidden yet, only if we really show it later
showModule(module, speed, callback, options); showModule(module, speed, callback, options);
} }
}; };
})(); })();
// Add polyfill for Object.assign. // Add polyfill for Object.assign.
if (typeof Object.assign !== "function") { if (typeof Object.assign !== "function") {
(function() { (function () {
Object.assign = function(target) { Object.assign = function (target) {
"use strict"; "use strict";
if (target === undefined || target === null) { if (target === undefined || target === null) {
throw new TypeError("Cannot convert undefined or null to object"); throw new TypeError("Cannot convert undefined or null to object");

View File

@ -1,15 +1,12 @@
/* global Log, Class, Loader, Class , MM */ /* global Class, cloneObject, Loader, MMSocket, nunjucks, Translator */
/* exported Module */
/* Magic Mirror /* Magic Mirror
* Module Blueprint. * Module Blueprint.
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
var Module = Class.extend({ var Module = Class.extend({
/********************************************************* /*********************************************************
* All methods (and properties) below can be subclassed. * * All methods (and properties) below can be subclassed. *
*********************************************************/ *********************************************************/
@ -82,7 +79,7 @@ var Module = Class.extend({
*/ */
getDom: function () { getDom: function () {
var self = this; var self = this;
return new Promise(function(resolve) { return new Promise(function (resolve) {
var div = document.createElement("div"); var div = document.createElement("div");
var template = self.getTemplate(); var template = self.getTemplate();
var templateData = self.getTemplateData(); var templateData = self.getTemplateData();
@ -128,7 +125,7 @@ var Module = Class.extend({
* return string - The template string of filename. * return string - The template string of filename.
*/ */
getTemplate: function () { getTemplate: function () {
return "<div class=\"normal\">" + this.name + "</div><div class=\"small dimmed\">" + this.identifier + "</div>"; return '<div class="normal">' + this.name + '</div><div class="small dimmed">' + this.identifier + "</div>";
}, },
/* getTemplateData() /* getTemplateData()
@ -163,18 +160,18 @@ var Module = Class.extend({
* @returns Nunjucks Environment * @returns Nunjucks Environment
*/ */
nunjucksEnvironment: function() { nunjucksEnvironment: function () {
if (this._nunjucksEnvironment !== null) { if (this._nunjucksEnvironment !== null) {
return this._nunjucksEnvironment; return this._nunjucksEnvironment;
} }
var self = this; var self = this;
this._nunjucksEnvironment = new nunjucks.Environment(new nunjucks.WebLoader(this.file(""), {async: true}), { this._nunjucksEnvironment = new nunjucks.Environment(new nunjucks.WebLoader(this.file(""), { async: true }), {
trimBlocks: true, trimBlocks: true,
lstripBlocks: true lstripBlocks: true
}); });
this._nunjucksEnvironment.addFilter("translate", function(str) { this._nunjucksEnvironment.addFilter("translate", function (str) {
return self.translate(str); return self.translate(str);
}); });
@ -238,7 +235,7 @@ var Module = Class.extend({
*/ */
socket: function () { socket: function () {
if (typeof this._socket === "undefined") { if (typeof this._socket === "undefined") {
this._socket = this._socket = new MMSocket(this.name); this._socket = new MMSocket(this.name);
} }
var self = this; var self = this;
@ -315,7 +312,9 @@ var Module = Class.extend({
// The variable `first` will contain the first // The variable `first` will contain the first
// defined translation after the following line. // defined translation after the following line.
for (var first in translations) { break; } for (var first in translations) {
break;
}
if (translations) { if (translations) {
var translationFile = translations[lang] || undefined; var translationFile = translations[lang] || undefined;
@ -339,11 +338,11 @@ var Module = Class.extend({
* Request the translation for a given key with optional variables and default value. * Request the translation for a given key with optional variables and default value.
* *
* argument key string - The key of the string to translate * argument key string - The key of the string to translate
* argument defaultValueOrVariables string/object - The default value or variables for translating. (Optional) * argument defaultValueOrVariables string/object - The default value or variables for translating. (Optional)
* argument defaultValue string - The default value with variables. (Optional) * argument defaultValue string - The default value with variables. (Optional)
*/ */
translate: function (key, defaultValueOrVariables, defaultValue) { translate: function (key, defaultValueOrVariables, defaultValue) {
if(typeof defaultValueOrVariables === "object") { if (typeof defaultValueOrVariables === "object") {
return Translator.translate(this, key, defaultValueOrVariables) || defaultValue || ""; return Translator.translate(this, key, defaultValueOrVariables) || defaultValue || "";
} }
return Translator.translate(this, key) || defaultValueOrVariables || ""; return Translator.translate(this, key) || defaultValueOrVariables || "";
@ -388,17 +387,22 @@ var Module = Class.extend({
hide: function (speed, callback, options) { hide: function (speed, callback, options) {
if (typeof callback === "object") { if (typeof callback === "object") {
options = callback; options = callback;
callback = function () { }; callback = function () {};
} }
callback = callback || function () { }; callback = callback || function () {};
options = options || {}; options = options || {};
var self = this; var self = this;
MM.hideModule(self, speed, function () { MM.hideModule(
self.suspend(); self,
callback(); speed,
}, options); function () {
self.suspend();
callback();
},
options
);
}, },
/* showModule(module, speed, callback) /* showModule(module, speed, callback)
@ -411,24 +415,28 @@ var Module = Class.extend({
show: function (speed, callback, options) { show: function (speed, callback, options) {
if (typeof callback === "object") { if (typeof callback === "object") {
options = callback; options = callback;
callback = function () { }; callback = function () {};
} }
callback = callback || function () { }; callback = callback || function () {};
options = options || {}; options = options || {};
var self = this; var self = this;
MM.showModule(this, speed, function () { MM.showModule(
self.resume(); this,
callback; speed,
}, options); function () {
self.resume();
callback;
},
options
);
} }
}); });
Module.definitions = {}; Module.definitions = {};
Module.create = function (name) { Module.create = function (name) {
// Make sure module definition is available. // Make sure module definition is available.
if (!Module.definitions[name]) { if (!Module.definitions[name]) {
return; return;
@ -444,11 +452,11 @@ Module.create = function (name) {
}; };
/* cmpVersions(a,b) /* cmpVersions(a,b)
* Compare two semantic version numbers and return the difference. * Compare two semantic version numbers and return the difference.
* *
* argument a string - Version number a. * argument a string - Version number a.
* argument a string - Version number b. * argument a string - Version number b.
*/ */
function cmpVersions(a, b) { function cmpVersions(a, b) {
var i, diff; var i, diff;
var regExStrip0 = /(\.0+)+$/; var regExStrip0 = /(\.0+)+$/;
@ -466,10 +474,9 @@ function cmpVersions(a, b) {
} }
Module.register = function (name, moduleDefinition) { Module.register = function (name, moduleDefinition) {
if (moduleDefinition.requiresVersion) { if (moduleDefinition.requiresVersion) {
Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + version); Log.log("Check MagicMirror version for module '" + name + "' - Minimum version: " + moduleDefinition.requiresVersion + " - Current version: " + window.version);
if (cmpVersions(version, moduleDefinition.requiresVersion) >= 0) { if (cmpVersions(window.version, moduleDefinition.requiresVersion) >= 0) {
Log.log("Version is ok!"); Log.log("Version is ok!");
} else { } else {
Log.log("Version is incorrect. Skip module: '" + name + "'"); Log.log("Version is incorrect. Skip module: '" + name + "'");

View File

@ -1,25 +1,23 @@
/* Magic Mirror /* Magic Mirror
* Node Helper Superclass * Node Helper Superclass
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
const Class = require("./class.js");
const express = require("express");
var Class = require("./class.js"); var NodeHelper = Class.extend({
var express = require("express"); init: function () {
var path = require("path");
NodeHelper = Class.extend({
init: function() {
console.log("Initializing new module helper ..."); console.log("Initializing new module helper ...");
}, },
loaded: function(callback) { loaded: function (callback) {
console.log("Module helper loaded: " + this.name); console.log("Module helper loaded: " + this.name);
callback(); callback();
}, },
start: function() { start: function () {
console.log("Starting module helper: " + this.name); console.log("Starting module helper: " + this.name);
}, },
@ -29,7 +27,7 @@ NodeHelper = Class.extend({
* gracefully exit the module. * gracefully exit the module.
* *
*/ */
stop: function() { stop: function () {
console.log("Stopping module helper: " + this.name); console.log("Stopping module helper: " + this.name);
}, },
@ -39,7 +37,7 @@ NodeHelper = Class.extend({
* argument notification string - The identifier of the notification. * argument notification string - The identifier of the notification.
* argument payload mixed - The payload of the notification. * argument payload mixed - The payload of the notification.
*/ */
socketNotificationReceived: function(notification, payload) { socketNotificationReceived: function (notification, payload) {
console.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload); console.log(this.name + " received a socket notification: " + notification + " - Payload: " + payload);
}, },
@ -48,7 +46,7 @@ NodeHelper = Class.extend({
* *
* argument name string - Module name. * argument name string - Module name.
*/ */
setName: function(name) { setName: function (name) {
this.name = name; this.name = name;
}, },
@ -57,7 +55,7 @@ NodeHelper = Class.extend({
* *
* argument path string - Module path. * argument path string - Module path.
*/ */
setPath: function(path) { setPath: function (path) {
this.path = path; this.path = path;
}, },
@ -67,7 +65,7 @@ NodeHelper = Class.extend({
* argument notification string - The identifier of the notification. * argument notification string - The identifier of the notification.
* argument payload mixed - The payload of the notification. * argument payload mixed - The payload of the notification.
*/ */
sendSocketNotification: function(notification, payload) { sendSocketNotification: function (notification, payload) {
this.io.of(this.name).emit(notification, payload); this.io.of(this.name).emit(notification, payload);
}, },
@ -77,7 +75,7 @@ NodeHelper = Class.extend({
* *
* argument app Express app - The Express app object. * argument app Express app - The Express app object.
*/ */
setExpressApp: function(app) { setExpressApp: function (app) {
this.expressApp = app; this.expressApp = app;
var publicPath = this.path + "/public"; var publicPath = this.path + "/public";
@ -90,16 +88,16 @@ NodeHelper = Class.extend({
* *
* argument io Socket.io - The Socket io object. * argument io Socket.io - The Socket io object.
*/ */
setSocketIO: function(io) { setSocketIO: function (io) {
var self = this; var self = this;
self.io = io; self.io = io;
console.log("Connecting socket for: " + this.name); console.log("Connecting socket for: " + this.name);
var namespace = this.name; var namespace = this.name;
io.of(namespace).on("connection", function(socket) { io.of(namespace).on("connection", function (socket) {
// add a catch all event. // add a catch all event.
var onevent = socket.onevent; var onevent = socket.onevent;
socket.onevent = function(packet) { socket.onevent = function (packet) {
var args = packet.data || []; var args = packet.data || [];
onevent.call(this, packet); // original call onevent.call(this, packet); // original call
packet.data = ["*"].concat(args); packet.data = ["*"].concat(args);
@ -107,19 +105,21 @@ NodeHelper = Class.extend({
}; };
// register catch all. // register catch all.
socket.on("*", function(notification, payload) { socket.on("*", function (notification, payload) {
if (notification !== "*") { if (notification !== "*") {
//console.log('received message in namespace: ' + namespace); //console.log('received message in namespace: ' + namespace);
self.socketNotificationReceived(notification, payload); self.socketNotificationReceived(notification, payload);
} }
}); });
}); });
} }
}); });
NodeHelper.create = function(moduleDefinition) { NodeHelper.create = function (moduleDefinition) {
return NodeHelper.extend(moduleDefinition); return NodeHelper.extend(moduleDefinition);
}; };
module.exports = NodeHelper; /*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = NodeHelper;
}

View File

@ -1,7 +1,7 @@
/* Magic Mirror /* Magic Mirror
* Server * Server
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
@ -13,21 +13,20 @@ var fs = require("fs");
var helmet = require("helmet"); var helmet = require("helmet");
var Utils = require(__dirname + "/utils.js"); var Utils = require(__dirname + "/utils.js");
var Server = function(config, callback) { var Server = function (config, callback) {
var port = config.port; var port = config.port;
if (process.env.MM_PORT) { if (process.env.MM_PORT) {
port = process.env.MM_PORT; port = process.env.MM_PORT;
} }
var server = null; var server = null;
if(config.useHttps){ if (config.useHttps) {
var options = { var options = {
key: fs.readFileSync(config.httpsPrivateKey), key: fs.readFileSync(config.httpsPrivateKey),
cert: fs.readFileSync(config.httpsCertificate) cert: fs.readFileSync(config.httpsCertificate)
}; };
server = require("https").Server(options, app); server = require("https").Server(options, app);
}else{ } else {
server = require("http").Server(app); server = require("http").Server(app);
} }
var io = require("socket.io")(server); var io = require("socket.io")(server);
@ -40,8 +39,8 @@ var Server = function(config, callback) {
console.info(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs")); console.info(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs"));
} }
app.use(function(req, res, next) { app.use(function (req, res, next) {
var result = ipfilter(config.ipWhitelist, {mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false})(req, res, function(err) { var result = ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) {
if (err === undefined) { if (err === undefined) {
return next(); return next();
} }
@ -59,21 +58,21 @@ var Server = function(config, callback) {
app.use(directory, express.static(path.resolve(global.root_path + directory))); app.use(directory, express.static(path.resolve(global.root_path + directory)));
} }
app.get("/version", function(req,res) { app.get("/version", function (req, res) {
res.send(global.version); res.send(global.version);
}); });
app.get("/config", function(req,res) { app.get("/config", function (req, res) {
res.send(config); res.send(config);
}); });
app.get("/", function(req, res) { app.get("/", function (req, res) {
var html = fs.readFileSync(path.resolve(global.root_path + "/index.html"), {encoding: "utf8"}); var html = fs.readFileSync(path.resolve(global.root_path + "/index.html"), { encoding: "utf8" });
html = html.replace("#VERSION#", global.version); html = html.replace("#VERSION#", global.version);
configFile = "config/config.js"; var configFile = "config/config.js";
if (typeof(global.configuration_file) !== "undefined") { if (typeof global.configuration_file !== "undefined") {
configFile = global.configuration_file; configFile = global.configuration_file;
} }
html = html.replace("#CONFIG_FILE#", configFile); html = html.replace("#CONFIG_FILE#", configFile);

View File

@ -1,35 +0,0 @@
/* exported Log */
/* Magic Mirror
* Socket Connection
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
var MMSocket = function(moduleName) {
var self = this;
if (typeof moduleName !== "string") {
throw new Error("Please set the module name for the MMSocket.");
}
self.moduleName = moduleName;
self.socket = io("http://localhost:8080");
self.socket.on("notification", function(data) {
MM.sendNotification(data.notification, data.payload, Socket);
});
return {
sendMessage: function(notification, payload, sender) {
Log.log("Send socket message: " + notification);
self.socket.emit("notification", {
notification: notification,
sender: sender,
payload: payload
});
}
};
};

View File

@ -1,4 +1,12 @@
var MMSocket = function(moduleName) { /* global io */
/* Magic Mirror
* TODO add description
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
var MMSocket = function (moduleName) {
var self = this; var self = this;
if (typeof moduleName !== "string") { if (typeof moduleName !== "string") {
@ -8,13 +16,17 @@ var MMSocket = function(moduleName) {
self.moduleName = moduleName; self.moduleName = moduleName;
// Private Methods // Private Methods
var base = "/";
if (typeof config !== "undefined" && typeof config.basePath !== "undefined") {
base = config.basePath;
}
self.socket = io("/" + self.moduleName, { self.socket = io("/" + self.moduleName, {
path: window.location.pathname + "socket.io" path: base + "socket.io"
}); });
var notificationCallback = function() {}; var notificationCallback = function () {};
var onevent = self.socket.onevent; var onevent = self.socket.onevent;
self.socket.onevent = function(packet) { self.socket.onevent = function (packet) {
var args = packet.data || []; var args = packet.data || [];
onevent.call(this, packet); // original call onevent.call(this, packet); // original call
packet.data = ["*"].concat(args); packet.data = ["*"].concat(args);
@ -22,18 +34,18 @@ var MMSocket = function(moduleName) {
}; };
// register catch all. // register catch all.
self.socket.on("*", function(notification, payload) { self.socket.on("*", function (notification, payload) {
if (notification !== "*") { if (notification !== "*") {
notificationCallback(notification, payload); notificationCallback(notification, payload);
} }
}); });
// Public Methods // Public Methods
this.setNotificationCallback = function(callback) { this.setNotificationCallback = function (callback) {
notificationCallback = callback; notificationCallback = callback;
}; };
this.sendNotification = function(notification, payload) { this.sendNotification = function (notification, payload) {
if (typeof payload === "undefined") { if (typeof payload === "undefined") {
payload = {}; payload = {};
} }

View File

@ -1,12 +1,12 @@
/* exported Translator */ /* global translations */
/* Magic Mirror /* Magic Mirror
* Translator (l10n) * Translator (l10n)
* *
* By Christopher Fenner http://github.com/CFenner * By Christopher Fenner https://github.com/CFenner
* MIT Licensed. * MIT Licensed.
*/ */
var Translator = (function() { var Translator = (function () {
/* loadJSON(file, callback) /* loadJSON(file, callback)
* Load a JSON file via XHR. * Load a JSON file via XHR.
* *
@ -61,7 +61,7 @@ var Translator = (function() {
currentChar = str[i]; currentChar = str[i];
nextChar = str[i + 1]; nextChar = str[i + 1];
if (!insideComment && currentChar === "\"") { if (!insideComment && currentChar === '"') {
var escaped = str[i - 1] === "\\" && str[i - 2] !== "\\"; var escaped = str[i - 1] === "\\" && str[i - 2] !== "\\";
if (!escaped) { if (!escaped) {
insideString = !insideString; insideString = !insideString;
@ -118,7 +118,7 @@ var Translator = (function() {
* argument key string - The key of the text to translate. * argument key string - The key of the text to translate.
* argument variables - The variables to use within the translation template (optional) * argument variables - The variables to use within the translation template (optional)
*/ */
translate: function(module, key, variables) { translate: function (module, key, variables) {
variables = variables || {}; //Empty object by default variables = variables || {}; //Empty object by default
// Combines template and variables like: // Combines template and variables like:
@ -126,18 +126,18 @@ var Translator = (function() {
// variables: {timeToWait: "2 hours", work: "painting"} // variables: {timeToWait: "2 hours", work: "painting"}
// to: "Please wait for 2 hours before continuing with painting." // to: "Please wait for 2 hours before continuing with painting."
function createStringFromTemplate(template, variables) { function createStringFromTemplate(template, variables) {
if(Object.prototype.toString.call(template) !== "[object String]") { if (Object.prototype.toString.call(template) !== "[object String]") {
return template; return template;
} }
if(variables.fallback && !template.match(new RegExp("\{.+\}"))) { if (variables.fallback && !template.match(new RegExp("{.+}"))) {
template = variables.fallback; template = variables.fallback;
} }
return template.replace(new RegExp("\{([^\}]+)\}", "g"), function(_unused, varName){ return template.replace(new RegExp("{([^}]+)}", "g"), function (_unused, varName) {
return variables[varName] || "{"+varName+"}"; return variables[varName] || "{" + varName + "}";
}); });
} }
if(this.translations[module.name] && key in this.translations[module.name]) { if (this.translations[module.name] && key in this.translations[module.name]) {
// Log.log("Got translation for " + key + " from module translation: "); // Log.log("Got translation for " + key + " from module translation: ");
return createStringFromTemplate(this.translations[module.name][key], variables); return createStringFromTemplate(this.translations[module.name][key], variables);
} }
@ -168,7 +168,7 @@ var Translator = (function() {
* argument isFallback boolean - Flag to indicate fallback translations. * argument isFallback boolean - Flag to indicate fallback translations.
* argument callback function - Function called when done. * argument callback function - Function called when done.
*/ */
load: function(module, file, isFallback, callback) { load: function (module, file, isFallback, callback) {
if (!isFallback) { if (!isFallback) {
Log.log(module.name + " - Load translation: " + file); Log.log(module.name + " - Load translation: " + file);
} else { } else {
@ -176,8 +176,8 @@ var Translator = (function() {
} }
var self = this; var self = this;
if(!this.translationsFallback[module.name]) { if (!this.translationsFallback[module.name]) {
loadJSON(module.file(file), function(json) { loadJSON(module.file(file), function (json) {
if (!isFallback) { if (!isFallback) {
self.translations[module.name] = json; self.translations[module.name] = json;
} else { } else {
@ -195,12 +195,12 @@ var Translator = (function() {
* *
* argument lang String - The language identifier of the core language. * argument lang String - The language identifier of the core language.
*/ */
loadCoreTranslations: function(lang) { loadCoreTranslations: function (lang) {
var self = this; var self = this;
if (lang in translations) { if (lang in translations) {
Log.log("Loading core translation file: " + translations[lang]); Log.log("Loading core translation file: " + translations[lang]);
loadJSON(translations[lang], function(translations) { loadJSON(translations[lang], function (translations) {
self.coreTranslations = translations; self.coreTranslations = translations;
}); });
} else { } else {
@ -214,19 +214,21 @@ var Translator = (function() {
* Load the core translations fallback. * Load the core translations fallback.
* The first language defined in translations.js will be used. * The first language defined in translations.js will be used.
*/ */
loadCoreTranslationsFallback: function() { loadCoreTranslationsFallback: function () {
var self = this; var self = this;
// The variable `first` will contain the first // The variable `first` will contain the first
// defined translation after the following line. // defined translation after the following line.
for (var first in translations) {break;} for (var first in translations) {
break;
}
if (first) { if (first) {
Log.log("Loading core translation fallback file: " + translations[first]); Log.log("Loading core translation fallback file: " + translations[first]);
loadJSON(translations[first], function(translations) { loadJSON(translations[first], function (translations) {
self.coreTranslationsFallback = translations; self.coreTranslationsFallback = translations;
}); });
} }
}, }
}; };
})(); })();

View File

@ -1,11 +1,9 @@
/* exported Utils */
/* Magic Mirror /* Magic Mirror
* Utils * Utils
* *
* By Rodrigo Ramírez Norambuena https://rodrigoramirez.com * By Rodrigo Ramírez Norambuena https://rodrigoramirez.com
* MIT Licensed. * MIT Licensed.
*/ */
var colors = require("colors/safe"); var colors = require("colors/safe");
var Utils = { var Utils = {
@ -16,4 +14,6 @@ var Utils = {
} }
}; };
if (typeof module !== "undefined") {module.exports = Utils;} if (typeof module !== "undefined") {
module.exports = Utils;
}

View File

@ -6,8 +6,5 @@
"module": "commonjs", "module": "commonjs",
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true
}, },
"exclude": [ "exclude": ["modules", "node_modules"]
"modules",
"node_modules"
]
} }

View File

@ -1,4 +1,5 @@
# Module: Alert # Module: Alert
The alert module is one of the default modules of the MagicMirror. This module displays notifications from other modules. The alert module is one of the default modules of the MagicMirror. This module displays notifications from other modules.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/alert.html). For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/alert.html).

View File

@ -1,13 +1,12 @@
/* global Module */ /* global NotificationFx */
/* Magic Mirror /* Magic Mirror
* Module: alert * Module: alert
* *
* By Paul-Vincent Roll http://paulvincentroll.com * By Paul-Vincent Roll https://paulvincentroll.com/
* MIT Licensed. * MIT Licensed.
*/ */
Module.register("alert", {
Module.register("alert",{
defaults: { defaults: {
// scale|slide|genie|jelly|flip|bouncyflip|exploader // scale|slide|genie|jelly|flip|bouncyflip|exploader
effect: "slide", effect: "slide",
@ -18,31 +17,33 @@ Module.register("alert",{
//Position //Position
position: "center", position: "center",
//shown at startup //shown at startup
welcome_message: false, welcome_message: false
}, },
getScripts: function() { getScripts: function () {
return ["classie.js", "modernizr.custom.js", "notificationFx.js"]; return ["notificationFx.js"];
}, },
getStyles: function() { getStyles: function () {
return ["ns-default.css", "font-awesome.css"]; return ["notificationFx.css", "font-awesome.css"];
}, },
// Define required translations. // Define required translations.
getTranslations: function() { getTranslations: function () {
return { return {
en: "translations/en.json", en: "translations/en.json",
de: "translations/de.json", de: "translations/de.json",
nl: "translations/nl.json", nl: "translations/nl.json"
}; };
}, },
show_notification: function(message) { show_notification: function (message) {
if (this.config.effect === "slide") {this.config.effect = this.config.effect + "-" + this.config.position;} if (this.config.effect === "slide") {
msg = ""; this.config.effect = this.config.effect + "-" + this.config.position;
}
let msg = "";
if (message.title) { if (message.title) {
msg += "<span class='thin dimmed medium'>" + message.title + "</span>"; msg += "<span class='thin dimmed medium'>" + message.title + "</span>";
} }
if (message.message){ if (message.message) {
if (msg !== ""){ if (msg !== "") {
msg+= "<br />"; msg += "<br />";
} }
msg += "<span class='light bright small'>" + message.message + "</span>"; msg += "<span class='light bright small'>" + message.message + "</span>";
} }
@ -54,23 +55,26 @@ Module.register("alert",{
ttl: message.timer !== undefined ? message.timer : this.config.display_time ttl: message.timer !== undefined ? message.timer : this.config.display_time
}).show(); }).show();
}, },
show_alert: function(params, sender) { show_alert: function (params, sender) {
var self = this; let image = "";
//Set standard params if not provided by module //Set standard params if not provided by module
if (typeof params.timer === "undefined") { params.timer = null; } if (typeof params.timer === "undefined") {
if (typeof params.imageHeight === "undefined") { params.imageHeight = "80px"; } params.timer = null;
}
if (typeof params.imageHeight === "undefined") {
params.imageHeight = "80px";
}
if (typeof params.imageUrl === "undefined" && typeof params.imageFA === "undefined") { if (typeof params.imageUrl === "undefined" && typeof params.imageFA === "undefined") {
params.imageUrl = null; params.imageUrl = null;
image = ""; } else if (typeof params.imageFA === "undefined") {
} else if (typeof params.imageFA === "undefined"){ image = "<img src='" + params.imageUrl.toString() + "' height='" + params.imageHeight.toString() + "' style='margin-bottom: 10px;'/><br />";
image = "<img src='" + (params.imageUrl).toString() + "' height='" + (params.imageHeight).toString() + "' style='margin-bottom: 10px;'/><br />"; } else if (typeof params.imageUrl === "undefined") {
} else if (typeof params.imageUrl === "undefined"){ image = "<span class='bright " + "fa fa-" + params.imageFA + "' style='margin-bottom: 10px;font-size:" + params.imageHeight.toString() + ";'/></span><br />";
image = "<span class='bright " + "fa fa-" + params.imageFA + "' style='margin-bottom: 10px;font-size:" + (params.imageHeight).toString() + ";'/></span><br />";
} }
//Create overlay //Create overlay
var overlay = document.createElement("div"); const overlay = document.createElement("div");
overlay.id = "overlay"; overlay.id = "overlay";
overlay.innerHTML += "<div class=\"black_overlay\"></div>"; overlay.innerHTML += '<div class="black_overlay"></div>';
document.body.insertBefore(overlay, document.body.firstChild); document.body.insertBefore(overlay, document.body.firstChild);
//If module already has an open alert close it //If module already has an open alert close it
@ -79,12 +83,12 @@ Module.register("alert",{
} }
//Display title and message only if they are provided in notification parameters //Display title and message only if they are provided in notification parameters
var message = ""; let message = "";
if (params.title) { if (params.title) {
message += "<span class='light dimmed medium'>" + params.title + "</span>"; message += "<span class='light dimmed medium'>" + params.title + "</span>";
} }
if (params.message) { if (params.message) {
if (message !== ""){ if (message !== "") {
message += "<br />"; message += "<br />";
} }
@ -102,34 +106,40 @@ Module.register("alert",{
this.alerts[sender.name].show(); this.alerts[sender.name].show();
//Add timer to dismiss alert and overlay //Add timer to dismiss alert and overlay
if (params.timer) { if (params.timer) {
setTimeout(function() { setTimeout(() => {
self.hide_alert(sender); this.hide_alert(sender);
}, params.timer); }, params.timer);
} }
}, },
hide_alert: function(sender) { hide_alert: function (sender) {
//Dismiss alert and remove from this.alerts //Dismiss alert and remove from this.alerts
if (this.alerts[sender.name]) { if (this.alerts[sender.name]) {
this.alerts[sender.name].dismiss(); this.alerts[sender.name].dismiss();
this.alerts[sender.name] = null; this.alerts[sender.name] = null;
//Remove overlay //Remove overlay
var overlay = document.getElementById("overlay"); const overlay = document.getElementById("overlay");
overlay.parentNode.removeChild(overlay); overlay.parentNode.removeChild(overlay);
} }
}, },
setPosition: function(pos) { setPosition: function (pos) {
//Add css to body depending on the set position for notifications //Add css to body depending on the set position for notifications
var sheet = document.createElement("style"); const sheet = document.createElement("style");
if (pos === "center") {sheet.innerHTML = ".ns-box {margin-left: auto; margin-right: auto;text-align: center;}";} if (pos === "center") {
if (pos === "right") {sheet.innerHTML = ".ns-box {margin-left: auto;text-align: right;}";} sheet.innerHTML = ".ns-box {margin-left: auto; margin-right: auto;text-align: center;}";
if (pos === "left") {sheet.innerHTML = ".ns-box {margin-right: auto;text-align: left;}";} }
if (pos === "right") {
sheet.innerHTML = ".ns-box {margin-left: auto;text-align: right;}";
}
if (pos === "left") {
sheet.innerHTML = ".ns-box {margin-right: auto;text-align: left;}";
}
document.body.appendChild(sheet); document.body.appendChild(sheet);
}, },
notificationReceived: function(notification, payload, sender) { notificationReceived: function (notification, payload, sender) {
if (notification === "SHOW_ALERT") { if (notification === "SHOW_ALERT") {
if (typeof payload.type === "undefined") { payload.type = "alert"; } if (typeof payload.type === "undefined") {
payload.type = "alert";
}
if (payload.type === "alert") { if (payload.type === "alert") {
this.show_alert(payload, sender); this.show_alert(payload, sender);
} else if (payload.type === "notification") { } else if (payload.type === "notification") {
@ -139,15 +149,14 @@ Module.register("alert",{
this.hide_alert(sender); this.hide_alert(sender);
} }
}, },
start: function() { start: function () {
this.alerts = {}; this.alerts = {};
this.setPosition(this.config.position); this.setPosition(this.config.position);
if (this.config.welcome_message) { if (this.config.welcome_message) {
if (this.config.welcome_message === true){ if (this.config.welcome_message === true) {
this.show_notification({title: this.translate("sysTitle"), message: this.translate("welcome")}); this.show_notification({ title: this.translate("sysTitle"), message: this.translate("welcome") });
} } else {
else{ this.show_notification({ title: this.translate("sysTitle"), message: this.config.welcome_message });
this.show_notification({title: this.translate("sysTitle"), message: this.config.welcome_message});
} }
} }
Log.info("Starting module: " + this.name); Log.info("Starting module: " + this.name);

View File

@ -1,79 +0,0 @@
/*!
* classie - class helper functions
* from bonzo https://github.com/ded/bonzo
*
* classie.has( elem, 'my-class' ) -> true/false
* classie.add( elem, 'my-new-class' )
* classie.remove( elem, 'my-unwanted-class' )
* classie.toggle( elem, 'my-class' )
*/
// jscs:disable
/*jshint browser: true, strict: true, undef: true */
/*global define: false */
(function(window) {
"use strict";
// class helper functions from bonzo https://github.com/ded/bonzo
function classReg(className) {
return new RegExp("(^|\\s+)" + className + "(\\s+|$)");
}
// classList support for class management
// altho to be fair, the api sucks because it won't accept multiple classes at once
var hasClass, addClass, removeClass;
if ("classList" in document.documentElement) {
hasClass = function(elem, c) {
return elem.classList.contains(c);
};
addClass = function(elem, c) {
elem.classList.add(c);
};
removeClass = function(elem, c) {
elem.classList.remove(c);
};
} else {
hasClass = function(elem, c) {
return classReg(c).test(elem.className);
};
addClass = function(elem, c) {
if (!hasClass(elem, c)) {
elem.className = elem.className + " " + c;
}
};
removeClass = function(elem, c) {
elem.className = elem.className.replace(classReg(c), " ");
};
}
function toggleClass(elem, c) {
var fn = hasClass(elem, c) ? removeClass : addClass;
fn(elem, c);
}
var classie = {
// full names
hasClass: hasClass,
addClass: addClass,
removeClass: removeClass,
toggleClass: toggleClass,
// short names
has: hasClass,
add: addClass,
remove: removeClass,
toggle: toggleClass
};
// transport
if (typeof define === "function" && define.amd) {
// AMD
define(classie);
} else {
// browser global
window.classie = classie;
}
})(window);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,933 @@
/* Based on work by https://tympanus.net/codrops/licensing/ */
.ns-box {
background-color: rgba(0, 0, 0, 0.93);
padding: 17px;
line-height: 1.4;
margin-bottom: 10px;
z-index: 1;
color: black;
font-size: 70%;
position: relative;
display: table;
word-wrap: break-word;
max-width: 100%;
border-width: 1px;
border-radius: 5px;
border-style: solid;
border-color: #666;
}
.ns-alert {
border-style: solid;
border-color: #fff;
padding: 17px;
line-height: 1.4;
margin-bottom: 10px;
z-index: 3;
color: white;
font-size: 70%;
position: fixed;
text-align: center;
right: 0;
left: 0;
margin-right: auto;
margin-left: auto;
top: 40%;
width: 40%;
height: auto;
word-wrap: break-word;
border-radius: 20px;
}
.black_overlay {
position: fixed;
z-index: 2;
background-color: rgba(0, 0, 0, 0.93);
width: 100%;
height: 100%;
}
[class^="ns-effect-"].ns-growl.ns-hide,
[class*=" ns-effect-"].ns-growl.ns-hide {
animation-direction: reverse;
}
.ns-effect-flip {
transform-origin: 50% 100%;
backface-visibility: hidden;
}
.ns-effect-flip.ns-show,
.ns-effect-flip.ns-hide {
animation-name: animFlipFront;
animation-duration: 0.3s;
}
.ns-effect-flip.ns-hide {
animation-name: animFlipBack;
}
@keyframes animFlipFront {
0% {
transform: perspective(1000px) rotate3d(1, 0, 0, -90deg);
}
100% {
transform: perspective(1000px);
}
}
@keyframes animFlipBack {
0% {
transform: perspective(1000px) rotate3d(1, 0, 0, 90deg);
}
100% {
transform: perspective(1000px);
}
}
.ns-effect-bouncyflip.ns-show,
.ns-effect-bouncyflip.ns-hide {
animation-name: flipInX;
animation-duration: 0.8s;
}
@keyframes flipInX {
0% {
transform: perspective(400px) rotate3d(1, 0, 0, -90deg);
transition-timing-function: ease-in;
}
40% {
transform: perspective(400px) rotate3d(1, 0, 0, 20deg);
transition-timing-function: ease-out;
}
60% {
transform: perspective(400px) rotate3d(1, 0, 0, -10deg);
transition-timing-function: ease-in;
opacity: 1;
}
80% {
transform: perspective(400px) rotate3d(1, 0, 0, 5deg);
transition-timing-function: ease-out;
}
100% {
transform: perspective(400px);
}
}
.ns-effect-bouncyflip.ns-hide {
animation-name: flipInXSimple;
animation-duration: 0.3s;
}
@keyframes flipInXSimple {
0% {
transform: perspective(400px) rotate3d(1, 0, 0, -90deg);
transition-timing-function: ease-in;
}
100% {
transform: perspective(400px);
}
}
.ns-effect-exploader {
transform-origin: 0 0;
}
.ns-effect-exploader p {
padding: 0.25em 2em 0.25em 3em;
}
.ns-effect-exploader.ns-show {
animation-name: animLoad;
animation-duration: 1s;
}
@keyframes animLoad {
0% {
opacity: 1;
transform: scale3d(0, 0.3, 1);
}
100% {
opacity: 1;
transform: scale3d(1, 1, 1);
}
}
.ns-effect-exploader.ns-hide {
animation-name: animFade;
animation-duration: 0.3s;
}
.ns-effect-exploader.ns-show .ns-box-inner,
.ns-effect-exploader.ns-show .ns-close {
animation-fill-mode: both;
animation-duration: 0.3s;
animation-delay: 0.6s;
}
.ns-effect-exploader.ns-show .ns-close {
animation-name: animFade;
}
.ns-effect-exploader.ns-show .ns-box-inner {
animation-name: animFadeMove;
animation-timing-function: ease-out;
}
@keyframes animFadeMove {
0% {
opacity: 0;
transform: translate3d(0, 10px, 0);
}
100% {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@keyframes animFade {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.ns-effect-scale.ns-show,
.ns-effect-scale.ns-hide {
animation-name: animScale;
animation-duration: 0.25s;
}
@keyframes animScale {
0% {
opacity: 0;
transform: translate3d(0, 40px, 0) scale3d(0.1, 0.6, 1);
}
100% {
opacity: 1;
transform: translate3d(0, 0, 0) scale3d(1, 1, 1);
}
}
.ns-effect-jelly.ns-show {
animation-name: animJelly;
animation-duration: 1s;
animation-timing-function: linear;
}
.ns-effect-jelly.ns-hide {
animation-name: animFade;
animation-duration: 0.3s;
}
@keyframes animFade {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes animJelly {
0% {
transform: matrix3d(0.7, 0, 0, 0, 0, 0.7, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
2.083333% {
transform: matrix3d(0.75266, 0, 0, 0, 0, 0.76342, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
4.166667% {
transform: matrix3d(0.81071, 0, 0, 0, 0, 0.84545, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
6.25% {
transform: matrix3d(0.86808, 0, 0, 0, 0, 0.9286, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
8.333333% {
transform: matrix3d(0.92038, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
10.416667% {
transform: matrix3d(0.96482, 0, 0, 0, 0, 1.05202, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
12.5% {
transform: matrix3d(1, 0, 0, 0, 0, 1.08204, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
14.583333% {
transform: matrix3d(1.02563, 0, 0, 0, 0, 1.09149, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
16.666667% {
transform: matrix3d(1.04227, 0, 0, 0, 0, 1.08453, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
18.75% {
transform: matrix3d(1.05102, 0, 0, 0, 0, 1.06666, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
20.833333% {
transform: matrix3d(1.05334, 0, 0, 0, 0, 1.04355, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
22.916667% {
transform: matrix3d(1.05078, 0, 0, 0, 0, 1.02012, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
25% {
transform: matrix3d(1.04487, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
27.083333% {
transform: matrix3d(1.03699, 0, 0, 0, 0, 0.98534, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
29.166667% {
transform: matrix3d(1.02831, 0, 0, 0, 0, 0.97688, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
31.25% {
transform: matrix3d(1.01973, 0, 0, 0, 0, 0.97422, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
33.333333% {
transform: matrix3d(1.01191, 0, 0, 0, 0, 0.97618, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
35.416667% {
transform: matrix3d(1.00526, 0, 0, 0, 0, 0.98122, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
37.5% {
transform: matrix3d(1, 0, 0, 0, 0, 0.98773, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
39.583333% {
transform: matrix3d(0.99617, 0, 0, 0, 0, 0.99433, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
41.666667% {
transform: matrix3d(0.99368, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
43.75% {
transform: matrix3d(0.99237, 0, 0, 0, 0, 1.00413, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
45.833333% {
transform: matrix3d(0.99202, 0, 0, 0, 0, 1.00651, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
47.916667% {
transform: matrix3d(0.99241, 0, 0, 0, 0, 1.00726, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
50% {
transform: matrix3d(0.99329, 0, 0, 0, 0, 1.00671, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
52.083333% {
transform: matrix3d(0.99447, 0, 0, 0, 0, 1.00529, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
54.166667% {
transform: matrix3d(0.99577, 0, 0, 0, 0, 1.00346, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
56.25% {
transform: matrix3d(0.99705, 0, 0, 0, 0, 1.0016, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
58.333333% {
transform: matrix3d(0.99822, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
60.416667% {
transform: matrix3d(0.99921, 0, 0, 0, 0, 0.99884, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
62.5% {
transform: matrix3d(1, 0, 0, 0, 0, 0.99816, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
64.583333% {
transform: matrix3d(1.00057, 0, 0, 0, 0, 0.99795, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
66.666667% {
transform: matrix3d(1.00095, 0, 0, 0, 0, 0.99811, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
68.75% {
transform: matrix3d(1.00114, 0, 0, 0, 0, 0.99851, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
70.833333% {
transform: matrix3d(1.00119, 0, 0, 0, 0, 0.99903, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
72.916667% {
transform: matrix3d(1.00114, 0, 0, 0, 0, 0.99955, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
75% {
transform: matrix3d(1.001, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
77.083333% {
transform: matrix3d(1.00083, 0, 0, 0, 0, 1.00033, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
79.166667% {
transform: matrix3d(1.00063, 0, 0, 0, 0, 1.00052, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
81.25% {
transform: matrix3d(1.00044, 0, 0, 0, 0, 1.00058, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
83.333333% {
transform: matrix3d(1.00027, 0, 0, 0, 0, 1.00053, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
85.416667% {
transform: matrix3d(1.00012, 0, 0, 0, 0, 1.00042, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
87.5% {
transform: matrix3d(1, 0, 0, 0, 0, 1.00027, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
89.583333% {
transform: matrix3d(0.99991, 0, 0, 0, 0, 1.00013, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
91.666667% {
transform: matrix3d(0.99986, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
93.75% {
transform: matrix3d(0.99983, 0, 0, 0, 0, 0.99991, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
95.833333% {
transform: matrix3d(0.99982, 0, 0, 0, 0, 0.99985, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
97.916667% {
transform: matrix3d(0.99983, 0, 0, 0, 0, 0.99984, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
100% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
}
.ns-effect-slide-left.ns-show {
animation-name: animSlideElasticLeft;
animation-duration: 1s;
animation-timing-function: linear;
}
@keyframes animSlideElasticLeft {
0% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -1000, 0, 0, 1);
}
1.666667% {
transform: matrix3d(1.92933, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -739.26805, 0, 0, 1);
}
3.333333% {
transform: matrix3d(1.96989, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -521.82545, 0, 0, 1);
}
5% {
transform: matrix3d(1.70901, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -349.26115, 0, 0, 1);
}
6.666667% {
transform: matrix3d(1.4235, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -218.3238, 0, 0, 1);
}
8.333333% {
transform: matrix3d(1.21065, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -123.29848, 0, 0, 1);
}
10% {
transform: matrix3d(1.08167, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -57.59273, 0, 0, 1);
}
11.666667% {
transform: matrix3d(1.0165, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -14.72371, 0, 0, 1);
}
13.333333% {
transform: matrix3d(0.99057, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 11.12794, 0, 0, 1);
}
15% {
transform: matrix3d(0.98478, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 24.86339, 0, 0, 1);
}
16.666667% {
transform: matrix3d(0.98719, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 30.40503, 0, 0, 1);
}
18.333333% {
transform: matrix3d(0.9916, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 30.75275, 0, 0, 1);
}
20% {
transform: matrix3d(0.99541, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 28.10141, 0, 0, 1);
}
21.666667% {
transform: matrix3d(0.99795, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 23.98271, 0, 0, 1);
}
23.333333% {
transform: matrix3d(0.99936, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 19.40752, 0, 0, 1);
}
25% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 14.99558, 0, 0, 1);
}
26.666667% {
transform: matrix3d(1.00021, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 11.08575, 0, 0, 1);
}
28.333333% {
transform: matrix3d(1.00022, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 7.82507, 0, 0, 1);
}
30% {
transform: matrix3d(1.00016, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5.23737, 0, 0, 1);
}
31.666667% {
transform: matrix3d(1.0001, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3.27389, 0, 0, 1);
}
33.333333% {
transform: matrix3d(1.00005, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.84893, 0, 0, 1);
}
35% {
transform: matrix3d(1.00002, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.86364, 0, 0, 1);
}
36.666667% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.22079, 0, 0, 1);
}
38.333333% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.16687, 0, 0, 1);
}
40% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.37284, 0, 0, 1);
}
41.666667% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.45594, 0, 0, 1);
}
43.333333% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.46116, 0, 0, 1);
}
45% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.4214, 0, 0, 1);
}
46.666667% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.35963, 0, 0, 1);
}
48.333333% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.29103, 0, 0, 1);
}
50% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.22487, 0, 0, 1);
}
51.666667% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.16624, 0, 0, 1);
}
53.333333% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.11734, 0, 0, 1);
}
55% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.07854, 0, 0, 1);
}
56.666667% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.04909, 0, 0, 1);
}
58.333333% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.02773, 0, 0, 1);
}
60% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.01295, 0, 0, 1);
}
61.666667% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.00331, 0, 0, 1);
}
63.333333% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.0025, 0, 0, 1);
}
65% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00559, 0, 0, 1);
}
66.666667% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00684, 0, 0, 1);
}
68.333333% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00692, 0, 0, 1);
}
70% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00632, 0, 0, 1);
}
71.666667% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00539, 0, 0, 1);
}
73.333333% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00436, 0, 0, 1);
}
75% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00337, 0, 0, 1);
}
76.666667% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00249, 0, 0, 1);
}
78.333333% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00176, 0, 0, 1);
}
80% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00118, 0, 0, 1);
}
81.666667% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00074, 0, 0, 1);
}
83.333333% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00042, 0, 0, 1);
}
85% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00019, 0, 0, 1);
}
86.666667% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.00005, 0, 0, 1);
}
88.333333% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.00004, 0, 0, 1);
}
90% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.00008, 0, 0, 1);
}
91.666667% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0001, 0, 0, 1);
}
93.333333% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.0001, 0, 0, 1);
}
95% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.00009, 0, 0, 1);
}
96.666667% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.00008, 0, 0, 1);
}
98.333333% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.00007, 0, 0, 1);
}
100% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
}
.ns-effect-slide-left.ns-hide {
animation-name: animSlideLeft;
animation-duration: 0.25s;
}
@keyframes animSlideLeft {
0% {
transform: translate3d(-30px, 0, 0) translate3d(-100%, 0, 0);
}
100% {
transform: translate3d(0, 0, 0);
}
}
.ns-effect-slide-right.ns-show {
animation: animSlideElasticRight 2000ms linear both;
}
@keyframes animSlideElasticRight {
0% {
transform: matrix3d(2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1000, 0, 0, 1);
}
2.15% {
transform: matrix3d(1.486, 0, 0, 0, 0, 0.514, 0, 0, 0, 0, 1, 0, 664.594, 0, 0, 1);
}
4.1% {
transform: matrix3d(1.147, 0, 0, 0, 0, 0.853, 0, 0, 0, 0, 1, 0, 419.708, 0, 0, 1);
}
4.3% {
transform: matrix3d(1.121, 0, 0, 0, 0, 0.879, 0, 0, 0, 0, 1, 0, 398.136, 0, 0, 1);
}
6.46% {
transform: matrix3d(0.948, 0, 0, 0, 0, 1.052, 0, 0, 0, 0, 1, 0, 206.714, 0, 0, 1);
}
8.11% {
transform: matrix3d(0.908, 0, 0, 0, 0, 1.092, 0, 0, 0, 0, 1, 0, 105.491, 0, 0, 1);
}
8.61% {
transform: matrix3d(0.907, 0, 0, 0, 0, 1.093, 0, 0, 0, 0, 1, 0, 81.572, 0, 0, 1);
}
12.11% {
transform: matrix3d(0.95, 0, 0, 0, 0, 1.05, 0, 0, 0, 0, 1, 0, -18.434, 0, 0, 1);
}
14.16% {
transform: matrix3d(0.979, 0, 0, 0, 0, 1.021, 0, 0, 0, 0, 1, 0, -38.734, 0, 0, 1);
}
16.12% {
transform: matrix3d(0.997, 0, 0, 0, 0, 1.003, 0, 0, 0, 0, 1, 0, -43.356, 0, 0, 1);
}
19.72% {
transform: matrix3d(1.006, 0, 0, 0, 0, 0.994, 0, 0, 0, 0, 1, 0, -34.155, 0, 0, 1);
}
27.23% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -7.839, 0, 0, 1);
}
30.83% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -1.951, 0, 0, 1);
}
38.34% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.037, 0, 0, 1);
}
41.99% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.812, 0, 0, 1);
}
50% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.159, 0, 0, 1);
}
60.56% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -0.025, 0, 0, 1);
}
82.78% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0.001, 0, 0, 1);
}
100% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
}
.ns-effect-slide-right.ns-hide {
animation-name: animSlideRight;
animation-duration: 0.25s;
}
@keyframes animSlideRight {
0% {
transform: translate3d(30px, 0, 0) translate3d(100%, 0, 0);
}
100% {
transform: translate3d(0, 0, 0);
}
}
.ns-effect-slide-center.ns-show {
animation: animSlideElasticCenter 2000ms linear both;
}
@keyframes animSlideElasticCenter {
0% {
transform: matrix3d(1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, 0, -300, 0, 1);
}
2.15% {
transform: matrix3d(1, 0, 0, 0, 0, 1.971, 0, 0, 0, 0, 1, 0, 0, -199.378, 0, 1);
}
4.1% {
transform: matrix3d(1, 0, 0, 0, 0, 1.294, 0, 0, 0, 0, 1, 0, 0, -125.912, 0, 1);
}
4.3% {
transform: matrix3d(1, 0, 0, 0, 0, 1.243, 0, 0, 0, 0, 1, 0, 0, -119.441, 0, 1);
}
6.46% {
transform: matrix3d(1, 0, 0, 0, 0, 0.895, 0, 0, 0, 0, 1, 0, 0, -62.014, 0, 1);
}
8.11% {
transform: matrix3d(1, 0, 0, 0, 0, 0.817, 0, 0, 0, 0, 1, 0, 0, -31.647, 0, 1);
}
8.61% {
transform: matrix3d(1, 0, 0, 0, 0, 0.813, 0, 0, 0, 0, 1, 0, 0, -24.472, 0, 1);
}
12.11% {
transform: matrix3d(1, 0, 0, 0, 0, 0.9, 0, 0, 0, 0, 1, 0, 0, 5.53, 0, 1);
}
14.16% {
transform: matrix3d(1, 0, 0, 0, 0, 0.959, 0, 0, 0, 0, 1, 0, 0, 11.62, 0, 1);
}
16.12% {
transform: matrix3d(1, 0, 0, 0, 0, 0.994, 0, 0, 0, 0, 1, 0, 0, 13.007, 0, 1);
}
19.72% {
transform: matrix3d(1, 0, 0, 0, 0, 1.012, 0, 0, 0, 0, 1, 0, 0, 10.247, 0, 1);
}
27.23% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 2.352, 0, 1);
}
30.83% {
transform: matrix3d(1, 0, 0, 0, 0, 0.999, 0, 0, 0, 0, 1, 0, 0, 0.585, 0, 1);
}
38.34% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -0.311, 0, 1);
}
41.99% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -0.244, 0, 1);
}
50% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, -0.048, 0, 1);
}
60.56% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0.007, 0, 1);
}
82.78% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
100% {
transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
}
.ns-effect-slide-center.ns-hide {
animation-name: animSlideCenter;
animation-duration: 0.25s;
}
@keyframes animSlideCenter {
0% {
transform: translate3d(0, -30px, 0) translate3d(0, -100%, 0);
}
100% {
transform: translate3d(0, 0, 0);
}
}
.ns-effect-genie.ns-show,
.ns-effect-genie.ns-hide {
animation-name: animGenie;
animation-duration: 0.4s;
}
@keyframes animGenie {
0% {
opacity: 0;
transform: translate3d(0, calc(200% + 30px), 0) scale3d(0, 1, 1);
animation-timing-function: ease-in;
}
40% {
opacity: 0.5;
transform: translate3d(0, 0, 0) scale3d(0.02, 1.1, 1);
animation-timing-function: ease-out;
}
70% {
opacity: 0.6;
transform: translate3d(0, -40px, 0) scale3d(0.8, 1.1, 1);
}
100% {
opacity: 1;
transform: translate3d(0, 0, 0) scale3d(1, 1, 1);
}
}

View File

@ -1,34 +1,21 @@
/** /**
* Based on work by
*
* notificationFx.js v1.0.0 * notificationFx.js v1.0.0
* http://www.codrops.com * https://tympanus.net/codrops/
* *
* Licensed under the MIT license. * Licensed under the MIT license.
* http://www.opensource.org/licenses/mit-license.php * https://opensource.org/licenses/mit-license.php
* *
* Copyright 2014, Codrops * Copyright 2014, Codrops
* http://www.codrops.com * https://tympanus.net/codrops/
*/ */
// jscs:disable (function (window) {
;(function(window) {
"use strict";
var docElem = window.document.documentElement,
support = {animations: Modernizr.cssanimations},
animEndEventNames = {
"WebkitAnimation": "webkitAnimationEnd",
"OAnimation": "oAnimationEnd",
"msAnimation": "MSAnimationEnd",
"animation": "animationend"
},
// animation end event name
animEndEventName = animEndEventNames[ Modernizr.prefixed("animation") ];
/** /**
* extend obj function * extend obj function
*/ */
function extend(a, b) { function extend(a, b) {
for (var key in b) { for (let key in b) {
if (b.hasOwnProperty(key)) { if (b.hasOwnProperty(key)) {
a[key] = b[key]; a[key] = b[key];
} }
@ -70,19 +57,23 @@
ttl: 6000, ttl: 6000,
al_no: "ns-box", al_no: "ns-box",
// callbacks // callbacks
onClose: function() { return false; }, onClose: function () {
onOpen: function() { return false; } return false;
},
onOpen: function () {
return false;
}
}; };
/** /**
* init function * init function
* initialize and cache some vars * initialize and cache some vars
*/ */
NotificationFx.prototype._init = function() { NotificationFx.prototype._init = function () {
// create HTML structure // create HTML structure
this.ntf = document.createElement("div"); this.ntf = document.createElement("div");
this.ntf.className = this.options.al_no + " ns-" + this.options.layout + " ns-effect-" + this.options.effect + " ns-type-" + this.options.type; this.ntf.className = this.options.al_no + " ns-" + this.options.layout + " ns-effect-" + this.options.effect + " ns-type-" + this.options.type;
var strinner = "<div class=\"ns-box-inner\">"; let strinner = '<div class="ns-box-inner">';
strinner += this.options.message; strinner += this.options.message;
strinner += "</div>"; strinner += "</div>";
this.ntf.innerHTML = strinner; this.ntf.innerHTML = strinner;
@ -91,13 +82,12 @@
this.options.wrapper.insertBefore(this.ntf, this.options.wrapper.nextSibling); this.options.wrapper.insertBefore(this.ntf, this.options.wrapper.nextSibling);
// dismiss after [options.ttl]ms // dismiss after [options.ttl]ms
var self = this;
if (this.options.ttl) { if (this.options.ttl) {
this.dismissttl = setTimeout(function() { this.dismissttl = setTimeout(() => {
if (self.active) { if (this.active) {
self.dismiss(); this.dismiss();
} }
}, this.options.ttl); }, this.options.ttl);
} }
// init events // init events
@ -107,59 +97,54 @@
/** /**
* init events * init events
*/ */
NotificationFx.prototype._initEvents = function() { NotificationFx.prototype._initEvents = function () {
var self = this;
// dismiss notification by tapping on it if someone has a touchscreen // dismiss notification by tapping on it if someone has a touchscreen
this.ntf.querySelector(".ns-box-inner").addEventListener("click", function() { self.dismiss(); }); this.ntf.querySelector(".ns-box-inner").addEventListener("click", () => {
this.dismiss();
});
}; };
/** /**
* show the notification * show the notification
*/ */
NotificationFx.prototype.show = function() { NotificationFx.prototype.show = function () {
this.active = true; this.active = true;
classie.remove(this.ntf, "ns-hide"); this.ntf.classList.remove("ns-hide");
classie.add(this.ntf, "ns-show"); this.ntf.classList.add("ns-show");
this.options.onOpen(); this.options.onOpen();
}; };
/** /**
* dismiss the notification * dismiss the notification
*/ */
NotificationFx.prototype.dismiss = function() { NotificationFx.prototype.dismiss = function () {
var self = this;
this.active = false; this.active = false;
clearTimeout(this.dismissttl); clearTimeout(this.dismissttl);
classie.remove(this.ntf, "ns-show"); this.ntf.classList.remove("ns-show");
setTimeout(function() { setTimeout(() => {
classie.add(self.ntf, "ns-hide"); this.ntf.classList.add("ns-hide");
// callback // callback
self.options.onClose(); this.options.onClose();
}, 25); }, 25);
// after animation ends remove ntf from the DOM // after animation ends remove ntf from the DOM
var onEndAnimationFn = function(ev) { const onEndAnimationFn = (ev) => {
if (support.animations) { if (ev.target !== this.ntf) {
if (ev.target !== self.ntf) return false; return false;
this.removeEventListener(animEndEventName, onEndAnimationFn);
} }
this.ntf.removeEventListener("animationend", onEndAnimationFn);
if (this.parentNode === self.options.wrapper) { if (ev.target.parentNode === this.options.wrapper) {
self.options.wrapper.removeChild(this); this.options.wrapper.removeChild(this.ntf);
} }
}; };
if (support.animations) { this.ntf.addEventListener("animationend", onEndAnimationFn);
this.ntf.addEventListener(animEndEventName, onEndAnimationFn);
} else {
onEndAnimationFn();
}
}; };
/** /**
* add to global namespace * add to global namespace
*/ */
window.NotificationFx = NotificationFx; window.NotificationFx = NotificationFx;
})(window); })(window);

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
{ {
"sysTitle": "MagicMirror нотификация", "sysTitle": "MagicMirror нотификация",
"welcome": "Добре дошли, стартирането беше успешно" "welcome": "Добре дошли, стартирането беше успешно"
} }

View File

@ -1,4 +1,4 @@
{ {
"sysTitle": "MagicMirror Notifikation", "sysTitle": "MagicMirror Notifikation",
"welcome": "Velkommen, modulet er succesfuldt startet!" "welcome": "Velkommen, modulet er succesfuldt startet!"
} }

View File

@ -1,4 +1,4 @@
{ {
"sysTitle": "MagicMirror Benachrichtigung", "sysTitle": "MagicMirror Benachrichtigung",
"welcome": "Willkommen, Start war erfolgreich!" "welcome": "Willkommen, Start war erfolgreich!"
} }

View File

@ -1,4 +1,4 @@
{ {
"sysTitle": "MagicMirror Notification", "sysTitle": "MagicMirror Notification",
"welcome": "Welcome, start was successful!" "welcome": "Welcome, start was successful!"
} }

View File

@ -1,4 +1,4 @@
{ {
"sysTitle": "MagicMirror Notificaciones", "sysTitle": "MagicMirror Notificaciones",
"welcome": "Bienvenido, ¡se iniciado correctamente!" "welcome": "Bienvenido, ¡se iniciado correctamente!"
} }

View File

@ -1,4 +1,4 @@
{ {
"sysTitle": "MagicMirror Notification", "sysTitle": "MagicMirror Notification",
"welcome": "Bienvenue, le démarrage a été un succès!" "welcome": "Bienvenue, le démarrage a été un succès!"
} }

View File

@ -1,4 +1,4 @@
{ {
"sysTitle": "MagicMirror értesítés", "sysTitle": "MagicMirror értesítés",
"welcome": "Üdvözöljük, indulás sikeres!" "welcome": "Üdvözöljük, indulás sikeres!"
} }

View File

@ -1,4 +1,4 @@
{ {
"sysTitle": "MagicMirror Notificatie", "sysTitle": "MagicMirror Notificatie",
"welcome": "Welkom, Succesvol gestart!" "welcome": "Welkom, Succesvol gestart!"
} }

View File

@ -1,4 +1,4 @@
{ {
"sysTitle": "MagicMirror Уведомление", "sysTitle": "MagicMirror Уведомление",
"welcome": "Добро пожаловать, старт был успешным!" "welcome": "Добро пожаловать, старт был успешным!"
} }

View File

@ -1,4 +1,5 @@
# Module: Calendar # Module: Calendar
The `calendar` module is one of the default modules of the MagicMirror. The `calendar` module is one of the default modules of the MagicMirror.
This module displays events from a public .ical calendar. It can combine multiple calendars. This module displays events from a public .ical calendar. It can combine multiple calendars.

View File

@ -7,8 +7,6 @@
.calendar .symbol span { .calendar .symbol span {
display: inline-block; display: inline-block;
-ms-transform: translate(0, 2px); /* IE 9 */
-webkit-transform: translate(0, 2px); /* Safari */
transform: translate(0, 2px); transform: translate(0, 2px);
} }

View File

@ -1,20 +1,18 @@
/* global Module */ /* global cloneObject */
/* Magic Mirror /* Magic Mirror
* Module: Calendar * Module: Calendar
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
Module.register("calendar", { Module.register("calendar", {
// Define module defaults // Define module defaults
defaults: { defaults: {
maximumEntries: 10, // Total Maximum Entries maximumEntries: 10, // Total Maximum Entries
maximumNumberOfDays: 365, maximumNumberOfDays: 365,
displaySymbol: true, displaySymbol: true,
defaultSymbol: "calendar", // Fontawesome Symbol see http://fontawesome.io/cheatsheet/ defaultSymbol: "calendar", // Fontawesome Symbol see https://fontawesome.com/cheatsheet?from=io
showLocation: false, showLocation: false,
displayRepeatingCountTitle: false, displayRepeatingCountTitle: false,
defaultRepeatingCountTitle: "", defaultRepeatingCountTitle: "",
@ -40,8 +38,8 @@ Module.register("calendar", {
calendars: [ calendars: [
{ {
symbol: "calendar", symbol: "calendar",
url: "http://www.calendarlabs.com/templates/ical/US-Holidays.ics", url: "https://www.calendarlabs.com/templates/ical/US-Holidays.ics"
}, }
], ],
titleReplace: { titleReplace: {
"De verjaardag van ": "", "De verjaardag van ": "",
@ -86,7 +84,7 @@ Module.register("calendar", {
var calendarConfig = { var calendarConfig = {
maximumEntries: calendar.maximumEntries, maximumEntries: calendar.maximumEntries,
maximumNumberOfDays: calendar.maximumNumberOfDays, maximumNumberOfDays: calendar.maximumNumberOfDays,
broadcastPastEvents: calendar.broadcastPastEvents, broadcastPastEvents: calendar.broadcastPastEvents
}; };
if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) { if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) {
calendarConfig.symbolClass = ""; calendarConfig.symbolClass = "";
@ -99,7 +97,7 @@ Module.register("calendar", {
} }
// we check user and password here for backwards compatibility with old configs // we check user and password here for backwards compatibility with old configs
if(calendar.user && calendar.pass) { if (calendar.user && calendar.pass) {
Log.warn("Deprecation warning: Please update your calendar authentication configuration."); Log.warn("Deprecation warning: Please update your calendar authentication configuration.");
Log.warn("https://github.com/MichMich/MagicMirror/tree/v2.1.2/modules/default/calendar#calendar-authentication-options"); Log.warn("https://github.com/MichMich/MagicMirror/tree/v2.1.2/modules/default/calendar#calendar-authentication-options");
calendar.auth = { calendar.auth = {
@ -113,7 +111,7 @@ Module.register("calendar", {
// Trigger ADD_CALENDAR every fetchInterval to make sure there is always a calendar // Trigger ADD_CALENDAR every fetchInterval to make sure there is always a calendar
// fetcher running on the server side. // fetcher running on the server side.
var self = this; var self = this;
setInterval(function() { setInterval(function () {
self.addCalendar(calendar.url, calendar.auth, calendarConfig); self.addCalendar(calendar.url, calendar.auth, calendarConfig);
}, self.config.fetchInterval); }, self.config.fetchInterval);
} }
@ -145,13 +143,12 @@ Module.register("calendar", {
// Override dom generator. // Override dom generator.
getDom: function () { getDom: function () {
var events = this.createEventList(); var events = this.createEventList();
var wrapper = document.createElement("table"); var wrapper = document.createElement("table");
wrapper.className = this.config.tableClass; wrapper.className = this.config.tableClass;
if (events.length === 0) { if (events.length === 0) {
wrapper.innerHTML = (this.loaded) ? this.translate("EMPTY") : this.translate("LOADING"); wrapper.innerHTML = this.loaded ? this.translate("EMPTY") : this.translate("LOADING");
wrapper.className = this.config.tableClass + " dimmed"; wrapper.className = this.config.tableClass + " dimmed";
return wrapper; return wrapper;
} }
@ -170,8 +167,8 @@ Module.register("calendar", {
for (var e in events) { for (var e in events) {
var event = events[e]; var event = events[e];
var dateAsString = moment(event.startDate, "x").format(this.config.dateFormat); var dateAsString = moment(event.startDate, "x").format(this.config.dateFormat);
if(this.config.timeFormat === "dateheaders"){ if (this.config.timeFormat === "dateheaders") {
if(lastSeenDate !== dateAsString){ if (lastSeenDate !== dateAsString) {
var dateRow = document.createElement("tr"); var dateRow = document.createElement("tr");
dateRow.className = "normal"; dateRow.className = "normal";
var dateCell = document.createElement("td"); var dateCell = document.createElement("td");
@ -182,9 +179,10 @@ Module.register("calendar", {
dateRow.appendChild(dateCell); dateRow.appendChild(dateCell);
wrapper.appendChild(dateRow); wrapper.appendChild(dateRow);
if (e >= startFade) { //fading if (e >= startFade) {
//fading
currentFadeStep = e - startFade; currentFadeStep = e - startFade;
dateRow.style.opacity = 1 - (1 / fadeSteps * currentFadeStep); dateRow.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
} }
lastSeenDate = dateAsString; lastSeenDate = dateAsString;
@ -210,20 +208,20 @@ Module.register("calendar", {
symbolWrapper.className = "symbol align-right " + symbolClass; symbolWrapper.className = "symbol align-right " + symbolClass;
var symbols = this.symbolsForUrl(event.url); var symbols = this.symbolsForUrl(event.url);
if(typeof symbols === "string") { if (typeof symbols === "string") {
symbols = [symbols]; symbols = [symbols];
} }
for(var i = 0; i < symbols.length; i++) { for (var i = 0; i < symbols.length; i++) {
var symbol = document.createElement("span"); var symbol = document.createElement("span");
symbol.className = "fa fa-fw fa-" + symbols[i]; symbol.className = "fa fa-fw fa-" + symbols[i];
if(i > 0){ if (i > 0) {
symbol.style.paddingLeft = "5px"; symbol.style.paddingLeft = "5px";
} }
symbolWrapper.appendChild(symbol); symbolWrapper.appendChild(symbol);
} }
eventWrapper.appendChild(symbolWrapper); eventWrapper.appendChild(symbolWrapper);
} else if(this.config.timeFormat === "dateheaders"){ } else if (this.config.timeFormat === "dateheaders") {
var blankCell = document.createElement("td"); var blankCell = document.createElement("td");
blankCell.innerHTML = "&nbsp;&nbsp;&nbsp;"; blankCell.innerHTML = "&nbsp;&nbsp;&nbsp;";
eventWrapper.appendChild(blankCell); eventWrapper.appendChild(blankCell);
@ -233,7 +231,6 @@ Module.register("calendar", {
repeatingCountTitle = ""; repeatingCountTitle = "";
if (this.config.displayRepeatingCountTitle && event.firstYear !== undefined) { if (this.config.displayRepeatingCountTitle && event.firstYear !== undefined) {
repeatingCountTitle = this.countTitleForUrl(event.url); repeatingCountTitle = this.countTitleForUrl(event.url);
if (repeatingCountTitle !== "") { if (repeatingCountTitle !== "") {
@ -254,17 +251,15 @@ Module.register("calendar", {
titleWrapper.className = "title " + titleClass; titleWrapper.className = "title " + titleClass;
} }
if(this.config.timeFormat === "dateheaders"){ var timeWrapper;
if (this.config.timeFormat === "dateheaders") {
if (event.fullDayEvent) { if (event.fullDayEvent) {
titleWrapper.colSpan = "2"; titleWrapper.colSpan = "2";
titleWrapper.align = "left"; titleWrapper.align = "left";
} else { } else {
timeWrapper = document.createElement("td");
var timeClass = this.timeClassForUrl(event.url); timeWrapper.className = "time light " + this.timeClassForUrl(event.url);
var timeWrapper = document.createElement("td");
timeWrapper.className = "time light " + timeClass;
timeWrapper.align = "left"; timeWrapper.align = "left";
timeWrapper.style.paddingLeft = "2px"; timeWrapper.style.paddingLeft = "2px";
timeWrapper.innerHTML = moment(event.startDate, "x").format("LT"); timeWrapper.innerHTML = moment(event.startDate, "x").format("LT");
@ -274,7 +269,7 @@ Module.register("calendar", {
eventWrapper.appendChild(titleWrapper); eventWrapper.appendChild(titleWrapper);
} else { } else {
var timeWrapper = document.createElement("td"); timeWrapper = document.createElement("td");
eventWrapper.appendChild(titleWrapper); eventWrapper.appendChild(titleWrapper);
//console.log(event.today); //console.log(event.today);
@ -299,14 +294,14 @@ Module.register("calendar", {
} }
} else { } else {
/* Check to see if the user displays absolute or relative dates with their events /* Check to see if the user displays absolute or relative dates with their events
* Also check to see if an event is happening within an 'urgency' time frameElement * Also check to see if an event is happening within an 'urgency' time frameElement
* For example, if the user set an .urgency of 7 days, those events that fall within that * For example, if the user set an .urgency of 7 days, those events that fall within that
* time frame will be displayed with 'in xxx' time format or moment.fromNow() * time frame will be displayed with 'in xxx' time format or moment.fromNow()
* *
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim * Note: this needs to be put in its own function, as the whole thing repeats again verbatim
*/ */
if (this.config.timeFormat === "absolute") { if (this.config.timeFormat === "absolute") {
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * oneDay))) { if (this.config.urgency > 1 && event.startDate - now < this.config.urgency * oneDay) {
// This event falls within the config.urgency period that the user has set // This event falls within the config.urgency period that the user has set
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").from(moment().format("YYYYMMDD"))); timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").from(moment().format("YYYYMMDD")));
} else { } else {
@ -316,9 +311,9 @@ Module.register("calendar", {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").from(moment().format("YYYYMMDD"))); timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").from(moment().format("YYYYMMDD")));
} }
} }
if(this.config.showEnd){ if (this.config.showEnd) {
timeWrapper.innerHTML += "-" ; timeWrapper.innerHTML += "-";
timeWrapper.innerHTML += this.capFirst(moment(event.endDate , "x").format(this.config.fullDayEventDateFormat)); timeWrapper.innerHTML += this.capFirst(moment(event.endDate, "x").format(this.config.fullDayEventDateFormat));
} }
} else { } else {
if (event.startDate >= new Date()) { if (event.startDate >= new Date()) {
@ -328,7 +323,7 @@ Module.register("calendar", {
// If event is within 6 hour, display 'in xxx' time format or moment.fromNow() // If event is within 6 hour, display 'in xxx' time format or moment.fromNow()
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow()); timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
} else { } else {
if(this.config.timeFormat === "absolute" && !this.config.nextDaysRelative) { if (this.config.timeFormat === "absolute" && !this.config.nextDaysRelative) {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.dateFormat)); timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
} else { } else {
// Otherwise just say 'Today/Tomorrow at such-n-such time' // Otherwise just say 'Today/Tomorrow at such-n-such time'
@ -337,14 +332,14 @@ Module.register("calendar", {
} }
} else { } else {
/* Check to see if the user displays absolute or relative dates with their events /* Check to see if the user displays absolute or relative dates with their events
* Also check to see if an event is happening within an 'urgency' time frameElement * Also check to see if an event is happening within an 'urgency' time frameElement
* For example, if the user set an .urgency of 7 days, those events that fall within that * For example, if the user set an .urgency of 7 days, those events that fall within that
* time frame will be displayed with 'in xxx' time format or moment.fromNow() * time frame will be displayed with 'in xxx' time format or moment.fromNow()
* *
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim * Note: this needs to be put in its own function, as the whole thing repeats again verbatim
*/ */
if (this.config.timeFormat === "absolute") { if (this.config.timeFormat === "absolute") {
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * oneDay))) { if (this.config.urgency > 1 && event.startDate - now < this.config.urgency * oneDay) {
// This event falls within the config.urgency period that the user has set // This event falls within the config.urgency period that the user has set
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow()); timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
} else { } else {
@ -365,13 +360,11 @@ Module.register("calendar", {
if (this.config.showEnd) { if (this.config.showEnd) {
timeWrapper.innerHTML += "-"; timeWrapper.innerHTML += "-";
timeWrapper.innerHTML += this.capFirst(moment(event.endDate, "x").format(this.config.dateEndFormat)); timeWrapper.innerHTML += this.capFirst(moment(event.endDate, "x").format(this.config.dateEndFormat));
} }
} }
//timeWrapper.innerHTML += ' - '+ moment(event.startDate,'x').format('lll'); //timeWrapper.innerHTML += ' - '+ moment(event.startDate,'x').format('lll');
//console.log(event); //console.log(event);
var timeClass = this.timeClassForUrl(event.url); timeWrapper.className = "time light " + this.timeClassForUrl(event.url);
timeWrapper.className = "time light " + timeClass;
eventWrapper.appendChild(timeWrapper); eventWrapper.appendChild(timeWrapper);
} }
@ -380,7 +373,7 @@ Module.register("calendar", {
// Create fade effect. // Create fade effect.
if (e >= startFade) { if (e >= startFade) {
currentFadeStep = e - startFade; currentFadeStep = e - startFade;
eventWrapper.style.opacity = 1 - (1 / fadeSteps * currentFadeStep); eventWrapper.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
} }
if (this.config.showLocation) { if (this.config.showLocation) {
@ -403,7 +396,7 @@ Module.register("calendar", {
if (e >= startFade) { if (e >= startFade) {
currentFadeStep = e - startFade; currentFadeStep = e - startFade;
locationRow.style.opacity = 1 - (1 / fadeSteps * currentFadeStep); locationRow.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
} }
} }
} }
@ -420,20 +413,17 @@ Module.register("calendar", {
* @param {number} timeFormat Specifies either 12 or 24 hour time format * @param {number} timeFormat Specifies either 12 or 24 hour time format
* @returns {moment.LocaleSpecification} * @returns {moment.LocaleSpecification}
*/ */
getLocaleSpecification: function(timeFormat) { getLocaleSpecification: function (timeFormat) {
switch (timeFormat) { switch (timeFormat) {
case 12: { case 12: {
return { longDateFormat: {LT: "h:mm A"} }; return { longDateFormat: { LT: "h:mm A" } };
break; }
} case 24: {
case 24: { return { longDateFormat: { LT: "HH:mm" } };
return { longDateFormat: {LT: "HH:mm"} }; }
break; default: {
} return { longDateFormat: { LT: moment.localeData().longDateFormat("LT") } };
default: { }
return { longDateFormat: {LT: moment.localeData().longDateFormat("LT")} };
break;
}
} }
}, },
@ -469,37 +459,37 @@ Module.register("calendar", {
var calendar = this.calendarData[c]; var calendar = this.calendarData[c];
for (var e in calendar) { for (var e in calendar) {
var event = JSON.parse(JSON.stringify(calendar[e])); // clone object var event = JSON.parse(JSON.stringify(calendar[e])); // clone object
if(event.endDate < now) { if (event.endDate < now) {
continue; continue;
} }
if(this.config.hidePrivate) { if (this.config.hidePrivate) {
if(event.class === "PRIVATE") { if (event.class === "PRIVATE") {
// do not add the current event, skip it // do not add the current event, skip it
continue;
}
}
if(this.config.hideOngoing) {
if(event.startDate < now) {
continue; continue;
} }
} }
if(this.listContainsEvent(events,event)){ if (this.config.hideOngoing) {
if (event.startDate < now) {
continue;
}
}
if (this.listContainsEvent(events, event)) {
continue; continue;
} }
event.url = c; event.url = c;
event.today = event.startDate >= today && event.startDate < (today + 24 * 60 * 60 * 1000); event.today = event.startDate >= today && event.startDate < today + 24 * 60 * 60 * 1000;
/* if sliceMultiDayEvents is set to true, multiday events (events exceeding at least one midnight) are sliced into days, /* if sliceMultiDayEvents is set to true, multiday events (events exceeding at least one midnight) are sliced into days,
* otherwise, esp. in dateheaders mode it is not clear how long these events are. * otherwise, esp. in dateheaders mode it is not clear how long these events are.
*/ */
var maxCount = Math.ceil(((event.endDate - 1) - moment(event.startDate, "x").endOf("day").format("x"))/(1000*60*60*24)) + 1; var maxCount = Math.ceil((event.endDate - 1 - moment(event.startDate, "x").endOf("day").format("x")) / (1000 * 60 * 60 * 24)) + 1;
if (this.config.sliceMultiDayEvents && maxCount > 1) { if (this.config.sliceMultiDayEvents && maxCount > 1) {
var splitEvents = []; var splitEvents = [];
var midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x"); var midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x");
var count = 1; var count = 1;
while (event.endDate > midnight) { while (event.endDate > midnight) {
var thisEvent = JSON.parse(JSON.stringify(event)); // clone object var thisEvent = JSON.parse(JSON.stringify(event)); // clone object
thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < (today + 24 * 60 * 60 * 1000); thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < today + 24 * 60 * 60 * 1000;
thisEvent.endDate = midnight; thisEvent.endDate = midnight;
thisEvent.title += " (" + count + "/" + maxCount + ")"; thisEvent.title += " (" + count + "/" + maxCount + ")";
splitEvents.push(thisEvent); splitEvents.push(thisEvent);
@ -509,11 +499,11 @@ Module.register("calendar", {
midnight = moment(midnight, "x").add(1, "day").format("x"); // next day midnight = moment(midnight, "x").add(1, "day").format("x"); // next day
} }
// Last day // Last day
event.title += " ("+count+"/"+maxCount+")"; event.title += " (" + count + "/" + maxCount + ")";
splitEvents.push(event); splitEvents.push(event);
for (event of splitEvents) { for (event of splitEvents) {
if ((event.endDate > now) && (event.endDate <= future)) { if (event.endDate > now && event.endDate <= future) {
events.push(event); events.push(event);
} }
} }
@ -529,9 +519,9 @@ Module.register("calendar", {
return events.slice(0, this.config.maximumEntries); return events.slice(0, this.config.maximumEntries);
}, },
listContainsEvent: function(eventList, event){ listContainsEvent: function (eventList, event) {
for(var evt of eventList){ for (var evt of eventList) {
if(evt.title === event.title && parseInt(evt.startDate) === parseInt(event.startDate)){ if (evt.title === event.title && parseInt(evt.startDate) === parseInt(event.startDate)) {
return true; return true;
} }
} }
@ -554,7 +544,7 @@ Module.register("calendar", {
titleClass: calendarConfig.titleClass, titleClass: calendarConfig.titleClass,
timeClass: calendarConfig.timeClass, timeClass: calendarConfig.timeClass,
auth: auth, auth: auth,
broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents, broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents
}); });
}, },
@ -681,8 +671,9 @@ Module.register("calendar", {
for (var i = 0; i < words.length; i++) { for (var i = 0; i < words.length; i++) {
var word = words[i]; var word = words[i];
if (currentLine.length + word.length < (typeof maxLength === "number" ? maxLength : 25) - 1) { // max - 1 to account for a space if (currentLine.length + word.length < (typeof maxLength === "number" ? maxLength : 25) - 1) {
currentLine += (word + " "); // max - 1 to account for a space
currentLine += word + " ";
} else { } else {
line++; line++;
if (line > maxTitleLines - 1) { if (line > maxTitleLines - 1) {
@ -693,9 +684,9 @@ Module.register("calendar", {
} }
if (currentLine.length > 0) { if (currentLine.length > 0) {
temp += (currentLine + "<br>" + word + " "); temp += currentLine + "<br>" + word + " ";
} else { } else {
temp += (word + "<br>"); temp += word + "<br>";
} }
currentLine = ""; currentLine = "";
} }
@ -734,8 +725,8 @@ Module.register("calendar", {
var regParts = needle.match(/^\/(.+)\/([gim]*)$/); var regParts = needle.match(/^\/(.+)\/([gim]*)$/);
if (regParts) { if (regParts) {
// the parsed pattern is a regexp. // the parsed pattern is a regexp.
needle = new RegExp(regParts[1], regParts[2]); needle = new RegExp(regParts[1], regParts[2]);
} }
title = title.replace(needle, replacement); title = title.replace(needle, replacement);
@ -763,11 +754,10 @@ Module.register("calendar", {
} }
} }
eventList.sort(function(a,b) { eventList.sort(function (a, b) {
return a.startDate - b.startDate; return a.startDate - b.startDate;
}); });
this.sendNotification("CALENDAR_EVENTS", eventList); this.sendNotification("CALENDAR_EVENTS", eventList);
} }
}); });

View File

@ -1,51 +1,49 @@
/* Magic Mirror /* Magic Mirror
* Node Helper: Calendar - CalendarFetcher * Node Helper: Calendar - CalendarFetcher
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
var ical = require("./vendor/ical.js"); const ical = require("./vendor/ical.js");
var moment = require("moment"); const moment = require("moment");
var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents) { var CalendarFetcher = function (url, reloadInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, includePastEvents) {
var self = this; var self = this;
var reloadTimer = null; var reloadTimer = null;
var events = []; var events = [];
var fetchFailedCallback = function() {}; var fetchFailedCallback = function () {};
var eventsReceivedCallback = function() {}; var eventsReceivedCallback = function () {};
/* fetchCalendar() /* fetchCalendar()
* Initiates calendar fetch. * Initiates calendar fetch.
*/ */
var fetchCalendar = function() { var fetchCalendar = function () {
clearTimeout(reloadTimer); clearTimeout(reloadTimer);
reloadTimer = null; reloadTimer = null;
nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); var nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
var opts = { var opts = {
headers: { headers: {
"User-Agent": "Mozilla/5.0 (Node.js "+ nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)" "User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)"
}, },
gzip: true gzip: true
}; };
if (auth) { if (auth) {
if(auth.method === "bearer"){ if (auth.method === "bearer") {
opts.auth = { opts.auth = {
bearer: auth.pass bearer: auth.pass
}; };
} else { } else {
opts.auth = { opts.auth = {
user: auth.user, user: auth.user,
pass: auth.pass pass: auth.pass
}; };
if(auth.method === "digest"){ if (auth.method === "digest") {
opts.auth.sendImmediately = false; opts.auth.sendImmediately = false;
} else { } else {
opts.auth.sendImmediately = true; opts.auth.sendImmediately = true;
@ -53,7 +51,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
} }
} }
ical.fromURL(url, opts, function(err, data) { ical.fromURL(url, opts, function (err, data) {
if (err) { if (err) {
fetchFailedCallback(self, err); fetchFailedCallback(self, err);
scheduleTimer(); scheduleTimer();
@ -61,28 +59,29 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
} }
// console.log(data); // console.log(data);
newEvents = []; var newEvents = [];
// limitFunction doesn't do much limiting, see comment re: the dates array in rrule section below as to why we need to do the filtering ourselves // limitFunction doesn't do much limiting, see comment re: the dates array in rrule section below as to why we need to do the filtering ourselves
var limitFunction = function(date, i) {return true;}; var limitFunction = function (date, i) {
return true;
};
var eventDate = function(event, time) { var eventDate = function (event, time) {
return (event[time].length === 8) ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time])); return event[time].length === 8 ? moment(event[time], "YYYYMMDD") : moment(new Date(event[time]));
}; };
for (var e in data) { for (var e in data) {
var event = data[e]; var event = data[e];
var now = new Date(); var now = new Date();
var today = moment().startOf("day").toDate(); var today = moment().startOf("day").toDate();
var future = moment().startOf("day").add(maximumNumberOfDays, "days").subtract(1,"seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat. var future = moment().startOf("day").add(maximumNumberOfDays, "days").subtract(1, "seconds").toDate(); // Subtract 1 second so that events that start on the middle of the night will not repeat.
var past = today; var past = today;
if (includePastEvents) { if (includePastEvents) {
past = moment().startOf("day").subtract(maximumNumberOfDays, "days").toDate(); past = moment().startOf("day").subtract(maximumNumberOfDays, "days").toDate();
} }
// FIXME: // FIXME: Ugly fix to solve the facebook birthday issue.
// Ugly fix to solve the facebook birthday issue.
// Otherwise, the recurring events only show the birthday for next year. // Otherwise, the recurring events only show the birthday for next year.
var isFacebookBirthday = false; var isFacebookBirthday = false;
if (typeof event.uid !== "undefined") { if (typeof event.uid !== "undefined") {
@ -92,13 +91,12 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
} }
if (event.type === "VEVENT") { if (event.type === "VEVENT") {
var startDate = eventDate(event, "start"); var startDate = eventDate(event, "start");
var endDate; var endDate;
if (typeof event.end !== "undefined") { if (typeof event.end !== "undefined") {
endDate = eventDate(event, "end"); endDate = eventDate(event, "end");
} else if(typeof event.duration !== "undefined") { } else if (typeof event.duration !== "undefined") {
dur=moment.duration(event.duration); var dur = moment.duration(event.duration);
endDate = startDate.clone().add(dur); endDate = startDate.clone().add(dur);
} else { } else {
if (!isFacebookBirthday) { if (!isFacebookBirthday) {
@ -176,8 +174,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
var addedEvents = 0; var addedEvents = 0;
// can cause problems with e.g. birthdays before 1900 // can cause problems with e.g. birthdays before 1900
if(rule.options && rule.origOptions && rule.origOptions.dtstart && rule.origOptions.dtstart.getFullYear() < 1900 || if ((rule.options && rule.origOptions && rule.origOptions.dtstart && rule.origOptions.dtstart.getFullYear() < 1900) || (rule.options && rule.options.dtstart && rule.options.dtstart.getFullYear() < 1900)) {
rule.options && rule.options.dtstart && rule.options.dtstart.getFullYear() < 1900){
rule.origOptions.dtstart.setYear(1900); rule.origOptions.dtstart.setYear(1900);
rule.options.dtstart.setYear(1900); rule.options.dtstart.setYear(1900);
} }
@ -188,7 +185,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
var pastLocal = moment(past).subtract(past.getTimezoneOffset(), "minutes").toDate(); var pastLocal = moment(past).subtract(past.getTimezoneOffset(), "minutes").toDate();
var futureLocal = moment(future).subtract(future.getTimezoneOffset(), "minutes").toDate(); var futureLocal = moment(future).subtract(future.getTimezoneOffset(), "minutes").toDate();
var datesLocal = rule.between(pastLocal, futureLocal, true, limitFunction); var datesLocal = rule.between(pastLocal, futureLocal, true, limitFunction);
var dates = datesLocal.map(function(dateLocal) { var dates = datesLocal.map(function (dateLocal) {
var date = moment(dateLocal).add(dateLocal.getTimezoneOffset(), "minutes").toDate(); var date = moment(dateLocal).add(dateLocal.getTimezoneOffset(), "minutes").toDate();
return date; return date;
}); });
@ -200,17 +197,14 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
// because the logic below will filter out any recurrences that don"t actually belong within // because the logic below will filter out any recurrences that don"t actually belong within
// our display range. // our display range.
// Would be great if there was a better way to handle this. // Would be great if there was a better way to handle this.
if (event.recurrences != undefined) if (event.recurrences !== undefined) {
{
var pastMoment = moment(past); var pastMoment = moment(past);
var futureMoment = moment(future); var futureMoment = moment(future);
for (var r in event.recurrences) for (var r in event.recurrences) {
{
// Only add dates that weren't already in the range we added from the rrule so that // Only add dates that weren't already in the range we added from the rrule so that
// we don"t double-add those events. // we don"t double-add those events.
if (moment(new Date(r)).isBetween(pastMoment, futureMoment) != true) if (moment(new Date(r)).isBetween(pastMoment, futureMoment) !== true) {
{
dates.push(new Date(r)); dates.push(new Date(r));
} }
} }
@ -222,7 +216,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
// ical.js started returning recurrences and exdates as ISOStrings without time information. // ical.js started returning recurrences and exdates as ISOStrings without time information.
// .toISOString().substring(0,10) is the method they use to calculate keys, so we'll do the same // .toISOString().substring(0,10) is the method they use to calculate keys, so we'll do the same
// (see https://github.com/peterbraden/ical.js/pull/84 ) // (see https://github.com/peterbraden/ical.js/pull/84 )
var dateKey = date.toISOString().substring(0,10); var dateKey = date.toISOString().substring(0, 10);
var curEvent = event; var curEvent = event;
var showRecurrence = true; var showRecurrence = true;
@ -235,22 +229,20 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
startDate = moment(date); startDate = moment(date);
// For each date that we"re checking, it"s possible that there is a recurrence override for that one day. // For each date that we"re checking, it"s possible that there is a recurrence override for that one day.
if ((curEvent.recurrences != undefined) && (curEvent.recurrences[dateKey] != undefined)) if (curEvent.recurrences !== undefined && curEvent.recurrences[dateKey] !== undefined) {
{
// We found an override, so for this recurrence, use a potentially different title, start date, and duration. // We found an override, so for this recurrence, use a potentially different title, start date, and duration.
curEvent = curEvent.recurrences[dateKey]; curEvent = curEvent.recurrences[dateKey];
startDate = moment(curEvent.start); startDate = moment(curEvent.start);
duration = parseInt(moment(curEvent.end).format("x")) - parseInt(startDate.format("x")); duration = parseInt(moment(curEvent.end).format("x")) - parseInt(startDate.format("x"));
} }
// If there"s no recurrence override, check for an exception date. Exception dates represent exceptions to the rule. // If there"s no recurrence override, check for an exception date. Exception dates represent exceptions to the rule.
else if ((curEvent.exdate != undefined) && (curEvent.exdate[dateKey] != undefined)) else if (curEvent.exdate !== undefined && curEvent.exdate[dateKey] !== undefined) {
{
// This date is an exception date, which means we should skip it in the recurrence pattern. // This date is an exception date, which means we should skip it in the recurrence pattern.
showRecurrence = false; showRecurrence = false;
} }
endDate = moment(parseInt(startDate.format("x")) + duration, "x"); endDate = moment(parseInt(startDate.format("x")) + duration, "x");
if (startDate.format("x") == endDate.format("x")) { if (startDate.format("x") === endDate.format("x")) {
endDate = endDate.endOf("day"); endDate = endDate.endOf("day");
} }
@ -266,7 +258,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
showRecurrence = false; showRecurrence = false;
} }
if ((showRecurrence === true) && (addedEvents < maximumEntries)) { if (showRecurrence === true && addedEvents < maximumEntries) {
addedEvents++; addedEvents++;
newEvents.push({ newEvents.push({
title: recurrenceTitle, title: recurrenceTitle,
@ -285,7 +277,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
} else { } else {
// console.log("Single event ..."); // console.log("Single event ...");
// Single event. // Single event.
var fullDayEvent = (isFacebookBirthday) ? true : isFullDayEvent(event); var fullDayEvent = isFacebookBirthday ? true : isFullDayEvent(event);
if (includePastEvents) { if (includePastEvents) {
if (endDate < past) { if (endDate < past) {
@ -330,12 +322,11 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
geo: geo, geo: geo,
description: description description: description
}); });
} }
} }
} }
newEvents.sort(function(a, b) { newEvents.sort(function (a, b) {
return a.startDate - b.startDate; return a.startDate - b.startDate;
}); });
@ -351,10 +342,10 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
/* scheduleTimer() /* scheduleTimer()
* Schedule the timer for the next update. * Schedule the timer for the next update.
*/ */
var scheduleTimer = function() { var scheduleTimer = function () {
//console.log('Schedule update timer.'); //console.log('Schedule update timer.');
clearTimeout(reloadTimer); clearTimeout(reloadTimer);
reloadTimer = setTimeout(function() { reloadTimer = setTimeout(function () {
fetchCalendar(); fetchCalendar();
}, reloadInterval); }, reloadInterval);
}; };
@ -366,7 +357,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
* *
* return bool - The event is a fullday event. * return bool - The event is a fullday event.
*/ */
var isFullDayEvent = function(event) { var isFullDayEvent = function (event) {
if (event.start.length === 8 || event.start.dateOnly) { if (event.start.length === 8 || event.start.dateOnly) {
return true; return true;
} }
@ -374,7 +365,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
var start = event.start || 0; var start = event.start || 0;
var startDate = new Date(start); var startDate = new Date(start);
var end = event.end || 0; var end = event.end || 0;
if (((end - start) % (24 * 60 * 60 * 1000)) === 0 && startDate.getHours() === 0 && startDate.getMinutes() === 0) { if ((end - start) % (24 * 60 * 60 * 1000) === 0 && startDate.getHours() === 0 && startDate.getMinutes() === 0) {
// Is 24 hours, and starts on the middle of the night. // Is 24 hours, and starts on the middle of the night.
return true; return true;
} }
@ -391,7 +382,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
* *
* return bool - The event should be filtered out * return bool - The event should be filtered out
*/ */
var timeFilterApplies = function(now, endDate, filter) { var timeFilterApplies = function (now, endDate, filter) {
if (filter) { if (filter) {
var until = filter.split(" "), var until = filter.split(" "),
value = parseInt(until[0]), value = parseInt(until[0]),
@ -405,16 +396,16 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
}; };
/* getTitleFromEvent(event) /* getTitleFromEvent(event)
* Gets the title from the event. * Gets the title from the event.
* *
* argument event object - The event object to check. * argument event object - The event object to check.
* *
* return string - The title of the event, or "Event" if no title is found. * return string - The title of the event, or "Event" if no title is found.
*/ */
var getTitleFromEvent = function (event) { var getTitleFromEvent = function (event) {
var title = "Event"; var title = "Event";
if (event.summary) { if (event.summary) {
title = (typeof event.summary.val !== "undefined") ? event.summary.val : event.summary; title = typeof event.summary.val !== "undefined" ? event.summary.val : event.summary;
} else if (event.description) { } else if (event.description) {
title = event.description; title = event.description;
} }
@ -443,14 +434,14 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
/* startFetch() /* startFetch()
* Initiate fetchCalendar(); * Initiate fetchCalendar();
*/ */
this.startFetch = function() { this.startFetch = function () {
fetchCalendar(); fetchCalendar();
}; };
/* broadcastItems() /* broadcastItems()
* Broadcast the existing events. * Broadcast the existing events.
*/ */
this.broadcastEvents = function() { this.broadcastEvents = function () {
//console.log('Broadcasting ' + events.length + ' events.'); //console.log('Broadcasting ' + events.length + ' events.');
eventsReceivedCallback(self); eventsReceivedCallback(self);
}; };
@ -460,7 +451,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
* *
* argument callback function - The on success callback. * argument callback function - The on success callback.
*/ */
this.onReceive = function(callback) { this.onReceive = function (callback) {
eventsReceivedCallback = callback; eventsReceivedCallback = callback;
}; };
@ -469,7 +460,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
* *
* argument callback function - The on error callback. * argument callback function - The on error callback.
*/ */
this.onError = function(callback) { this.onError = function (callback) {
fetchFailedCallback = callback; fetchFailedCallback = callback;
}; };
@ -478,7 +469,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
* *
* return string - The url of this fetcher. * return string - The url of this fetcher.
*/ */
this.url = function() { this.url = function () {
return url; return url;
}; };
@ -487,7 +478,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
* *
* return array - The current available events for this fetcher. * return array - The current available events for this fetcher.
*/ */
this.events = function() { this.events = function () {
return events; return events;
}; };
}; };

View File

@ -2,7 +2,7 @@
* use this script with `node debug.js` to test the fetcher without the need * use this script with `node debug.js` to test the fetcher without the need
* of starting the MagicMirror core. Adjust the values below to your desire. * of starting the MagicMirror core. Adjust the values below to your desire.
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
@ -15,8 +15,6 @@ var maximumEntries = 10;
var maximumNumberOfDays = 365; var maximumNumberOfDays = 365;
var user = "magicmirror"; var user = "magicmirror";
var pass = "MyStrongPass"; var pass = "MyStrongPass";
var broadcastPastEvents = false;
var auth = { var auth = {
user: user, user: user,
pass: pass pass: pass
@ -24,14 +22,14 @@ var auth = {
console.log("Create fetcher ..."); console.log("Create fetcher ...");
fetcher = new CalendarFetcher(url, fetchInterval, [], maximumEntries, maximumNumberOfDays, auth); var fetcher = new CalendarFetcher(url, fetchInterval, [], maximumEntries, maximumNumberOfDays, auth);
fetcher.onReceive(function(fetcher) { fetcher.onReceive(function (fetcher) {
console.log(fetcher.events()); console.log(fetcher.events());
console.log("------------------------------------------------------------"); console.log("------------------------------------------------------------");
}); });
fetcher.onError(function(fetcher, error) { fetcher.onError(function (fetcher, error) {
console.log("Fetcher error:"); console.log("Fetcher error:");
console.log(error); console.log(error);
}); });

View File

@ -1,7 +1,7 @@
/* Magic Mirror /* Magic Mirror
* Node Helper: Calendar * Node Helper: Calendar
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
@ -11,17 +11,16 @@ var CalendarFetcher = require("./calendarfetcher.js");
module.exports = NodeHelper.create({ module.exports = NodeHelper.create({
// Override start method. // Override start method.
start: function() { start: function () {
var events = []; var events = [];
this.fetchers = []; this.fetchers = [];
console.log("Starting node helper for: " + this.name); console.log("Starting node helper for: " + this.name);
}, },
// Override socketNotificationReceived method. // Override socketNotificationReceived method.
socketNotificationReceived: function(notification, payload) { socketNotificationReceived: function (notification, payload) {
if (notification === "ADD_CALENDAR") { if (notification === "ADD_CALENDAR") {
//console.log('ADD_CALENDAR: '); //console.log('ADD_CALENDAR: ');
this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents); this.createFetcher(payload.url, payload.fetchInterval, payload.excludedEvents, payload.maximumEntries, payload.maximumNumberOfDays, payload.auth, payload.broadcastPastEvents);
@ -36,11 +35,11 @@ module.exports = NodeHelper.create({
* attribute reloadInterval number - Reload interval in milliseconds. * attribute reloadInterval number - Reload interval in milliseconds.
*/ */
createFetcher: function(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents) { createFetcher: function (url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents) {
var self = this; var self = this;
if (!validUrl.isUri(url)) { if (!validUrl.isUri(url)) {
self.sendSocketNotification("INCORRECT_URL", {url: url}); self.sendSocketNotification("INCORRECT_URL", { url: url });
return; return;
} }
@ -49,7 +48,7 @@ module.exports = NodeHelper.create({
console.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval); console.log("Create new calendar fetcher for url: " + url + " - Interval: " + fetchInterval);
fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents); fetcher = new CalendarFetcher(url, fetchInterval, excludedEvents, maximumEntries, maximumNumberOfDays, auth, broadcastPastEvents);
fetcher.onReceive(function(fetcher) { fetcher.onReceive(function (fetcher) {
//console.log('Broadcast events.'); //console.log('Broadcast events.');
//console.log(fetcher.events()); //console.log(fetcher.events());
@ -59,7 +58,7 @@ module.exports = NodeHelper.create({
}); });
}); });
fetcher.onError(function(fetcher, error) { fetcher.onError(function (fetcher, error) {
console.error("Calendar Error. Could not fetch calendar: ", fetcher.url(), error); console.error("Calendar Error. Could not fetch calendar: ", fetcher.url(), error);
self.sendSocketNotification("FETCH_ERROR", { self.sendSocketNotification("FETCH_ERROR", {
url: fetcher.url(), url: fetcher.url(),

View File

@ -1,4 +1,5 @@
# Module: Clock # Module: Clock
The `clock` module is one of the default modules of the MagicMirror. The `clock` module is one of the default modules of the MagicMirror.
This module displays the current date and time. The information will be updated realtime. This module displays the current date and time. The information will be updated realtime.

View File

@ -1,11 +1,12 @@
/* global Log, Module, moment, config */ /* global SunCalc */
/* Magic Mirror /* Magic Mirror
* Module: Clock * Module: Clock
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
Module.register("clock",{ Module.register("clock", {
// Module config defaults. // Module config defaults.
defaults: { defaults: {
displayType: "digital", // options: digital, analog, both displayType: "digital", // options: digital, analog, both
@ -30,18 +31,18 @@ Module.register("clock",{
showSunTimes: false, showSunTimes: false,
showMoonTimes: false, showMoonTimes: false,
lat: 47.630539, lat: 47.630539,
lon: -122.344147, lon: -122.344147
}, },
// Define required scripts. // Define required scripts.
getScripts: function() { getScripts: function () {
return ["moment.js", "moment-timezone.js", "suncalc.js"]; return ["moment.js", "moment-timezone.js", "suncalc.js"];
}, },
// Define styles. // Define styles.
getStyles: function() { getStyles: function () {
return ["clock_styles.css"]; return ["clock_styles.css"];
}, },
// Define start sequence. // Define start sequence.
start: function() { start: function () {
Log.info("Starting module: " + this.name); Log.info("Starting module: " + this.name);
// Schedule update interval. // Schedule update interval.
@ -50,16 +51,16 @@ Module.register("clock",{
self.minute = moment().minute(); self.minute = moment().minute();
//Calculate how many ms should pass until next update depending on if seconds is displayed or not //Calculate how many ms should pass until next update depending on if seconds is displayed or not
var delayCalculator = function(reducedSeconds) { var delayCalculator = function (reducedSeconds) {
if (self.config.displaySeconds) { if (self.config.displaySeconds) {
return 1000 - moment().milliseconds(); return 1000 - moment().milliseconds();
} else { } else {
return ((60 - reducedSeconds) * 1000) - moment().milliseconds(); return (60 - reducedSeconds) * 1000 - moment().milliseconds();
} }
}; };
//A recursive timeout function instead of interval to avoid drifting //A recursive timeout function instead of interval to avoid drifting
var notificationTimer = function() { var notificationTimer = function () {
self.updateDom(); self.updateDom();
//If seconds is displayed CLOCK_SECOND-notification should be sent (but not when CLOCK_MINUTE-notification is sent) //If seconds is displayed CLOCK_SECOND-notification should be sent (but not when CLOCK_MINUTE-notification is sent)
@ -83,11 +84,9 @@ Module.register("clock",{
// Set locale. // Set locale.
moment.locale(config.language); moment.locale(config.language);
}, },
// Override dom generator. // Override dom generator.
getDom: function() { getDom: function () {
var wrapper = document.createElement("div"); var wrapper = document.createElement("div");
/************************************ /************************************
@ -126,12 +125,12 @@ Module.register("clock",{
} }
if (this.config.clockBold === true) { if (this.config.clockBold === true) {
timeString = now.format(hourSymbol + "[<span class=\"bold\">]mm[</span>]"); timeString = now.format(hourSymbol + '[<span class="bold">]mm[</span>]');
} else { } else {
timeString = now.format(hourSymbol + ":mm"); timeString = now.format(hourSymbol + ":mm");
} }
if(this.config.showDate){ if (this.config.showDate) {
dateWrapper.innerHTML = now.format(this.config.dateFormat); dateWrapper.innerHTML = now.format(this.config.dateFormat);
} }
if (this.config.showWeek) { if (this.config.showWeek) {
@ -172,9 +171,18 @@ Module.register("clock",{
} }
const untilNextEvent = moment.duration(moment(nextEvent).diff(now)); const untilNextEvent = moment.duration(moment(nextEvent).diff(now));
const untilNextEventString = untilNextEvent.hours() + "h " + untilNextEvent.minutes() + "m"; const untilNextEventString = untilNextEvent.hours() + "h " + untilNextEvent.minutes() + "m";
sunWrapper.innerHTML = "<span class=\"" + (isVisible ? "bright" : "") + "\"><i class=\"fa fa-sun-o\" aria-hidden=\"true\"></i> " + untilNextEventString + "</span>" + sunWrapper.innerHTML =
"<span><i class=\"fa fa-arrow-up\" aria-hidden=\"true\"></i>" + formatTime(this.config, sunTimes.sunrise) + "</span>" + '<span class="' +
"<span><i class=\"fa fa-arrow-down\" aria-hidden=\"true\"></i>" + formatTime(this.config, sunTimes.sunset) + "</span>"; (isVisible ? "bright" : "") +
'"><i class="fa fa-sun-o" aria-hidden="true"></i> ' +
untilNextEventString +
"</span>" +
'<span><i class="fa fa-arrow-up" aria-hidden="true"></i>' +
formatTime(this.config, sunTimes.sunrise) +
"</span>" +
'<span><i class="fa fa-arrow-down" aria-hidden="true"></i>' +
formatTime(this.config, sunTimes.sunset) +
"</span>";
} }
if (this.config.showMoonTimes) { if (this.config.showMoonTimes) {
const moonIllumination = SunCalc.getMoonIllumination(now.toDate()); const moonIllumination = SunCalc.getMoonIllumination(now.toDate());
@ -189,24 +197,32 @@ Module.register("clock",{
} }
const isVisible = now.isBetween(moonRise, moonSet) || moonTimes.alwaysUp === true; const isVisible = now.isBetween(moonRise, moonSet) || moonTimes.alwaysUp === true;
const illuminatedFractionString = Math.round(moonIllumination.fraction * 100) + "%"; const illuminatedFractionString = Math.round(moonIllumination.fraction * 100) + "%";
moonWrapper.innerHTML = "<span class=\"" + (isVisible ? "bright" : "") + "\"><i class=\"fa fa-moon-o\" aria-hidden=\"true\"></i> " + illuminatedFractionString + "</span>" + moonWrapper.innerHTML =
"<span><i class=\"fa fa-arrow-up\" aria-hidden=\"true\"></i> " + (moonRise ? formatTime(this.config, moonRise) : "...") + "</span>"+ '<span class="' +
"<span><i class=\"fa fa-arrow-down\" aria-hidden=\"true\"></i> " + (moonSet ? formatTime(this.config, moonSet) : "...") + "</span>"; (isVisible ? "bright" : "") +
'"><i class="fa fa-moon-o" aria-hidden="true"></i> ' +
illuminatedFractionString +
"</span>" +
'<span><i class="fa fa-arrow-up" aria-hidden="true"></i> ' +
(moonRise ? formatTime(this.config, moonRise) : "...") +
"</span>" +
'<span><i class="fa fa-arrow-down" aria-hidden="true"></i> ' +
(moonSet ? formatTime(this.config, moonSet) : "...") +
"</span>";
} }
/**************************************************************** /****************************************************************
* Create wrappers for ANALOG clock, only if specified in config * Create wrappers for ANALOG clock, only if specified in config
*/ */
if (this.config.displayType !== "digital") { if (this.config.displayType !== "digital") {
// If it isn't 'digital', then an 'analog' clock was also requested // If it isn't 'digital', then an 'analog' clock was also requested
// Calculate the degree offset for each hand of the clock // Calculate the degree offset for each hand of the clock
var now = moment();
if (this.config.timezone) { if (this.config.timezone) {
now.tz(this.config.timezone); now.tz(this.config.timezone);
} }
var second = now.seconds() * 6, var second = now.seconds() * 6,
minute = now.minute() * 6 + second / 60, minute = now.minute() * 6 + second / 60,
hour = ((now.hours() % 12) / 12) * 360 + 90 + minute / 12; hour = ((now.hours() % 12) / 12) * 360 + 90 + minute / 12;
@ -217,13 +233,12 @@ Module.register("clock",{
clockCircle.style.height = this.config.analogSize; clockCircle.style.height = this.config.analogSize;
if (this.config.analogFace !== "" && this.config.analogFace !== "simple" && this.config.analogFace !== "none") { if (this.config.analogFace !== "" && this.config.analogFace !== "simple" && this.config.analogFace !== "none") {
clockCircle.style.background = "url("+ this.data.path + "faces/" + this.config.analogFace + ".svg)"; clockCircle.style.background = "url(" + this.data.path + "faces/" + this.config.analogFace + ".svg)";
clockCircle.style.backgroundSize = "100%"; clockCircle.style.backgroundSize = "100%";
// The following line solves issue: https://github.com/MichMich/MagicMirror/issues/611 // The following line solves issue: https://github.com/MichMich/MagicMirror/issues/611
// clockCircle.style.border = "1px solid black"; // clockCircle.style.border = "1px solid black";
clockCircle.style.border = "rgba(0, 0, 0, 0.1)"; //Updated fix for Issue 611 where non-black backgrounds are used clockCircle.style.border = "rgba(0, 0, 0, 0.1)"; //Updated fix for Issue 611 where non-black backgrounds are used
} else if (this.config.analogFace !== "none") { } else if (this.config.analogFace !== "none") {
clockCircle.style.border = "2px solid white"; clockCircle.style.border = "2px solid white";
} }
@ -289,11 +304,12 @@ Module.register("clock",{
// Both clocks have been configured, check position // Both clocks have been configured, check position
var placement = this.config.analogPlacement; var placement = this.config.analogPlacement;
analogWrapper = document.createElement("div"); var analogWrapper = document.createElement("div");
analogWrapper.id = "analog"; analogWrapper.id = "analog";
analogWrapper.style.cssFloat = "none"; analogWrapper.style.cssFloat = "none";
analogWrapper.appendChild(clockCircle); analogWrapper.appendChild(clockCircle);
digitalWrapper = document.createElement("div");
var digitalWrapper = document.createElement("div");
digitalWrapper.id = "digital"; digitalWrapper.id = "digital";
digitalWrapper.style.cssFloat = "none"; digitalWrapper.style.cssFloat = "none";
digitalWrapper.appendChild(dateWrapper); digitalWrapper.appendChild(dateWrapper);
@ -302,9 +318,9 @@ Module.register("clock",{
digitalWrapper.appendChild(moonWrapper); digitalWrapper.appendChild(moonWrapper);
digitalWrapper.appendChild(weekWrapper); digitalWrapper.appendChild(weekWrapper);
var appendClocks = function(condition, pos1, pos2) { var appendClocks = function (condition, pos1, pos2) {
var padding = [0,0,0,0]; var padding = [0, 0, 0, 0];
padding[(placement === condition) ? pos1 : pos2] = "20px"; padding[placement === condition ? pos1 : pos2] = "20px";
analogWrapper.style.padding = padding.join(" "); analogWrapper.style.padding = padding.join(" ");
if (placement === condition) { if (placement === condition) {
wrapper.appendChild(analogWrapper); wrapper.appendChild(analogWrapper);

View File

@ -29,11 +29,9 @@
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
margin: -2px 0 -2px -25%; /* numbers much match negative length & thickness */ margin: -2px 0 -2px -25%; /* numbers much match negative length & thickness */
padding: 2px 0 2px 25%; /* indicator length & thickness */ padding: 2px 0 2px 25%; /* indicator length & thickness */
background: white; background: white;
-webkit-transform-origin: 100% 50%;
-ms-transform-origin: 100% 50%;
transform-origin: 100% 50%; transform-origin: 100% 50%;
border-radius: 3px 0 0 3px; border-radius: 3px 0 0 3px;
} }
@ -44,11 +42,9 @@
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
margin: -35% -2px 0; /* numbers must match negative length & thickness */ margin: -35% -2px 0; /* numbers must match negative length & thickness */
padding: 35% 2px 0; /* indicator length & thickness */ padding: 35% 2px 0; /* indicator length & thickness */
background: white; background: white;
-webkit-transform-origin: 50% 100%;
-ms-transform-origin: 50% 100%;
transform-origin: 50% 100%; transform-origin: 50% 100%;
border-radius: 3px 0 0 3px; border-radius: 3px 0 0 3px;
} }
@ -59,11 +55,9 @@
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
margin: -38% -1px 0 0; /* numbers must match negative length & thickness */ margin: -38% -1px 0 0; /* numbers must match negative length & thickness */
padding: 38% 1px 0 0; /* indicator length & thickness */ padding: 38% 1px 0 0; /* indicator length & thickness */
background: #888; background: #888;
-webkit-transform-origin: 50% 100%;
-ms-transform-origin: 50% 100%;
transform-origin: 50% 100%; transform-origin: 50% 100%;
} }

View File

@ -1 +1 @@
<svg id="Hour_Markers_-_Singlets" data-name="Hour Markers - Singlets" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1,.cls-2{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;}.cls-2{stroke-width:0.5px;}</style></defs><title>face-001</title><line class="cls-1" x1="125" y1="1.25" x2="125" y2="16.23"/><line class="cls-1" x1="186.87" y1="17.83" x2="179.39" y2="30.8"/><line class="cls-1" x1="232.17" y1="63.12" x2="219.2" y2="70.61"/><line class="cls-1" x1="248.75" y1="125" x2="233.77" y2="125"/><line class="cls-1" x1="232.17" y1="186.87" x2="219.2" y2="179.39"/><line class="cls-1" x1="186.88" y1="232.17" x2="179.39" y2="219.2"/><line class="cls-1" x1="125" y1="248.75" x2="125" y2="233.77"/><line class="cls-1" x1="63.13" y1="232.17" x2="70.61" y2="219.2"/><line class="cls-1" x1="17.83" y1="186.88" x2="30.8" y2="179.39"/><line class="cls-1" x1="1.25" y1="125" x2="16.23" y2="125"/><line class="cls-1" x1="17.83" y1="63.13" x2="30.8" y2="70.61"/><line class="cls-1" x1="63.12" y1="17.83" x2="70.61" y2="30.8"/><line class="cls-2" x1="138.01" y1="1.25" x2="136.96" y2="11.23"/><line class="cls-2" x1="150.87" y1="3.29" x2="148.78" y2="13.11"/><line class="cls-2" x1="163.45" y1="6.66" x2="160.35" y2="16.21"/><line class="cls-2" x1="175.61" y1="11.33" x2="171.53" y2="20.5"/><line class="cls-2" x1="198.14" y1="24.33" x2="192.24" y2="32.45"/><line class="cls-2" x1="208.26" y1="32.53" x2="201.54" y2="39.99"/><line class="cls-2" x1="217.47" y1="41.74" x2="210.01" y2="48.46"/><line class="cls-2" x1="225.67" y1="51.86" x2="217.55" y2="57.76"/><line class="cls-2" x1="238.67" y1="74.39" x2="229.5" y2="78.47"/><line class="cls-2" x1="243.34" y1="86.55" x2="233.79" y2="89.65"/><line class="cls-2" x1="246.71" y1="99.13" x2="236.89" y2="101.22"/><line class="cls-2" x1="248.75" y1="111.99" x2="238.77" y2="113.04"/><line class="cls-2" x1="248.75" y1="138.01" x2="238.77" y2="136.96"/><line class="cls-2" x1="246.71" y1="150.87" x2="236.89" y2="148.78"/><line class="cls-2" x1="243.34" y1="163.45" x2="233.79" y2="160.35"/><line class="cls-2" x1="238.67" y1="175.61" x2="229.5" y2="171.53"/><line class="cls-2" x1="225.67" y1="198.14" x2="217.55" y2="192.24"/><line class="cls-2" x1="217.47" y1="208.26" x2="210.01" y2="201.54"/><line class="cls-2" x1="208.26" y1="217.47" x2="201.54" y2="210.01"/><line class="cls-2" x1="198.14" y1="225.67" x2="192.24" y2="217.55"/><line class="cls-2" x1="175.61" y1="238.67" x2="171.53" y2="229.5"/><line class="cls-2" x1="163.45" y1="243.34" x2="160.35" y2="233.79"/><line class="cls-2" x1="150.87" y1="246.71" x2="148.78" y2="236.89"/><line class="cls-2" x1="138.01" y1="248.75" x2="136.96" y2="238.77"/><line class="cls-2" x1="111.99" y1="248.75" x2="113.04" y2="238.77"/><line class="cls-2" x1="99.13" y1="246.71" x2="101.22" y2="236.89"/><line class="cls-2" x1="86.55" y1="243.34" x2="89.65" y2="233.79"/><line class="cls-2" x1="74.39" y1="238.67" x2="78.47" y2="229.5"/><line class="cls-2" x1="51.86" y1="225.67" x2="57.76" y2="217.55"/><line class="cls-2" x1="41.74" y1="217.47" x2="48.46" y2="210.01"/><line class="cls-2" x1="32.53" y1="208.26" x2="39.99" y2="201.54"/><line class="cls-2" x1="24.33" y1="198.14" x2="32.45" y2="192.24"/><line class="cls-2" x1="11.33" y1="175.61" x2="20.5" y2="171.53"/><line class="cls-2" x1="6.66" y1="163.45" x2="16.21" y2="160.35"/><line class="cls-2" x1="3.29" y1="150.87" x2="13.11" y2="148.78"/><line class="cls-2" x1="1.25" y1="138.01" x2="11.23" y2="136.96"/><line class="cls-2" x1="1.25" y1="111.99" x2="11.23" y2="113.04"/><line class="cls-2" x1="3.29" y1="99.13" x2="13.11" y2="101.22"/><line class="cls-2" x1="6.66" y1="86.55" x2="16.21" y2="89.65"/><line class="cls-2" x1="11.33" y1="74.39" x2="20.5" y2="78.47"/><line class="cls-2" x1="24.33" y1="51.86" x2="32.45" y2="57.76"/><line class="cls-2" x1="32.53" y1="41.74" x2="39.99" y2="48.46"/><line class="cls-2" x1="41.74" y1="32.53" x2="48.46" y2="39.99"/><line class="cls-2" x1="51.86" y1="24.33" x2="57.76" y2="32.45"/><line class="cls-2" x1="74.39" y1="11.33" x2="78.47" y2="20.5"/><line class="cls-2" x1="86.55" y1="6.66" x2="89.65" y2="16.21"/><line class="cls-2" x1="99.13" y1="3.29" x2="101.22" y2="13.11"/><line class="cls-2" x1="111.99" y1="1.25" x2="113.04" y2="11.23"/></svg> <svg id="Hour_Markers_-_Singlets" data-name="Hour Markers - Singlets" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1,.cls-2{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;}.cls-2{stroke-width:0.5px;}</style></defs><title>face-001</title><line class="cls-1" x1="125" y1="1.25" x2="125" y2="16.23"/><line class="cls-1" x1="186.87" y1="17.83" x2="179.39" y2="30.8"/><line class="cls-1" x1="232.17" y1="63.12" x2="219.2" y2="70.61"/><line class="cls-1" x1="248.75" y1="125" x2="233.77" y2="125"/><line class="cls-1" x1="232.17" y1="186.87" x2="219.2" y2="179.39"/><line class="cls-1" x1="186.88" y1="232.17" x2="179.39" y2="219.2"/><line class="cls-1" x1="125" y1="248.75" x2="125" y2="233.77"/><line class="cls-1" x1="63.13" y1="232.17" x2="70.61" y2="219.2"/><line class="cls-1" x1="17.83" y1="186.88" x2="30.8" y2="179.39"/><line class="cls-1" x1="1.25" y1="125" x2="16.23" y2="125"/><line class="cls-1" x1="17.83" y1="63.13" x2="30.8" y2="70.61"/><line class="cls-1" x1="63.12" y1="17.83" x2="70.61" y2="30.8"/><line class="cls-2" x1="138.01" y1="1.25" x2="136.96" y2="11.23"/><line class="cls-2" x1="150.87" y1="3.29" x2="148.78" y2="13.11"/><line class="cls-2" x1="163.45" y1="6.66" x2="160.35" y2="16.21"/><line class="cls-2" x1="175.61" y1="11.33" x2="171.53" y2="20.5"/><line class="cls-2" x1="198.14" y1="24.33" x2="192.24" y2="32.45"/><line class="cls-2" x1="208.26" y1="32.53" x2="201.54" y2="39.99"/><line class="cls-2" x1="217.47" y1="41.74" x2="210.01" y2="48.46"/><line class="cls-2" x1="225.67" y1="51.86" x2="217.55" y2="57.76"/><line class="cls-2" x1="238.67" y1="74.39" x2="229.5" y2="78.47"/><line class="cls-2" x1="243.34" y1="86.55" x2="233.79" y2="89.65"/><line class="cls-2" x1="246.71" y1="99.13" x2="236.89" y2="101.22"/><line class="cls-2" x1="248.75" y1="111.99" x2="238.77" y2="113.04"/><line class="cls-2" x1="248.75" y1="138.01" x2="238.77" y2="136.96"/><line class="cls-2" x1="246.71" y1="150.87" x2="236.89" y2="148.78"/><line class="cls-2" x1="243.34" y1="163.45" x2="233.79" y2="160.35"/><line class="cls-2" x1="238.67" y1="175.61" x2="229.5" y2="171.53"/><line class="cls-2" x1="225.67" y1="198.14" x2="217.55" y2="192.24"/><line class="cls-2" x1="217.47" y1="208.26" x2="210.01" y2="201.54"/><line class="cls-2" x1="208.26" y1="217.47" x2="201.54" y2="210.01"/><line class="cls-2" x1="198.14" y1="225.67" x2="192.24" y2="217.55"/><line class="cls-2" x1="175.61" y1="238.67" x2="171.53" y2="229.5"/><line class="cls-2" x1="163.45" y1="243.34" x2="160.35" y2="233.79"/><line class="cls-2" x1="150.87" y1="246.71" x2="148.78" y2="236.89"/><line class="cls-2" x1="138.01" y1="248.75" x2="136.96" y2="238.77"/><line class="cls-2" x1="111.99" y1="248.75" x2="113.04" y2="238.77"/><line class="cls-2" x1="99.13" y1="246.71" x2="101.22" y2="236.89"/><line class="cls-2" x1="86.55" y1="243.34" x2="89.65" y2="233.79"/><line class="cls-2" x1="74.39" y1="238.67" x2="78.47" y2="229.5"/><line class="cls-2" x1="51.86" y1="225.67" x2="57.76" y2="217.55"/><line class="cls-2" x1="41.74" y1="217.47" x2="48.46" y2="210.01"/><line class="cls-2" x1="32.53" y1="208.26" x2="39.99" y2="201.54"/><line class="cls-2" x1="24.33" y1="198.14" x2="32.45" y2="192.24"/><line class="cls-2" x1="11.33" y1="175.61" x2="20.5" y2="171.53"/><line class="cls-2" x1="6.66" y1="163.45" x2="16.21" y2="160.35"/><line class="cls-2" x1="3.29" y1="150.87" x2="13.11" y2="148.78"/><line class="cls-2" x1="1.25" y1="138.01" x2="11.23" y2="136.96"/><line class="cls-2" x1="1.25" y1="111.99" x2="11.23" y2="113.04"/><line class="cls-2" x1="3.29" y1="99.13" x2="13.11" y2="101.22"/><line class="cls-2" x1="6.66" y1="86.55" x2="16.21" y2="89.65"/><line class="cls-2" x1="11.33" y1="74.39" x2="20.5" y2="78.47"/><line class="cls-2" x1="24.33" y1="51.86" x2="32.45" y2="57.76"/><line class="cls-2" x1="32.53" y1="41.74" x2="39.99" y2="48.46"/><line class="cls-2" x1="41.74" y1="32.53" x2="48.46" y2="39.99"/><line class="cls-2" x1="51.86" y1="24.33" x2="57.76" y2="32.45"/><line class="cls-2" x1="74.39" y1="11.33" x2="78.47" y2="20.5"/><line class="cls-2" x1="86.55" y1="6.66" x2="89.65" y2="16.21"/><line class="cls-2" x1="99.13" y1="3.29" x2="101.22" y2="13.11"/><line class="cls-2" x1="111.99" y1="1.25" x2="113.04" y2="11.23"/></svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1 +1 @@
<svg id="Hour_Markers_-_Doubles" data-name="Hour Markers - Doubles" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;stroke-width:2.98px;}</style></defs><title>face-002</title><line class="cls-1" x1="122.01" y1="1.75" x2="122.01" y2="16.67"/><line class="cls-1" x1="186.62" y1="18.26" x2="179.17" y2="31.18"/><line class="cls-1" x1="231.74" y1="63.37" x2="218.82" y2="70.83"/><line class="cls-1" x1="248.25" y1="127.99" x2="233.33" y2="127.99"/><line class="cls-1" x1="231.74" y1="186.62" x2="218.82" y2="179.17"/><line class="cls-1" x1="186.63" y1="231.74" x2="179.17" y2="218.82"/><line class="cls-1" x1="127.99" y1="248.25" x2="127.99" y2="233.33"/><line class="cls-1" x1="63.38" y1="231.74" x2="70.83" y2="218.82"/><line class="cls-1" x1="18.26" y1="186.63" x2="31.18" y2="179.17"/><line class="cls-1" x1="1.75" y1="122.01" x2="16.67" y2="122.01"/><line class="cls-1" x1="18.26" y1="63.38" x2="31.18" y2="70.83"/><line class="cls-1" x1="63.37" y1="18.26" x2="70.83" y2="31.18"/><line class="cls-1" x1="127.99" y1="1.75" x2="127.99" y2="16.67"/><line class="cls-1" x1="248.25" y1="122.01" x2="233.33" y2="122.01"/><line class="cls-1" x1="122.01" y1="248.25" x2="122.01" y2="233.33"/><line class="cls-1" x1="1.75" y1="127.99" x2="16.67" y2="127.99"/></svg> <svg id="Hour_Markers_-_Doubles" data-name="Hour Markers - Doubles" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 250 250"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-linecap:round;stroke-miterlimit:10;stroke-width:2.98px;}</style></defs><title>face-002</title><line class="cls-1" x1="122.01" y1="1.75" x2="122.01" y2="16.67"/><line class="cls-1" x1="186.62" y1="18.26" x2="179.17" y2="31.18"/><line class="cls-1" x1="231.74" y1="63.37" x2="218.82" y2="70.83"/><line class="cls-1" x1="248.25" y1="127.99" x2="233.33" y2="127.99"/><line class="cls-1" x1="231.74" y1="186.62" x2="218.82" y2="179.17"/><line class="cls-1" x1="186.63" y1="231.74" x2="179.17" y2="218.82"/><line class="cls-1" x1="127.99" y1="248.25" x2="127.99" y2="233.33"/><line class="cls-1" x1="63.38" y1="231.74" x2="70.83" y2="218.82"/><line class="cls-1" x1="18.26" y1="186.63" x2="31.18" y2="179.17"/><line class="cls-1" x1="1.75" y1="122.01" x2="16.67" y2="122.01"/><line class="cls-1" x1="18.26" y1="63.38" x2="31.18" y2="70.83"/><line class="cls-1" x1="63.37" y1="18.26" x2="70.83" y2="31.18"/><line class="cls-1" x1="127.99" y1="1.75" x2="127.99" y2="16.67"/><line class="cls-1" x1="248.25" y1="122.01" x2="233.33" y2="122.01"/><line class="cls-1" x1="122.01" y1="248.25" x2="122.01" y2="233.33"/><line class="cls-1" x1="1.75" y1="127.99" x2="16.67" y2="127.99"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -1,4 +1,5 @@
# Module: Compliments # Module: Compliments
The `compliments` module is one of the default modules of the MagicMirror. The `compliments` module is one of the default modules of the MagicMirror.
This module displays a random compliment. This module displays a random compliment.

View File

@ -1,37 +1,18 @@
/* global Log, Module, moment */
/* Magic Mirror /* Magic Mirror
* Module: Compliments * Module: Compliments
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
Module.register("compliments", { Module.register("compliments", {
// Module config defaults. // Module config defaults.
defaults: { defaults: {
compliments: { compliments: {
anytime: [ anytime: ["Hey there sexy!"],
"Hey there sexy!" morning: ["Good morning, handsome!", "Enjoy your day!", "How was your sleep?"],
], afternoon: ["Hello, beauty!", "You look sexy!", "Looking good today!"],
morning: [ evening: ["Wow, you look hot!", "You look nice!", "Hi, sexy!"],
"Good morning, handsome!", "....-01-01": ["Happy new year!"]
"Enjoy your day!",
"How was your sleep?"
],
afternoon: [
"Hello, beauty!",
"You look sexy!",
"Looking good today!"
],
evening: [
"Wow, you look hot!",
"You look nice!",
"Hi, sexy!"
],
"....-01-01": [
"Happy new year!"
]
}, },
updateInterval: 30000, updateInterval: 30000,
remoteFile: null, remoteFile: null,
@ -41,33 +22,46 @@ Module.register("compliments", {
afternoonStartTime: 12, afternoonStartTime: 12,
afternoonEndTime: 17, afternoonEndTime: 17,
random: true, random: true,
mockDate: null mockDate: null,
advice: false
}, },
lastIndexUsed:-1, lastIndexUsed: -1,
// Set currentweather from module // Set currentweather from module
currentWeatherType: "", currentWeatherType: "",
// Define required scripts. // Define required scripts.
getScripts: function() { getScripts: function () {
return ["moment.js"]; return ["moment.js"];
}, },
// Define start sequence. // Define start sequence.
start: function() { start: function () {
Log.info("Starting module: " + this.name); Log.info("Starting module: " + this.name);
this.lastComplimentIndex = -1; this.lastComplimentIndex = -1;
var self = this; var self = this;
if (this.config.remoteFile !== null) { if (this.config.remoteFile !== null) {
this.complimentFile(function(response) { this.complimentFile(function (response) {
self.config.compliments = JSON.parse(response); self.config.compliments = JSON.parse(response);
self.updateDom(); self.updateDom();
}); });
} else if (this.config.advice) {
var xobj = new XMLHttpRequest();
xobj.overrideMimeType("application/json");
xobj.open("GET", "https://api.adviceslip.com/advice", true);
xobj.onreadystatechange = function () {
if (xobj.readyState === 4 && xobj.status === 200) {
const adviceResp = JSON.parse(xobj.responseText);
self.config.compliments = adviceResp.slip.advice;
self.updateDom();
}
};
xobj.send(null);
} }
// Schedule update timer. // Schedule update timer.
setInterval(function() { setInterval(function () {
self.updateDom(self.config.fadeSpeed); self.updateDom(self.config.fadeSpeed);
}, this.config.updateInterval); }, this.config.updateInterval);
}, },
@ -79,12 +73,12 @@ Module.register("compliments", {
* *
* return Number - Random index. * return Number - Random index.
*/ */
randomIndex: function(compliments) { randomIndex: function (compliments) {
if (compliments.length === 1) { if (compliments.length === 1) {
return 0; return 0;
} }
var generate = function() { var generate = function () {
return Math.floor(Math.random() * compliments.length); return Math.floor(Math.random() * compliments.length);
}; };
@ -104,7 +98,7 @@ Module.register("compliments", {
* *
* return compliments Array<String> - Array with compliments for the time of the day. * return compliments Array<String> - Array with compliments for the time of the day.
*/ */
complimentArray: function() { complimentArray: function () {
var hour = moment().hour(); var hour = moment().hour();
var date = this.config.mockDate ? this.config.mockDate : moment().format("YYYY-MM-DD"); var date = this.config.mockDate ? this.config.mockDate : moment().format("YYYY-MM-DD");
var compliments; var compliments;
@ -113,7 +107,7 @@ Module.register("compliments", {
compliments = this.config.compliments.morning.slice(0); compliments = this.config.compliments.morning.slice(0);
} else if (hour >= this.config.afternoonStartTime && hour < this.config.afternoonEndTime && this.config.compliments.hasOwnProperty("afternoon")) { } else if (hour >= this.config.afternoonStartTime && hour < this.config.afternoonEndTime && this.config.compliments.hasOwnProperty("afternoon")) {
compliments = this.config.compliments.afternoon.slice(0); compliments = this.config.compliments.afternoon.slice(0);
} else if(this.config.compliments.hasOwnProperty("evening")) { } else if (this.config.compliments.hasOwnProperty("evening")) {
compliments = this.config.compliments.evening.slice(0); compliments = this.config.compliments.evening.slice(0);
} }
@ -127,7 +121,7 @@ Module.register("compliments", {
compliments.push.apply(compliments, this.config.compliments.anytime); compliments.push.apply(compliments, this.config.compliments.anytime);
for (entry in this.config.compliments) { for (var entry in this.config.compliments) {
if (new RegExp(entry).test(date)) { if (new RegExp(entry).test(date)) {
compliments.push.apply(compliments, this.config.compliments[entry]); compliments.push.apply(compliments, this.config.compliments[entry]);
} }
@ -139,13 +133,13 @@ Module.register("compliments", {
/* complimentFile(callback) /* complimentFile(callback)
* Retrieve a file from the local filesystem * Retrieve a file from the local filesystem
*/ */
complimentFile: function(callback) { complimentFile: function (callback) {
var xobj = new XMLHttpRequest(), var xobj = new XMLHttpRequest(),
isRemote = this.config.remoteFile.indexOf("http://") === 0 || this.config.remoteFile.indexOf("https://") === 0, isRemote = this.config.remoteFile.indexOf("http://") === 0 || this.config.remoteFile.indexOf("https://") === 0,
path = isRemote ? this.config.remoteFile : this.file(this.config.remoteFile); path = isRemote ? this.config.remoteFile : this.file(this.config.remoteFile);
xobj.overrideMimeType("application/json"); xobj.overrideMimeType("application/json");
xobj.open("GET", path, true); xobj.open("GET", path, true);
xobj.onreadystatechange = function() { xobj.onreadystatechange = function () {
if (xobj.readyState === 4 && xobj.status === 200) { if (xobj.readyState === 4 && xobj.status === 200) {
callback(xobj.responseText); callback(xobj.responseText);
} }
@ -158,27 +152,26 @@ Module.register("compliments", {
* *
* return compliment string - A compliment. * return compliment string - A compliment.
*/ */
randomCompliment: function() { randomCompliment: function () {
// get the current time of day compliments list // get the current time of day compliments list
var compliments = this.complimentArray(); var compliments = this.complimentArray();
// variable for index to next message to display // variable for index to next message to display
let index = 0; let index = 0;
// are we randomizing // are we randomizing
if(this.config.random){ if (this.config.random) {
// yes // yes
index = this.randomIndex(compliments); index = this.randomIndex(compliments);
} } else {
else{
// no, sequential // no, sequential
// if doing sequential, don't fall off the end // if doing sequential, don't fall off the end
index = (this.lastIndexUsed >= (compliments.length-1))?0: ++this.lastIndexUsed; index = this.lastIndexUsed >= compliments.length - 1 ? 0 : ++this.lastIndexUsed;
} }
return compliments[index] || ""; return compliments[index] || "";
}, },
// Override dom generator. // Override dom generator.
getDom: function() { getDom: function () {
var wrapper = document.createElement("div"); var wrapper = document.createElement("div");
wrapper.className = this.config.classes ? this.config.classes : "thin xlarge bright pre-line"; wrapper.className = this.config.classes ? this.config.classes : "thin xlarge bright pre-line";
// get the compliment text // get the compliment text
@ -188,7 +181,7 @@ Module.register("compliments", {
// create a span to hold it all // create a span to hold it all
var compliment = document.createElement("span"); var compliment = document.createElement("span");
// process all the parts of the compliment text // process all the parts of the compliment text
for (part of parts){ for (var part of parts) {
// create a text element for each part // create a text element for each part
compliment.appendChild(document.createTextNode(part)); compliment.appendChild(document.createTextNode(part));
// add a break ` // add a break `
@ -202,7 +195,7 @@ Module.register("compliments", {
}, },
// From data currentweather set weather type // From data currentweather set weather type
setCurrentWeatherType: function(data) { setCurrentWeatherType: function (data) {
var weatherIconTable = { var weatherIconTable = {
"01d": "day_sunny", "01d": "day_sunny",
"02d": "day_cloudy", "02d": "day_cloudy",
@ -227,10 +220,9 @@ Module.register("compliments", {
}, },
// Override notification handler. // Override notification handler.
notificationReceived: function(notification, payload, sender) { notificationReceived: function (notification, payload, sender) {
if (notification === "CURRENTWEATHER_DATA") { if (notification === "CURRENTWEATHER_DATA") {
this.setCurrentWeatherType(payload.data); this.setCurrentWeatherType(payload.data);
} }
}, }
}); });

View File

@ -1,4 +1,5 @@
# Module: Current Weather # Module: Current Weather
The `currentweather` module is one of the default modules of the MagicMirror. The `currentweather` module is one of the default modules of the MagicMirror.
This module displays the current weather, including the windspeed, the sunset or sunrise time, the temperature and an icon to display the current conditions. This module displays the current weather, including the windspeed, the sunset or sunrise time, the temperature and an icon to display the current conditions.

View File

@ -3,8 +3,6 @@
font-size: 75%; font-size: 75%;
line-height: 65px; line-height: 65px;
display: inline-block; display: inline-block;
-ms-transform: translate(0, -3px); /* IE 9 */
-webkit-transform: translate(0, -3px); /* Safari */
transform: translate(0, -3px); transform: translate(0, -3px);
} }

View File

@ -1,14 +1,10 @@
/* global Module */
/* Magic Mirror /* Magic Mirror
* Module: CurrentWeather * Module: CurrentWeather
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
Module.register("currentweather", {
Module.register("currentweather",{
// Default module config. // Default module config.
defaults: { defaults: {
location: false, location: false,
@ -23,7 +19,6 @@ Module.register("currentweather",{
showWindDirection: true, showWindDirection: true,
showWindDirectionAsArrow: false, showWindDirectionAsArrow: false,
useBeaufort: true, useBeaufort: true,
appendLocationNameToHeader: false,
useKMPHwind: false, useKMPHwind: false,
lang: config.language, lang: config.language,
decimalSymbol: ".", decimalSymbol: ".",
@ -67,7 +62,7 @@ Module.register("currentweather",{
"11n": "wi-night-thunderstorm", "11n": "wi-night-thunderstorm",
"13n": "wi-night-snow", "13n": "wi-night-snow",
"50n": "wi-night-alt-cloudy-windy" "50n": "wi-night-alt-cloudy-windy"
}, }
}, },
// create a variable for the first upcoming calendar event. Used if no location is specified. // create a variable for the first upcoming calendar event. Used if no location is specified.
@ -77,17 +72,17 @@ Module.register("currentweather",{
fetchedLocationName: "", fetchedLocationName: "",
// Define required scripts. // Define required scripts.
getScripts: function() { getScripts: function () {
return ["moment.js"]; return ["moment.js"];
}, },
// Define required scripts. // Define required scripts.
getStyles: function() { getStyles: function () {
return ["weather-icons.css", "currentweather.css"]; return ["weather-icons.css", "currentweather.css"];
}, },
// Define required translations. // Define required translations.
getTranslations: function() { getTranslations: function () {
// The translations for the default modules are defined in the core translation files. // The translations for the default modules are defined in the core translation files.
// Therefor we can just return false. Otherwise we should have returned a dictionary. // Therefor we can just return false. Otherwise we should have returned a dictionary.
// If you're trying to build your own module including translations, check out the documentation. // If you're trying to build your own module including translations, check out the documentation.
@ -95,7 +90,7 @@ Module.register("currentweather",{
}, },
// Define start sequence. // Define start sequence.
start: function() { start: function () {
Log.info("Starting module: " + this.name); Log.info("Starting module: " + this.name);
// Set locale. // Set locale.
@ -113,13 +108,11 @@ Module.register("currentweather",{
this.feelsLike = null; this.feelsLike = null;
this.loaded = false; this.loaded = false;
this.scheduleUpdate(this.config.initialLoadDelay); this.scheduleUpdate(this.config.initialLoadDelay);
}, },
// add extra information of current weather // add extra information of current weather
// windDirection, humidity, sunrise and sunset // windDirection, humidity, sunrise and sunset
addExtraInfoWeather: function(wrapper) { addExtraInfoWeather: function (wrapper) {
var small = document.createElement("div"); var small = document.createElement("div");
small.className = "normal medium"; small.className = "normal medium";
@ -134,8 +127,8 @@ Module.register("currentweather",{
if (this.config.showWindDirection) { if (this.config.showWindDirection) {
var windDirection = document.createElement("sup"); var windDirection = document.createElement("sup");
if (this.config.showWindDirectionAsArrow) { if (this.config.showWindDirectionAsArrow) {
if(this.windDeg !== null) { if (this.windDeg !== null) {
windDirection.innerHTML = " &nbsp;<i class=\"fa fa-long-arrow-down\" style=\"transform:rotate("+this.windDeg+"deg);\"></i>&nbsp;"; windDirection.innerHTML = ' &nbsp;<i class="fa fa-long-arrow-down" style="transform:rotate(' + this.windDeg + 'deg);"></i>&nbsp;';
} }
} else { } else {
windDirection.innerHTML = " " + this.translate(this.windDirection); windDirection.innerHTML = " " + this.translate(this.windDirection);
@ -150,15 +143,15 @@ Module.register("currentweather",{
var humidity = document.createElement("span"); var humidity = document.createElement("span");
humidity.innerHTML = this.humidity; humidity.innerHTML = this.humidity;
var spacer = document.createElement("sup"); var supspacer = document.createElement("sup");
spacer.innerHTML = "&nbsp;"; supspacer.innerHTML = "&nbsp;";
var humidityIcon = document.createElement("sup"); var humidityIcon = document.createElement("sup");
humidityIcon.className = "wi wi-humidity humidityIcon"; humidityIcon.className = "wi wi-humidity humidityIcon";
humidityIcon.innerHTML = "&nbsp;"; humidityIcon.innerHTML = "&nbsp;";
small.appendChild(humidity); small.appendChild(humidity);
small.appendChild(spacer); small.appendChild(supspacer);
small.appendChild(humidityIcon); small.appendChild(humidityIcon);
} }
@ -174,7 +167,7 @@ Module.register("currentweather",{
}, },
// Override dom generator. // Override dom generator.
getDom: function() { getDom: function () {
var wrapper = document.createElement("div"); var wrapper = document.createElement("div");
wrapper.className = this.config.tableClass; wrapper.className = this.config.tableClass;
@ -201,17 +194,17 @@ Module.register("currentweather",{
if (this.config.units === "metric" || this.config.units === "imperial") { if (this.config.units === "metric" || this.config.units === "imperial") {
degreeLabel += "°"; degreeLabel += "°";
} }
if(this.config.degreeLabel) { if (this.config.degreeLabel) {
switch(this.config.units) { switch (this.config.units) {
case "metric": case "metric":
degreeLabel += "C"; degreeLabel += "C";
break; break;
case "imperial": case "imperial":
degreeLabel += "F"; degreeLabel += "F";
break; break;
case "default": case "default":
degreeLabel += "K"; degreeLabel += "K";
break; break;
} }
} }
@ -254,7 +247,7 @@ Module.register("currentweather",{
wrapper.appendChild(large); wrapper.appendChild(large);
if (this.config.showFeelsLike && this.config.onlyTemp === false){ if (this.config.showFeelsLike && this.config.onlyTemp === false) {
var small = document.createElement("div"); var small = document.createElement("div");
small.className = "normal medium"; small.className = "normal medium";
@ -270,7 +263,7 @@ Module.register("currentweather",{
}, },
// Override getHeader method. // Override getHeader method.
getHeader: function() { getHeader: function () {
if (this.config.appendLocationNameToHeader && this.data.header !== undefined) { if (this.config.appendLocationNameToHeader && this.data.header !== undefined) {
return this.data.header + " " + this.fetchedLocationName; return this.data.header + " " + this.fetchedLocationName;
} }
@ -283,10 +276,10 @@ Module.register("currentweather",{
}, },
// Override notification handler. // Override notification handler.
notificationReceived: function(notification, payload, sender) { notificationReceived: function (notification, payload, sender) {
if (notification === "DOM_OBJECTS_CREATED") { if (notification === "DOM_OBJECTS_CREATED") {
if (this.config.appendLocationNameToHeader) { if (this.config.appendLocationNameToHeader) {
this.hide(0, {lockString: this.identifier}); this.hide(0, { lockString: this.identifier });
} }
} }
if (notification === "CALENDAR_EVENTS") { if (notification === "CALENDAR_EVENTS") {
@ -318,7 +311,7 @@ Module.register("currentweather",{
* Requests new data from openweather.org. * Requests new data from openweather.org.
* Calls processWeather on succesfull response. * Calls processWeather on succesfull response.
*/ */
updateWeather: function() { updateWeather: function () {
if (this.config.appid === "") { if (this.config.appid === "") {
Log.error("CurrentWeather: APPID not set!"); Log.error("CurrentWeather: APPID not set!");
return; return;
@ -330,7 +323,7 @@ Module.register("currentweather",{
var weatherRequest = new XMLHttpRequest(); var weatherRequest = new XMLHttpRequest();
weatherRequest.open("GET", url, true); weatherRequest.open("GET", url, true);
weatherRequest.onreadystatechange = function() { weatherRequest.onreadystatechange = function () {
if (this.readyState === 4) { if (this.readyState === 4) {
if (this.status === 200) { if (this.status === 200) {
self.processWeather(JSON.parse(this.response)); self.processWeather(JSON.parse(this.response));
@ -344,7 +337,7 @@ Module.register("currentweather",{
} }
if (retry) { if (retry) {
self.scheduleUpdate((self.loaded) ? -1 : self.config.retryDelay); self.scheduleUpdate(self.loaded ? -1 : self.config.retryDelay);
} }
} }
}; };
@ -356,18 +349,18 @@ Module.register("currentweather",{
* *
* return String - URL params. * return String - URL params.
*/ */
getParams: function() { getParams: function () {
var params = "?"; var params = "?";
if(this.config.locationID) { if (this.config.locationID) {
params += "id=" + this.config.locationID; params += "id=" + this.config.locationID;
} else if(this.config.location) { } else if (this.config.location) {
params += "q=" + this.config.location; params += "q=" + this.config.location;
} else if (this.firstEvent && this.firstEvent.geo) { } else if (this.firstEvent && this.firstEvent.geo) {
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon; params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
} else if (this.firstEvent && this.firstEvent.location) { } else if (this.firstEvent && this.firstEvent.location) {
params += "q=" + this.firstEvent.location; params += "q=" + this.firstEvent.location;
} else { } else {
this.hide(this.config.animationSpeed, {lockString:this.identifier}); this.hide(this.config.animationSpeed, { lockString: this.identifier });
return; return;
} }
@ -383,8 +376,7 @@ Module.register("currentweather",{
* *
* argument data object - Weather information received form openweather.org. * argument data object - Weather information received form openweather.org.
*/ */
processWeather: function(data) { processWeather: function (data) {
if (!data || !data.main || typeof data.main.temp === "undefined") { if (!data || !data.main || typeof data.main.temp === "undefined") {
// Did not receive usable new data. // Did not receive usable new data.
// Maybe this needs a better check? // Maybe this needs a better check?
@ -396,7 +388,7 @@ Module.register("currentweather",{
this.fetchedLocationName = data.name; this.fetchedLocationName = data.name;
this.feelsLike = 0; this.feelsLike = 0;
if (this.config.useBeaufort){ if (this.config.useBeaufort) {
this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed)); this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed));
} else if (this.config.useKMPHwind) { } else if (this.config.useKMPHwind) {
this.windSpeed = parseFloat((data.wind.speed * 60 * 60) / 1000).toFixed(0); this.windSpeed = parseFloat((data.wind.speed * 60 * 60) / 1000).toFixed(0);
@ -408,52 +400,59 @@ Module.register("currentweather",{
var windInMph = parseFloat(data.wind.speed * 2.23694); var windInMph = parseFloat(data.wind.speed * 2.23694);
var tempInF = 0; var tempInF = 0;
switch (this.config.units){ switch (this.config.units) {
case "metric": tempInF = 1.8 * this.temperature + 32; case "metric":
break; tempInF = 1.8 * this.temperature + 32;
case "imperial": tempInF = this.temperature; break;
break; case "imperial":
case "default": tempInF = this.temperature;
var tc = this.temperature - 273.15; break;
tempInF = 1.8 * tc + 32; case "default":
break; tempInF = 1.8 * (this.temperature - 273.15) + 32;
break;
} }
if (windInMph > 3 && tempInF < 50){ if (windInMph > 3 && tempInF < 50) {
// windchill // windchill
var windChillInF = Math.round(35.74+0.6215*tempInF-35.75*Math.pow(windInMph,0.16)+0.4275*tempInF*Math.pow(windInMph,0.16)); var windChillInF = Math.round(35.74 + 0.6215 * tempInF - 35.75 * Math.pow(windInMph, 0.16) + 0.4275 * tempInF * Math.pow(windInMph, 0.16));
var windChillInC = (windChillInF - 32) * (5/9); var windChillInC = (windChillInF - 32) * (5 / 9);
// this.feelsLike = windChillInC.toFixed(0); // this.feelsLike = windChillInC.toFixed(0);
switch (this.config.units){ switch (this.config.units) {
case "metric": this.feelsLike = windChillInC.toFixed(0); case "metric":
break; this.feelsLike = windChillInC.toFixed(0);
case "imperial": this.feelsLike = windChillInF.toFixed(0); break;
break; case "imperial":
case "default": this.feelsLike = windChillInF.toFixed(0);
var tc = windChillInC + 273.15; break;
this.feelsLike = tc.toFixed(0); case "default":
break; this.feelsLike = (windChillInC + 273.15).toFixed(0);
break;
} }
} else if (tempInF > 80 && this.humidity > 40) {
} else if (tempInF > 80 && this.humidity > 40){
// heat index // heat index
var Hindex = -42.379 + 2.04901523*tempInF + 10.14333127*this.humidity var Hindex =
- 0.22475541*tempInF*this.humidity - 6.83783*Math.pow(10,-3)*tempInF*tempInF -42.379 +
- 5.481717*Math.pow(10,-2)*this.humidity*this.humidity 2.04901523 * tempInF +
+ 1.22874*Math.pow(10,-3)*tempInF*tempInF*this.humidity 10.14333127 * this.humidity -
+ 8.5282*Math.pow(10,-4)*tempInF*this.humidity*this.humidity 0.22475541 * tempInF * this.humidity -
- 1.99*Math.pow(10,-6)*tempInF*tempInF*this.humidity*this.humidity; 6.83783 * Math.pow(10, -3) * tempInF * tempInF -
5.481717 * Math.pow(10, -2) * this.humidity * this.humidity +
1.22874 * Math.pow(10, -3) * tempInF * tempInF * this.humidity +
8.5282 * Math.pow(10, -4) * tempInF * this.humidity * this.humidity -
1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
switch (this.config.units){ switch (this.config.units) {
case "metric": this.feelsLike = parseFloat((Hindex - 32) / 1.8).toFixed(0); case "metric":
break; this.feelsLike = parseFloat((Hindex - 32) / 1.8).toFixed(0);
case "imperial": this.feelsLike = Hindex.toFixed(0); break;
break; case "imperial":
case "default": this.feelsLike = Hindex.toFixed(0);
var tc = parseFloat((Hindex - 32) / 1.8) + 273.15; break;
this.feelsLike = tc.toFixed(0); case "default":
break; var tc = parseFloat((Hindex - 32) / 1.8) + 273.15;
this.feelsLike = tc.toFixed(0);
break;
} }
} else { } else {
this.feelsLike = parseFloat(this.temperature).toFixed(0); this.feelsLike = parseFloat(this.temperature).toFixed(0);
@ -470,7 +469,7 @@ Module.register("currentweather",{
// The moment().format('h') method has a bug on the Raspberry Pi. // The moment().format('h') method has a bug on the Raspberry Pi.
// So we need to generate the timestring manually. // So we need to generate the timestring manually.
// See issue: https://github.com/MichMich/MagicMirror/issues/181 // See issue: https://github.com/MichMich/MagicMirror/issues/181
var sunriseSunsetDateObject = (sunrise < now && sunset > now) ? sunset : sunrise; var sunriseSunsetDateObject = sunrise < now && sunset > now ? sunset : sunrise;
var timeString = moment(sunriseSunsetDateObject).format("HH:mm"); var timeString = moment(sunriseSunsetDateObject).format("HH:mm");
if (this.config.timeFormat !== 24) { if (this.config.timeFormat !== 24) {
//var hours = sunriseSunsetDateObject.getHours() % 12 || 12; //var hours = sunriseSunsetDateObject.getHours() % 12 || 12;
@ -489,12 +488,12 @@ Module.register("currentweather",{
} }
this.sunriseSunsetTime = timeString; this.sunriseSunsetTime = timeString;
this.sunriseSunsetIcon = (sunrise < now && sunset > now) ? "wi-sunset" : "wi-sunrise"; this.sunriseSunsetIcon = sunrise < now && sunset > now ? "wi-sunset" : "wi-sunrise";
this.show(this.config.animationSpeed, {lockString:this.identifier}); this.show(this.config.animationSpeed, { lockString: this.identifier });
this.loaded = true; this.loaded = true;
this.updateDom(this.config.animationSpeed); this.updateDom(this.config.animationSpeed);
this.sendNotification("CURRENTWEATHER_DATA", {data: data}); this.sendNotification("CURRENTWEATHER_DATA", { data: data });
}, },
/* scheduleUpdate() /* scheduleUpdate()
@ -502,14 +501,14 @@ Module.register("currentweather",{
* *
* argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used. * argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
*/ */
scheduleUpdate: function(delay) { scheduleUpdate: function (delay) {
var nextLoad = this.config.updateInterval; var nextLoad = this.config.updateInterval;
if (typeof delay !== "undefined" && delay >= 0) { if (typeof delay !== "undefined" && delay >= 0) {
nextLoad = delay; nextLoad = delay;
} }
var self = this; var self = this;
setTimeout(function() { setTimeout(function () {
self.updateWeather(); self.updateWeather();
}, nextLoad); }, nextLoad);
}, },
@ -518,15 +517,15 @@ Module.register("currentweather",{
* Converts m2 to beaufort (windspeed). * Converts m2 to beaufort (windspeed).
* *
* see: * see:
* http://www.spc.noaa.gov/faq/tornado/beaufort.html * https://www.spc.noaa.gov/faq/tornado/beaufort.html
* https://en.wikipedia.org/wiki/Beaufort_scale#Modern_scale * https://en.wikipedia.org/wiki/Beaufort_scale#Modern_scale
* *
* argument ms number - Windspeed in m/s. * argument ms number - Windspeed in m/s.
* *
* return number - Windspeed in beaufort. * return number - Windspeed in beaufort.
*/ */
ms2Beaufort: function(ms) { ms2Beaufort: function (ms) {
var kmh = ms * 60 * 60 / 1000; var kmh = (ms * 60 * 60) / 1000;
var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000]; var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
for (var beaufort in speeds) { for (var beaufort in speeds) {
var speed = speeds[beaufort]; var speed = speeds[beaufort];
@ -537,8 +536,8 @@ Module.register("currentweather",{
return 12; return 12;
}, },
deg2Cardinal: function(deg) { deg2Cardinal: function (deg) {
if (deg>11.25 && deg<=33.75){ if (deg > 11.25 && deg <= 33.75) {
return "NNE"; return "NNE";
} else if (deg > 33.75 && deg <= 56.25) { } else if (deg > 33.75 && deg <= 56.25) {
return "NE"; return "NE";
@ -580,9 +579,8 @@ Module.register("currentweather",{
* *
* return string - Rounded Temperature. * return string - Rounded Temperature.
*/ */
roundValue: function(temperature) { roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1; var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals); return parseFloat(temperature).toFixed(decimals);
} }
}); });

View File

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

View File

@ -1,4 +1,5 @@
# Module: Hello World # Module: Hello World
The `helloworld` module is one of the default modules of the MagicMirror. It is a simple way to display a static text on the mirror. The `helloworld` module is one of the default modules of the MagicMirror. It is a simple way to display a static text on the mirror.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/helloworld.html). For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/helloworld.html).

View File

@ -1,14 +1,10 @@
/* global Module */
/* Magic Mirror /* Magic Mirror
* Module: HelloWorld * Module: HelloWorld
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
Module.register("helloworld", {
Module.register("helloworld",{
// Default module config. // Default module config.
defaults: { defaults: {
text: "Hello World!" text: "Hello World!"

View File

@ -1,5 +1,6 @@
# Module: News Feed # Module: News Feed
The `newsfeed ` module is one of the default modules of the MagicMirror.
This module displays news headlines based on an RSS feed. Scrolling through news headlines happens time-based (````updateInterval````), but can also be controlled by sending news feed specific notifications to the module. The `newsfeed` module is one of the default modules of the MagicMirror.
This module displays news headlines based on an RSS feed. Scrolling through news headlines happens time-based (`updateInterval`), but can also be controlled by sending news feed specific notifications to the module.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/newsfeed.html). For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/newsfeed.html).

View File

@ -1,7 +1,7 @@
/* Magic Mirror /* Magic Mirror
* Fetcher * Fetcher
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
@ -17,7 +17,7 @@ var iconv = require("iconv-lite");
* attribute logFeedWarnings boolean - Log warnings when there is an error parsing a news article. * attribute logFeedWarnings boolean - Log warnings when there is an error parsing a news article.
*/ */
var Fetcher = function(url, reloadInterval, encoding, logFeedWarnings) { var Fetcher = function (url, reloadInterval, encoding, logFeedWarnings) {
var self = this; var self = this;
if (reloadInterval < 1000) { if (reloadInterval < 1000) {
reloadInterval = 1000; reloadInterval = 1000;
@ -26,8 +26,8 @@ var Fetcher = function(url, reloadInterval, encoding, logFeedWarnings) {
var reloadTimer = null; var reloadTimer = null;
var items = []; var items = [];
var fetchFailedCallback = function() {}; var fetchFailedCallback = function () {};
var itemsReceivedCallback = function() {}; var itemsReceivedCallback = function () {};
/* private methods */ /* private methods */
@ -35,32 +35,29 @@ var Fetcher = function(url, reloadInterval, encoding, logFeedWarnings) {
* Request the new items. * Request the new items.
*/ */
var fetchNews = function() { var fetchNews = function () {
clearTimeout(reloadTimer); clearTimeout(reloadTimer);
reloadTimer = null; reloadTimer = null;
items = []; items = [];
var parser = new FeedMe(); var parser = new FeedMe();
parser.on("item", function(item) { parser.on("item", function (item) {
var title = item.title; var title = item.title;
var description = item.description || item.summary || item.content || ""; var description = item.description || item.summary || item.content || "";
var pubdate = item.pubdate || item.published || item.updated || item["dc:date"]; var pubdate = item.pubdate || item.published || item.updated || item["dc:date"];
var url = item.url || item.link || ""; var url = item.url || item.link || "";
if (title && pubdate) { if (title && pubdate) {
var regex = /(<([^>]+)>)/gi;
var regex = /(<([^>]+)>)/ig;
description = description.toString().replace(regex, ""); description = description.toString().replace(regex, "");
items.push({ items.push({
title: title, title: title,
description: description, description: description,
pubdate: pubdate, pubdate: pubdate,
url: url, url: url
}); });
} else if (logFeedWarnings) { } else if (logFeedWarnings) {
console.log("Can't parse feed item:"); console.log("Can't parse feed item:");
console.log(item); console.log(item);
@ -70,39 +67,37 @@ var Fetcher = function(url, reloadInterval, encoding, logFeedWarnings) {
} }
}); });
parser.on("end", function() { parser.on("end", function () {
//console.log("end parsing - " + url); //console.log("end parsing - " + url);
self.broadcastItems(); self.broadcastItems();
scheduleTimer(); scheduleTimer();
}); });
parser.on("error", function(error) { parser.on("error", function (error) {
fetchFailedCallback(self, error); fetchFailedCallback(self, error);
scheduleTimer(); scheduleTimer();
}); });
nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]); var nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
headers = {"User-Agent": "Mozilla/5.0 (Node.js "+ nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)", var headers = { "User-Agent": "Mozilla/5.0 (Node.js " + nodeVersion + ") MagicMirror/" + global.version + " (https://github.com/MichMich/MagicMirror/)", "Cache-Control": "max-age=0, no-cache, no-store, must-revalidate", Pragma: "no-cache" };
"Cache-Control": "max-age=0, no-cache, no-store, must-revalidate",
"Pragma": "no-cache"};
request({uri: url, encoding: null, headers: headers}) request({ uri: url, encoding: null, headers: headers })
.on("error", function(error) { .on("error", function (error) {
fetchFailedCallback(self, error); fetchFailedCallback(self, error);
scheduleTimer(); scheduleTimer();
}) })
.pipe(iconv.decodeStream(encoding)).pipe(parser); .pipe(iconv.decodeStream(encoding))
.pipe(parser);
}; };
/* scheduleTimer() /* scheduleTimer()
* Schedule the timer for the next update. * Schedule the timer for the next update.
*/ */
var scheduleTimer = function() { var scheduleTimer = function () {
//console.log('Schedule update timer.'); //console.log('Schedule update timer.');
clearTimeout(reloadTimer); clearTimeout(reloadTimer);
reloadTimer = setTimeout(function() { reloadTimer = setTimeout(function () {
fetchNews(); fetchNews();
}, reloadInterval); }, reloadInterval);
}; };
@ -114,7 +109,7 @@ var Fetcher = function(url, reloadInterval, encoding, logFeedWarnings) {
* *
* attribute interval number - Interval for the update in milliseconds. * attribute interval number - Interval for the update in milliseconds.
*/ */
this.setReloadInterval = function(interval) { this.setReloadInterval = function (interval) {
if (interval > 1000 && interval < reloadInterval) { if (interval > 1000 && interval < reloadInterval) {
reloadInterval = interval; reloadInterval = interval;
} }
@ -123,14 +118,14 @@ var Fetcher = function(url, reloadInterval, encoding, logFeedWarnings) {
/* startFetch() /* startFetch()
* Initiate fetchNews(); * Initiate fetchNews();
*/ */
this.startFetch = function() { this.startFetch = function () {
fetchNews(); fetchNews();
}; };
/* broadcastItems() /* broadcastItems()
* Broadcast the existing items. * Broadcast the existing items.
*/ */
this.broadcastItems = function() { this.broadcastItems = function () {
if (items.length <= 0) { if (items.length <= 0) {
//console.log('No items to broadcast yet.'); //console.log('No items to broadcast yet.');
return; return;
@ -139,19 +134,19 @@ var Fetcher = function(url, reloadInterval, encoding, logFeedWarnings) {
itemsReceivedCallback(self); itemsReceivedCallback(self);
}; };
this.onReceive = function(callback) { this.onReceive = function (callback) {
itemsReceivedCallback = callback; itemsReceivedCallback = callback;
}; };
this.onError = function(callback) { this.onError = function (callback) {
fetchFailedCallback = callback; fetchFailedCallback = callback;
}; };
this.url = function() { this.url = function () {
return url; return url;
}; };
this.items = function() { this.items = function () {
return items; return items;
}; };
}; };

View File

@ -1,20 +1,16 @@
/* global Module */
/* Magic Mirror /* Magic Mirror
* Module: NewsFeed * Module: NewsFeed
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
Module.register("newsfeed", {
Module.register("newsfeed",{
// Default module config. // Default module config.
defaults: { defaults: {
feeds: [ feeds: [
{ {
title: "New York Times", title: "New York Times",
url: "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml", url: "https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml",
encoding: "UTF-8" //ISO-8859-1 encoding: "UTF-8" //ISO-8859-1
} }
], ],
@ -44,12 +40,12 @@ Module.register("newsfeed",{
}, },
// Define required scripts. // Define required scripts.
getScripts: function() { getScripts: function () {
return ["moment.js"]; return ["moment.js"];
}, },
// Define required translations. // Define required translations.
getTranslations: function() { getTranslations: function () {
// The translations for the default modules are defined in the core translation files. // The translations for the default modules are defined in the core translation files.
// Therefor we can just return false. Otherwise we should have returned a dictionary. // Therefor we can just return false. Otherwise we should have returned a dictionary.
// If you're trying to build your own module including translations, check out the documentation. // If you're trying to build your own module including translations, check out the documentation.
@ -57,7 +53,7 @@ Module.register("newsfeed",{
}, },
// Define start sequence. // Define start sequence.
start: function() { start: function () {
Log.info("Starting module: " + this.name); Log.info("Starting module: " + this.name);
// Set locale. // Set locale.
@ -74,7 +70,7 @@ Module.register("newsfeed",{
}, },
// Override socket notification handler. // Override socket notification handler.
socketNotificationReceived: function(notification, payload) { socketNotificationReceived: function (notification, payload) {
if (notification === "NEWS_ITEMS") { if (notification === "NEWS_ITEMS") {
this.generateFeed(payload); this.generateFeed(payload);
@ -87,7 +83,7 @@ Module.register("newsfeed",{
}, },
// Override dom generator. // Override dom generator.
getDom: function() { getDom: function () {
var wrapper = document.createElement("div"); var wrapper = document.createElement("div");
if (this.config.feedUrl) { if (this.config.feedUrl) {
@ -101,7 +97,6 @@ Module.register("newsfeed",{
} }
if (this.newsItems.length > 0) { if (this.newsItems.length > 0) {
// this.config.showFullArticle is a run-time configuration, triggered by optional notifications // this.config.showFullArticle is a run-time configuration, triggered by optional notifications
if (!this.config.showFullArticle && (this.config.showSourceTitle || this.config.showPublishDate)) { if (!this.config.showFullArticle && (this.config.showSourceTitle || this.config.showPublishDate)) {
var sourceAndTimestamp = document.createElement("div"); var sourceAndTimestamp = document.createElement("div");
@ -116,7 +111,7 @@ Module.register("newsfeed",{
if (this.config.showPublishDate) { if (this.config.showPublishDate) {
sourceAndTimestamp.innerHTML += moment(new Date(this.newsItems[this.activeItem].pubdate)).fromNow(); sourceAndTimestamp.innerHTML += moment(new Date(this.newsItems[this.activeItem].pubdate)).fromNow();
} }
if (this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "" || this.config.showPublishDate) { if ((this.config.showSourceTitle && this.newsItems[this.activeItem].sourceTitle !== "") || this.config.showPublishDate) {
sourceAndTimestamp.innerHTML += ":"; sourceAndTimestamp.innerHTML += ":";
} }
@ -126,47 +121,42 @@ Module.register("newsfeed",{
//Remove selected tags from the beginning of rss feed items (title or description) //Remove selected tags from the beginning of rss feed items (title or description)
if (this.config.removeStartTags === "title" || this.config.removeStartTags === "both") { if (this.config.removeStartTags === "title" || this.config.removeStartTags === "both") {
for (let f = 0; f < this.config.startTags.length; f++) {
for (f=0; f<this.config.startTags.length;f++) { if (this.newsItems[this.activeItem].title.slice(0, this.config.startTags[f].length) === this.config.startTags[f]) {
if (this.newsItems[this.activeItem].title.slice(0,this.config.startTags[f].length) === this.config.startTags[f]) { this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].title.slice(this.config.startTags[f].length, this.newsItems[this.activeItem].title.length);
this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].title.slice(this.config.startTags[f].length,this.newsItems[this.activeItem].title.length);
} }
} }
} }
if (this.config.removeStartTags === "description" || this.config.removeStartTags === "both") { if (this.config.removeStartTags === "description" || this.config.removeStartTags === "both") {
if (this.isShowingDescription) { if (this.isShowingDescription) {
for (f=0; f<this.config.startTags.length;f++) { for (let f = 0; f < this.config.startTags.length; f++) {
if (this.newsItems[this.activeItem].description.slice(0,this.config.startTags[f].length) === this.config.startTags[f]) { if (this.newsItems[this.activeItem].description.slice(0, this.config.startTags[f].length) === this.config.startTags[f]) {
this.newsItems[this.activeItem].description = this.newsItems[this.activeItem].description.slice(this.config.startTags[f].length,this.newsItems[this.activeItem].description.length); this.newsItems[this.activeItem].description = this.newsItems[this.activeItem].description.slice(this.config.startTags[f].length, this.newsItems[this.activeItem].description.length);
} }
} }
} }
} }
//Remove selected tags from the end of rss feed items (title or description) //Remove selected tags from the end of rss feed items (title or description)
if (this.config.removeEndTags) { if (this.config.removeEndTags) {
for (f=0; f<this.config.endTags.length;f++) { for (let f = 0; f < this.config.endTags.length; f++) {
if (this.newsItems[this.activeItem].title.slice(-this.config.endTags[f].length)===this.config.endTags[f]) { if (this.newsItems[this.activeItem].title.slice(-this.config.endTags[f].length) === this.config.endTags[f]) {
this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].title.slice(0,-this.config.endTags[f].length); this.newsItems[this.activeItem].title = this.newsItems[this.activeItem].title.slice(0, -this.config.endTags[f].length);
} }
} }
if (this.isShowingDescription) { if (this.isShowingDescription) {
for (f=0; f<this.config.endTags.length;f++) { for (let f = 0; f < this.config.endTags.length; f++) {
if (this.newsItems[this.activeItem].description.slice(-this.config.endTags[f].length)===this.config.endTags[f]) { if (this.newsItems[this.activeItem].description.slice(-this.config.endTags[f].length) === this.config.endTags[f]) {
this.newsItems[this.activeItem].description = this.newsItems[this.activeItem].description.slice(0,-this.config.endTags[f].length); this.newsItems[this.activeItem].description = this.newsItems[this.activeItem].description.slice(0, -this.config.endTags[f].length);
} }
} }
} }
} }
if(!this.config.showFullArticle){ if (!this.config.showFullArticle) {
var title = document.createElement("div"); var title = document.createElement("div");
title.className = "newsfeed-title bright medium light" + (!this.config.wrapTitle ? " no-wrap" : ""); title.className = "newsfeed-title bright medium light" + (!this.config.wrapTitle ? " no-wrap" : "");
title.innerHTML = this.newsItems[this.activeItem].title; title.innerHTML = this.newsItems[this.activeItem].title;
@ -177,7 +167,7 @@ Module.register("newsfeed",{
var description = document.createElement("div"); var description = document.createElement("div");
description.className = "newsfeed-desc small light" + (!this.config.wrapDescription ? " no-wrap" : ""); description.className = "newsfeed-desc small light" + (!this.config.wrapDescription ? " no-wrap" : "");
var txtDesc = this.newsItems[this.activeItem].description; var txtDesc = this.newsItems[this.activeItem].description;
description.innerHTML = (this.config.truncDescription ? (txtDesc.length > this.config.lengthDescription ? txtDesc.substring(0, this.config.lengthDescription) + "..." : txtDesc) : txtDesc); description.innerHTML = this.config.truncDescription ? (txtDesc.length > this.config.lengthDescription ? txtDesc.substring(0, this.config.lengthDescription) + "..." : txtDesc) : txtDesc;
wrapper.appendChild(description); wrapper.appendChild(description);
} }
@ -199,7 +189,6 @@ Module.register("newsfeed",{
if (this.config.hideLoading) { if (this.config.hideLoading) {
this.show(); this.show();
} }
} else { } else {
if (this.config.hideLoading) { if (this.config.hideLoading) {
this.hide(); this.hide();
@ -212,14 +201,14 @@ Module.register("newsfeed",{
return wrapper; return wrapper;
}, },
getActiveItemURL: function() { getActiveItemURL: function () {
return typeof this.newsItems[this.activeItem].url === "string" ? this.newsItems[this.activeItem].url : this.newsItems[this.activeItem].url.href; return typeof this.newsItems[this.activeItem].url === "string" ? this.newsItems[this.activeItem].url : this.newsItems[this.activeItem].url.href;
}, },
/* registerFeeds() /* registerFeeds()
* registers the feeds to be used by the backend. * registers the feeds to be used by the backend.
*/ */
registerFeeds: function() { registerFeeds: function () {
for (var f in this.config.feeds) { for (var f in this.config.feeds) {
var feed = this.config.feeds[f]; var feed = this.config.feeds[f];
this.sendSocketNotification("ADD_FEED", { this.sendSocketNotification("ADD_FEED", {
@ -234,7 +223,7 @@ Module.register("newsfeed",{
* *
* attribute feeds object - An object with feeds returned by the node helper. * attribute feeds object - An object with feeds returned by the node helper.
*/ */
generateFeed: function(feeds) { generateFeed: function (feeds) {
var newsItems = []; var newsItems = [];
for (var feed in feeds) { for (var feed in feeds) {
var feedItems = feeds[feed]; var feedItems = feeds[feed];
@ -242,24 +231,24 @@ Module.register("newsfeed",{
for (var i in feedItems) { for (var i in feedItems) {
var item = feedItems[i]; var item = feedItems[i];
item.sourceTitle = this.titleForFeed(feed); item.sourceTitle = this.titleForFeed(feed);
if (!(this.config.ignoreOldItems && ((Date.now() - new Date(item.pubdate)) > this.config.ignoreOlderThan))) { if (!(this.config.ignoreOldItems && Date.now() - new Date(item.pubdate) > this.config.ignoreOlderThan)) {
newsItems.push(item); newsItems.push(item);
} }
} }
} }
} }
newsItems.sort(function(a,b) { newsItems.sort(function (a, b) {
var dateA = new Date(a.pubdate); var dateA = new Date(a.pubdate);
var dateB = new Date(b.pubdate); var dateB = new Date(b.pubdate);
return dateB - dateA; return dateB - dateA;
}); });
if(this.config.maxNewsItems > 0) { if (this.config.maxNewsItems > 0) {
newsItems = newsItems.slice(0, this.config.maxNewsItems); newsItems = newsItems.slice(0, this.config.maxNewsItems);
} }
if(this.config.prohibitedWords.length > 0) { if (this.config.prohibitedWords.length > 0) {
newsItems = newsItems.filter(function(value){ newsItems = newsItems.filter(function (value) {
for (var i=0; i < this.config.prohibitedWords.length; i++) { for (var i = 0; i < this.config.prohibitedWords.length; i++) {
if (value["title"].toLowerCase().indexOf(this.config.prohibitedWords[i].toLowerCase()) > -1) { if (value["title"].toLowerCase().indexOf(this.config.prohibitedWords[i].toLowerCase()) > -1) {
return false; return false;
} }
@ -270,8 +259,8 @@ Module.register("newsfeed",{
// get updated news items and broadcast them // get updated news items and broadcast them
var updatedItems = []; var updatedItems = [];
newsItems.forEach(value => { newsItems.forEach((value) => {
if (this.newsItems.findIndex(value1 => value1 === value) === -1) { if (this.newsItems.findIndex((value1) => value1 === value) === -1) {
// Add item to updated items list // Add item to updated items list
updatedItems.push(value); updatedItems.push(value);
} }
@ -279,7 +268,7 @@ Module.register("newsfeed",{
// check if updated items exist, if so and if we should broadcast these updates, then lets do so // check if updated items exist, if so and if we should broadcast these updates, then lets do so
if (this.config.broadcastNewsUpdates && updatedItems.length > 0) { if (this.config.broadcastNewsUpdates && updatedItems.length > 0) {
this.sendNotification("NEWS_FEED_UPDATE", {items: updatedItems}); this.sendNotification("NEWS_FEED_UPDATE", { items: updatedItems });
} }
this.newsItems = newsItems; this.newsItems = newsItems;
@ -292,7 +281,7 @@ Module.register("newsfeed",{
* *
* returns bool * returns bool
*/ */
subscribedToFeed: function(feedUrl) { subscribedToFeed: function (feedUrl) {
for (var f in this.config.feeds) { for (var f in this.config.feeds) {
var feed = this.config.feeds[f]; var feed = this.config.feeds[f];
if (feed.url === feedUrl) { if (feed.url === feedUrl) {
@ -309,7 +298,7 @@ Module.register("newsfeed",{
* *
* returns string * returns string
*/ */
titleForFeed: function(feedUrl) { titleForFeed: function (feedUrl) {
for (var f in this.config.feeds) { for (var f in this.config.feeds) {
var feed = this.config.feeds[f]; var feed = this.config.feeds[f];
if (feed.url === feedUrl) { if (feed.url === feedUrl) {
@ -322,23 +311,23 @@ Module.register("newsfeed",{
/* scheduleUpdateInterval() /* scheduleUpdateInterval()
* Schedule visual update. * Schedule visual update.
*/ */
scheduleUpdateInterval: function() { scheduleUpdateInterval: function () {
var self = this; var self = this;
self.updateDom(self.config.animationSpeed); self.updateDom(self.config.animationSpeed);
// Broadcast NewsFeed if needed // Broadcast NewsFeed if needed
if (self.config.broadcastNewsFeeds) { if (self.config.broadcastNewsFeeds) {
self.sendNotification("NEWS_FEED", {items: self.newsItems}); self.sendNotification("NEWS_FEED", { items: self.newsItems });
} }
timer = setInterval(function() { this.timer = setInterval(function () {
self.activeItem++; self.activeItem++;
self.updateDom(self.config.animationSpeed); self.updateDom(self.config.animationSpeed);
// Broadcast NewsFeed if needed // Broadcast NewsFeed if needed
if (self.config.broadcastNewsFeeds) { if (self.config.broadcastNewsFeeds) {
self.sendNotification("NEWS_FEED", {items: self.newsItems}); self.sendNotification("NEWS_FEED", { items: self.newsItems });
} }
}, this.config.updateInterval); }, this.config.updateInterval);
}, },
@ -350,25 +339,25 @@ Module.register("newsfeed",{
* *
* return string - Capitalized output string. * return string - Capitalized output string.
*/ */
capitalizeFirstLetter: function(string) { capitalizeFirstLetter: function (string) {
return string.charAt(0).toUpperCase() + string.slice(1); return string.charAt(0).toUpperCase() + string.slice(1);
}, },
resetDescrOrFullArticleAndTimer: function() { resetDescrOrFullArticleAndTimer: function () {
this.isShowingDescription = this.config.showDescription; this.isShowingDescription = this.config.showDescription;
this.config.showFullArticle = false; this.config.showFullArticle = false;
this.scrollPosition = 0; this.scrollPosition = 0;
// reset bottom bar alignment // reset bottom bar alignment
document.getElementsByClassName("region bottom bar")[0].style.bottom = "0"; document.getElementsByClassName("region bottom bar")[0].style.bottom = "0";
document.getElementsByClassName("region bottom bar")[0].style.top = "inherit"; document.getElementsByClassName("region bottom bar")[0].style.top = "inherit";
if(!timer){ if (!this.timer) {
this.scheduleUpdateInterval(); this.scheduleUpdateInterval();
} }
}, },
notificationReceived: function(notification, payload, sender) { notificationReceived: function (notification, payload, sender) {
if(notification === "ARTICLE_NEXT"){ var before = this.activeItem;
var before = this.activeItem; if (notification === "ARTICLE_NEXT") {
this.activeItem++; this.activeItem++;
if (this.activeItem >= this.newsItems.length) { if (this.activeItem >= this.newsItems.length) {
this.activeItem = 0; this.activeItem = 0;
@ -376,8 +365,7 @@ Module.register("newsfeed",{
this.resetDescrOrFullArticleAndTimer(); this.resetDescrOrFullArticleAndTimer();
Log.info(this.name + " - going from article #" + before + " to #" + this.activeItem + " (of " + this.newsItems.length + ")"); Log.info(this.name + " - going from article #" + before + " to #" + this.activeItem + " (of " + this.newsItems.length + ")");
this.updateDom(100); this.updateDom(100);
} else if(notification === "ARTICLE_PREVIOUS"){ } else if (notification === "ARTICLE_PREVIOUS") {
var before = this.activeItem;
this.activeItem--; this.activeItem--;
if (this.activeItem < 0) { if (this.activeItem < 0) {
this.activeItem = this.newsItems.length - 1; this.activeItem = this.newsItems.length - 1;
@ -387,58 +375,56 @@ Module.register("newsfeed",{
this.updateDom(100); this.updateDom(100);
} }
// if "more details" is received the first time: show article summary, on second time show full article // if "more details" is received the first time: show article summary, on second time show full article
else if(notification === "ARTICLE_MORE_DETAILS"){ else if (notification === "ARTICLE_MORE_DETAILS") {
// full article is already showing, so scrolling down // full article is already showing, so scrolling down
if(this.config.showFullArticle === true){ if (this.config.showFullArticle === true) {
this.scrollPosition += this.config.scrollLength; this.scrollPosition += this.config.scrollLength;
window.scrollTo(0, this.scrollPosition); window.scrollTo(0, this.scrollPosition);
Log.info(this.name + " - scrolling down"); Log.info(this.name + " - scrolling down");
Log.info(this.name + " - ARTICLE_MORE_DETAILS, scroll position: " + this.config.scrollLength); Log.info(this.name + " - ARTICLE_MORE_DETAILS, scroll position: " + this.config.scrollLength);
} } else {
else {
this.showFullArticle(); this.showFullArticle();
} }
} else if(notification === "ARTICLE_SCROLL_UP"){ } else if (notification === "ARTICLE_SCROLL_UP") {
if(this.config.showFullArticle === true){ if (this.config.showFullArticle === true) {
this.scrollPosition -= this.config.scrollLength; this.scrollPosition -= this.config.scrollLength;
window.scrollTo(0, this.scrollPosition); window.scrollTo(0, this.scrollPosition);
Log.info(this.name + " - scrolling up"); Log.info(this.name + " - scrolling up");
Log.info(this.name + " - ARTICLE_SCROLL_UP, scroll position: " + this.config.scrollLength); Log.info(this.name + " - ARTICLE_SCROLL_UP, scroll position: " + this.config.scrollLength);
} }
} else if(notification === "ARTICLE_LESS_DETAILS"){ } else if (notification === "ARTICLE_LESS_DETAILS") {
this.resetDescrOrFullArticleAndTimer(); this.resetDescrOrFullArticleAndTimer();
Log.info(this.name + " - showing only article titles again"); Log.info(this.name + " - showing only article titles again");
this.updateDom(100); this.updateDom(100);
} else if (notification === "ARTICLE_TOGGLE_FULL"){ } else if (notification === "ARTICLE_TOGGLE_FULL") {
if (this.config.showFullArticle){ if (this.config.showFullArticle) {
this.activeItem++; this.activeItem++;
this.resetDescrOrFullArticleAndTimer(); this.resetDescrOrFullArticleAndTimer();
} else { } else {
this.showFullArticle(); this.showFullArticle();
} }
} else if (notification === "ARTICLE_INFO_REQUEST"){ } else if (notification === "ARTICLE_INFO_REQUEST") {
this.sendNotification("ARTICLE_INFO_RESPONSE", { this.sendNotification("ARTICLE_INFO_RESPONSE", {
title: this.newsItems[this.activeItem].title, title: this.newsItems[this.activeItem].title,
source: this.newsItems[this.activeItem].sourceTitle, source: this.newsItems[this.activeItem].sourceTitle,
date: this.newsItems[this.activeItem].pubdate, date: this.newsItems[this.activeItem].pubdate,
desc: this.newsItems[this.activeItem].description, desc: this.newsItems[this.activeItem].description,
url: this.getActiveItemURL() url: this.getActiveItemURL()
}); });
} }
}, },
showFullArticle: function() { showFullArticle: function () {
this.isShowingDescription = !this.isShowingDescription; this.isShowingDescription = !this.isShowingDescription;
this.config.showFullArticle = !this.isShowingDescription; this.config.showFullArticle = !this.isShowingDescription;
// make bottom bar align to top to allow scrolling // make bottom bar align to top to allow scrolling
if(this.config.showFullArticle === true){ if (this.config.showFullArticle === true) {
document.getElementsByClassName("region bottom bar")[0].style.bottom = "inherit"; document.getElementsByClassName("region bottom bar")[0].style.bottom = "inherit";
document.getElementsByClassName("region bottom bar")[0].style.top = "-90px"; document.getElementsByClassName("region bottom bar")[0].style.top = "-90px";
} }
clearInterval(timer); clearInterval(this.timer);
timer = null; this.timer = null;
Log.info(this.name + " - showing " + this.isShowingDescription ? "article description" : "full article"); Log.info(this.name + " - showing " + this.isShowingDescription ? "article description" : "full article");
this.updateDom(100); this.updateDom(100);
} }
}); });

View File

@ -1,7 +1,7 @@
/* Magic Mirror /* Magic Mirror
* Node Helper: Newsfeed * Node Helper: Newsfeed
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
@ -11,13 +11,13 @@ var Fetcher = require("./fetcher.js");
module.exports = NodeHelper.create({ module.exports = NodeHelper.create({
// Subclass start method. // Subclass start method.
start: function() { start: function () {
console.log("Starting module: " + this.name); console.log("Starting module: " + this.name);
this.fetchers = []; this.fetchers = [];
}, },
// Subclass socketNotificationReceived received. // Subclass socketNotificationReceived received.
socketNotificationReceived: function(notification, payload) { socketNotificationReceived: function (notification, payload) {
if (notification === "ADD_FEED") { if (notification === "ADD_FEED") {
this.createFetcher(payload.feed, payload.config); this.createFetcher(payload.feed, payload.config);
return; return;
@ -31,7 +31,7 @@ module.exports = NodeHelper.create({
* attribute feed object - A feed object. * attribute feed object - A feed object.
* attribute config object - A configuration object containing reload interval in milliseconds. * attribute config object - A configuration object containing reload interval in milliseconds.
*/ */
createFetcher: function(feed, config) { createFetcher: function (feed, config) {
var self = this; var self = this;
var url = feed.url || ""; var url = feed.url || "";
@ -48,11 +48,11 @@ module.exports = NodeHelper.create({
console.log("Create new news fetcher for url: " + url + " - Interval: " + reloadInterval); console.log("Create new news fetcher for url: " + url + " - Interval: " + reloadInterval);
fetcher = new Fetcher(url, reloadInterval, encoding, config.logFeedWarnings); fetcher = new Fetcher(url, reloadInterval, encoding, config.logFeedWarnings);
fetcher.onReceive(function(fetcher) { fetcher.onReceive(function (fetcher) {
self.broadcastFeeds(); self.broadcastFeeds();
}); });
fetcher.onError(function(fetcher, error) { fetcher.onError(function (fetcher, error) {
self.sendSocketNotification("FETCH_ERROR", { self.sendSocketNotification("FETCH_ERROR", {
url: fetcher.url(), url: fetcher.url(),
error: error error: error
@ -74,7 +74,7 @@ module.exports = NodeHelper.create({
* Creates an object with all feed items of the different registered feeds, * Creates an object with all feed items of the different registered feeds,
* and broadcasts these using sendSocketNotification. * and broadcasts these using sendSocketNotification.
*/ */
broadcastFeeds: function() { broadcastFeeds: function () {
var feeds = {}; var feeds = {};
for (var f in this.fetchers) { for (var f in this.fetchers) {
feeds[f] = this.fetchers[f].items(); feeds[f] = this.fetchers[f].items();

View File

@ -1,3 +1,3 @@
{ {
"configuration_changed": "Die Konfigurationsoptionen für das Newsfeed-Modul haben sich geändert. \nBitte überprüfen Sie die Dokumentation." "configuration_changed": "Die Konfigurationsoptionen für das Newsfeed-Modul haben sich geändert. \nBitte überprüfen Sie die Dokumentation."
} }

View File

@ -1,3 +1,3 @@
{ {
"configuration_changed": "The configuration options for the newsfeed module have changed.\nPlease check the documentation." "configuration_changed": "The configuration options for the newsfeed module have changed.\nPlease check the documentation."
} }

View File

@ -1,3 +1,3 @@
{ {
"configuration_changed": "Las opciones de configuración para el módulo de suministro de noticias han cambiado. \nVerifique la documentación." "configuration_changed": "Las opciones de configuración para el módulo de suministro de noticias han cambiado. \nVerifique la documentación."
} }

View File

@ -1,3 +1,3 @@
{ {
"configuration_changed": "Les options de configuration du module newsfeed ont changé. \nVeuillez consulter la documentation." "configuration_changed": "Les options de configuration du module newsfeed ont changé. \nVeuillez consulter la documentation."
} }

View File

@ -1,4 +1,5 @@
# Module: Update Notification # Module: Update Notification
The `updatenotification` module is one of the default modules of the MagicMirror. The `updatenotification` module is one of the default modules of the MagicMirror.
This will display a message whenever a new version of the MagicMirror application is available. This will display a message whenever a new version of the MagicMirror application is available.

View File

@ -6,25 +6,22 @@ var defaultModules = require(__dirname + "/../defaultmodules.js");
var NodeHelper = require("node_helper"); var NodeHelper = require("node_helper");
module.exports = NodeHelper.create({ module.exports = NodeHelper.create({
config: {}, config: {},
updateTimer: null, updateTimer: null,
updateProcessStarted: false, updateProcessStarted: false,
start: function () { start: function () {},
},
configureModules: function(modules) {
configureModules: function (modules) {
// Push MagicMirror itself , biggest chance it'll show up last in UI and isn't overwritten // Push MagicMirror itself , biggest chance it'll show up last in UI and isn't overwritten
// others will be added in front // others will be added in front
// this method returns promises so we can't wait for every one to resolve before continuing // this method returns promises so we can't wait for every one to resolve before continuing
simpleGits.push({"module": "default", "git": SimpleGit(path.normalize(__dirname + "/../../../"))}); simpleGits.push({ module: "default", git: SimpleGit(path.normalize(__dirname + "/../../../")) });
var promises = []; var promises = [];
for (moduleName in modules) { for (var moduleName in modules) {
if (!this.ignoreUpdateChecking(moduleName)) { if (!this.ignoreUpdateChecking(moduleName)) {
// Default modules are included in the main MagicMirror repo // Default modules are included in the main MagicMirror repo
var moduleFolder = path.normalize(__dirname + "/../../" + moduleName); var moduleFolder = path.normalize(__dirname + "/../../" + moduleName);
@ -33,7 +30,7 @@ module.exports = NodeHelper.create({
//console.log("checking git for module="+moduleName) //console.log("checking git for module="+moduleName)
let stat = fs.statSync(path.join(moduleFolder, ".git")); let stat = fs.statSync(path.join(moduleFolder, ".git"));
promises.push(this.resolveRemote(moduleName, moduleFolder)); promises.push(this.resolveRemote(moduleName, moduleFolder));
} catch(err) { } catch (err) {
// Error when directory .git doesn't exist // Error when directory .git doesn't exist
// This module is not managed with git, skip // This module is not managed with git, skip
continue; continue;
@ -47,7 +44,7 @@ module.exports = NodeHelper.create({
socketNotificationReceived: function (notification, payload) { socketNotificationReceived: function (notification, payload) {
if (notification === "CONFIG") { if (notification === "CONFIG") {
this.config = payload; this.config = payload;
} else if(notification === "MODULES") { } else if (notification === "MODULES") {
// if this is the 1st time thru the update check process // if this is the 1st time thru the update check process
if (!this.updateProcessStarted) { if (!this.updateProcessStarted) {
this.updateProcessStarted = true; this.updateProcessStarted = true;
@ -56,7 +53,7 @@ module.exports = NodeHelper.create({
} }
}, },
resolveRemote: function(moduleName, moduleFolder) { resolveRemote: function (moduleName, moduleFolder) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
var git = SimpleGit(moduleFolder); var git = SimpleGit(moduleFolder);
git.getRemotes(true, (err, remotes) => { git.getRemotes(true, (err, remotes) => {
@ -65,19 +62,19 @@ module.exports = NodeHelper.create({
return resolve(); return resolve();
} }
// Folder has .git and has at least one git remote, watch this folder // Folder has .git and has at least one git remote, watch this folder
simpleGits.unshift({"module": moduleName, "git": git}); simpleGits.unshift({ module: moduleName, git: git });
resolve(); resolve();
}); });
}); });
}, },
performFetch: function() { performFetch: function () {
var self = this; var self = this;
simpleGits.forEach((sg) => { simpleGits.forEach((sg) => {
sg.git.fetch().status((err, data) => { sg.git.fetch().status((err, data) => {
data.module = sg.module; data.module = sg.module;
if (!err) { if (!err) {
sg.git.log({"-1": null}, (err, data2) => { sg.git.log({ "-1": null }, (err, data2) => {
if (!err && data2.latest && "hash" in data2.latest) { if (!err && data2.latest && "hash" in data2.latest) {
data.hash = data2.latest.hash; data.hash = data2.latest.hash;
self.sendSocketNotification("STATUS", data); self.sendSocketNotification("STATUS", data);
@ -90,19 +87,19 @@ module.exports = NodeHelper.create({
this.scheduleNextFetch(this.config.updateInterval); this.scheduleNextFetch(this.config.updateInterval);
}, },
scheduleNextFetch: function(delay) { scheduleNextFetch: function (delay) {
if (delay < 60 * 1000) { if (delay < 60 * 1000) {
delay = 60 * 1000; delay = 60 * 1000;
} }
var self = this; var self = this;
clearTimeout(this.updateTimer); clearTimeout(this.updateTimer);
this.updateTimer = setTimeout(function() { this.updateTimer = setTimeout(function () {
self.performFetch(); self.performFetch();
}, delay); }, delay);
}, },
ignoreUpdateChecking: function(moduleName) { ignoreUpdateChecking: function (moduleName) {
// Should not check for updates for default modules // Should not check for updates for default modules
if (defaultModules.indexOf(moduleName) >= 0) { if (defaultModules.indexOf(moduleName) >= 0) {
return true; return true;
@ -116,5 +113,4 @@ module.exports = NodeHelper.create({
// The rest of the modules that passes should check for updates // The rest of the modules that passes should check for updates
return false; return false;
} }
}); });

View File

@ -1,5 +1,10 @@
/* Magic Mirror
* Module: UpdateNotification
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
Module.register("updatenotification", { Module.register("updatenotification", {
defaults: { defaults: {
updateInterval: 10 * 60 * 1000, // every 10 minutes updateInterval: 10 * 60 * 1000, // every 10 minutes
refreshInterval: 24 * 60 * 60 * 1000, // one day refreshInterval: 24 * 60 * 60 * 1000, // one day
@ -12,7 +17,10 @@ Module.register("updatenotification", {
start: function () { start: function () {
var self = this; var self = this;
Log.log("Start updatenotification"); Log.log("Start updatenotification");
setInterval( () => { self.moduleList = {};self.updateDom(2); } , self.config.refreshInterval); setInterval(() => {
self.moduleList = {};
self.updateDom(2);
}, self.config.refreshInterval);
}, },
notificationReceived: function (notification, payload, sender) { notificationReceived: function (notification, payload, sender) {
@ -33,16 +41,15 @@ Module.register("updatenotification", {
var self = this; var self = this;
if (payload && payload.behind > 0) { if (payload && payload.behind > 0) {
// if we haven't seen info for this module // if we haven't seen info for this module
if(this.moduleList[payload.module] == undefined){ if (this.moduleList[payload.module] === undefined) {
// save it // save it
this.moduleList[payload.module]=payload; this.moduleList[payload.module] = payload;
self.updateDom(2); self.updateDom(2);
} }
//self.show(1000, { lockString: self.identifier }); //self.show(1000, { lockString: self.identifier });
} else if (payload && payload.behind === 0) {
} else if (payload && payload.behind == 0){
// if the module WAS in the list, but shouldn't be // if the module WAS in the list, but shouldn't be
if(this.moduleList[payload.module] != undefined){ if (this.moduleList[payload.module] !== undefined) {
// remove it // remove it
delete this.moduleList[payload.module]; delete this.moduleList[payload.module];
self.updateDom(2); self.updateDom(2);
@ -50,24 +57,19 @@ Module.register("updatenotification", {
} }
}, },
diffLink: function(module, text) { diffLink: function (module, text) {
var localRef = module.hash; var localRef = module.hash;
var remoteRef = module.tracking.replace(/.*\//, ""); var remoteRef = module.tracking.replace(/.*\//, "");
return "<a href=\"https://github.com/MichMich/MagicMirror/compare/"+localRef+"..."+remoteRef+"\" "+ return '<a href="https://github.com/MichMich/MagicMirror/compare/' + localRef + "..." + remoteRef + '" ' + 'class="xsmall dimmed" ' + 'style="text-decoration: none;" ' + 'target="_blank" >' + text + "</a>";
"class=\"xsmall dimmed\" "+
"style=\"text-decoration: none;\" "+
"target=\"_blank\" >" +
text +
"</a>";
}, },
// Override dom generator. // Override dom generator.
getDom: function () { getDom: function () {
var wrapper = document.createElement("div"); var wrapper = document.createElement("div");
if(this.suspended==false){ if (this.suspended === false) {
// process the hash of module info found // process the hash of module info found
for(key of Object.keys(this.moduleList)){ for (var key of Object.keys(this.moduleList)) {
let m= this.moduleList[key]; let m = this.moduleList[key];
var message = document.createElement("div"); var message = document.createElement("div");
message.className = "small bright"; message.className = "small bright";
@ -77,7 +79,7 @@ Module.register("updatenotification", {
icon.innerHTML = "&nbsp;"; icon.innerHTML = "&nbsp;";
message.appendChild(icon); message.appendChild(icon);
var updateInfoKeyName = m.behind == 1 ? "UPDATE_INFO_SINGLE" : "UPDATE_INFO_MULTIPLE"; var updateInfoKeyName = m.behind === 1 ? "UPDATE_INFO_SINGLE" : "UPDATE_INFO_MULTIPLE";
var subtextHtml = this.translate(updateInfoKeyName, { var subtextHtml = this.translate(updateInfoKeyName, {
COMMIT_COUNT: m.behind, COMMIT_COUNT: m.behind,
@ -85,9 +87,9 @@ Module.register("updatenotification", {
}); });
var text = document.createElement("span"); var text = document.createElement("span");
if (m.module == "default") { if (m.module === "default") {
text.innerHTML = this.translate("UPDATE_NOTIFICATION"); text.innerHTML = this.translate("UPDATE_NOTIFICATION");
subtextHtml = this.diffLink(m,subtextHtml); subtextHtml = this.diffLink(m, subtextHtml);
} else { } else {
text.innerHTML = this.translate("UPDATE_NOTIFICATION_MODULE", { text.innerHTML = this.translate("UPDATE_NOTIFICATION_MODULE", {
MODULE_NAME: m.module MODULE_NAME: m.module
@ -106,11 +108,11 @@ Module.register("updatenotification", {
return wrapper; return wrapper;
}, },
suspend: function() { suspend: function () {
this.suspended=true; this.suspended = true;
}, },
resume: function() { resume: function () {
this.suspended=false; this.suspended = false;
this.updateDom(2); this.updateDom(2);
} }
}); });

View File

@ -1,5 +1,5 @@
# Weather Module # Weather Module
This module is aimed to be the replacement for the current `currentweather` and `weatherforcast` modules. The module will be configurable to be used as a current weather view, or to show the forecast. This way the module can be used twice to fullfil both purposes. This module aims to be the replacement for the current `currentweather` and `weatherforcast` modules. The module will be configurable to be used as a current weather view, or to show the forecast. This way the module can be used twice to fullfil both purposes.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/weather.html). For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/weather.html).

View File

@ -13,17 +13,17 @@ Table of Contents:
## The weather provider file: yourprovider.js ## The weather provider file: yourprovider.js
This is the script in which the weather provider will be defined. In it's most simple form, the weather provider must implement the following: This is the script in which the weather provider will be defined. In its most simple form, the weather provider must implement the following:
````javascript ```javascript
WeatherProvider.register("yourprovider", { WeatherProvider.register("yourprovider", {
providerName: "YourProvider", providerName: "YourProvider",
fetchCurrentWeather() {}, fetchCurrentWeather() {},
fetchWeatherForecast() {} fetchWeatherForecast() {}
}); });
```` ```
### Weather provider methods to implement ### Weather provider methods to implement
@ -36,7 +36,7 @@ It will then automatically refresh the module DOM with the new data.
#### `fetchWeatherForecast()` #### `fetchWeatherForecast()`
This method is called when the weather module tries to fetch the weather weather of your provider. The implementation of this method is required. This method is called when the weather module tries to fetch the weather of your provider. The implementation of this method is required.
The implementation can make use of the already implemented function `this.fetchData(url, method, data);`, which is returning a promise. The implementation can make use of the already implemented function `this.fetchData(url, method, data);`, which is returning a promise.
After the response is processed, the weather forecast information (as an array of [WeatherObject](#weatherobject)s) needs to be set with `this.setCurrentWeather(forecast);`. After the response is processed, the weather forecast information (as an array of [WeatherObject](#weatherobject)s) needs to be set with `this.setCurrentWeather(forecast);`.
It will then automatically refresh the module DOM with the new data. It will then automatically refresh the module DOM with the new data.
@ -89,24 +89,24 @@ A convenience function to make requests. It returns a promise.
### WeatherObject ### WeatherObject
| Property | Type | Value/Unit | | Property | Type | Value/Unit |
| --- | --- | --- | | -------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
| units | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` | | units | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| tempUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` | | tempUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| windUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` | | windUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| date | `object` | [Moment.js](https://momentjs.com/) object of the time/date. | | date | `object` | [Moment.js](https://momentjs.com/) object of the time/date. |
| windSpeed |`number` | Metric: `meter/second` <br> Imperial: `miles/hour` | | windSpeed | `number` | Metric: `meter/second` <br> Imperial: `miles/hour` |
| windDirection |`number` | Direction of the wind in degrees. | | windDirection | `number` | Direction of the wind in degrees. |
| sunrise |`object` | [Moment.js](https://momentjs.com/) object of sunrise. | | sunrise | `object` | [Moment.js](https://momentjs.com/) object of sunrise. |
| sunset |`object` | [Moment.js](https://momentjs.com/) object of sunset. | | sunset | `object` | [Moment.js](https://momentjs.com/) object of sunset. |
| temperature | `number` | Current temperature | | temperature | `number` | Current temperature |
| minTemperature | `number` | Lowest temperature of the day. | | minTemperature | `number` | Lowest temperature of the day. |
| maxTemperature | `number` | Highest temperature of the day. | | maxTemperature | `number` | Highest temperature of the day. |
| weatherType | `string` | Icon name of the weather type. <br> Possible values: [WeatherIcons](https://www.npmjs.com/package/weathericons) | | weatherType | `string` | Icon name of the weather type. <br> Possible values: [WeatherIcons](https://www.npmjs.com/package/weathericons) |
| humidity | `number` | Percentage of humidity | | humidity | `number` | Percentage of humidity |
| rain | `number` | Metric: `millimeters` <br> Imperial: `inches` | | rain | `number` | Metric: `millimeters` <br> Imperial: `inches` |
| snow | `number` | Metric: `millimeters` <br> Imperial: `inches` | | snow | `number` | Metric: `millimeters` <br> Imperial: `inches` |
| precipitation | `number` | Metric: `millimeters` <br> Imperial: `inches` <br> UK Met Office provider: `percent` | | precipitation | `number` | Metric: `millimeters` <br> Imperial: `inches` <br> UK Met Office provider: `percent` |
#### Current weather #### Current weather

View File

@ -1,4 +1,4 @@
/* global WeatherProvider, WeatherDay */ /* global WeatherProvider, WeatherObject */
/* Magic Mirror /* Magic Mirror
* Module: Weather * Module: Weather
@ -16,40 +16,42 @@ WeatherProvider.register("darksky", {
providerName: "Dark Sky", providerName: "Dark Sky",
units: { units: {
imperial: 'us', imperial: "us",
metric: 'si' metric: "si"
}, },
fetchCurrentWeather() { fetchCurrentWeather() {
this.fetchData(this.getUrl()) this.fetchData(this.getUrl())
.then(data => { .then((data) => {
if(!data || !data.currently || typeof data.currently.temperature === "undefined") { if (!data || !data.currently || typeof data.currently.temperature === "undefined") {
// No usable data? // No usable data?
return; return;
} }
const currentWeather = this.generateWeatherDayFromCurrentWeather(data); const currentWeather = this.generateWeatherDayFromCurrentWeather(data);
this.setCurrentWeather(currentWeather); this.setCurrentWeather(currentWeather);
}).catch(function(request) { })
.catch(function (request) {
Log.error("Could not load data ... ", request); Log.error("Could not load data ... ", request);
}) })
.finally(() => this.updateAvailable()) .finally(() => this.updateAvailable());
}, },
fetchWeatherForecast() { fetchWeatherForecast() {
this.fetchData(this.getUrl()) this.fetchData(this.getUrl())
.then(data => { .then((data) => {
if(!data || !data.daily || !data.daily.data.length) { if (!data || !data.daily || !data.daily.data.length) {
// No usable data? // No usable data?
return; return;
} }
const forecast = this.generateWeatherObjectsFromForecast(data.daily.data); const forecast = this.generateWeatherObjectsFromForecast(data.daily.data);
this.setWeatherForecast(forecast); this.setWeatherForecast(forecast);
}).catch(function(request) { })
.catch(function (request) {
Log.error("Could not load data ... ", request); Log.error("Could not load data ... ", request);
}) })
.finally(() => this.updateAvailable()) .finally(() => this.updateAvailable());
}, },
// Create a URL from the config and base URL. // Create a URL from the config and base URL.
@ -109,12 +111,12 @@ WeatherProvider.register("darksky", {
const weatherTypes = { const weatherTypes = {
"clear-day": "day-sunny", "clear-day": "day-sunny",
"clear-night": "night-clear", "clear-night": "night-clear",
"rain": "rain", rain: "rain",
"snow": "snow", snow: "snow",
"sleet": "snow", sleet: "snow",
"wind": "wind", wind: "wind",
"fog": "fog", fog: "fog",
"cloudy": "cloudy", cloudy: "cloudy",
"partly-cloudy-day": "day-cloudy", "partly-cloudy-day": "day-cloudy",
"partly-cloudy-night": "night-cloudy" "partly-cloudy-night": "night-cloudy"
}; };

View File

@ -3,14 +3,12 @@
/* Magic Mirror /* Magic Mirror
* Module: Weather * Module: Weather
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
* *
* This class is the blueprint for a weather provider. * This class is the blueprint for a weather provider.
*/ */
WeatherProvider.register("openweathermap", { WeatherProvider.register("openweathermap", {
// Set the name of the provider. // Set the name of the provider.
// This isn't strictly necessary, since it will fallback to the provider identifier // This isn't strictly necessary, since it will fallback to the provider identifier
// But for debugging (and future alerts) it would be nice to have the real name. // But for debugging (and future alerts) it would be nice to have the real name.
@ -19,7 +17,7 @@ WeatherProvider.register("openweathermap", {
// Overwrite the fetchCurrentWeather method. // Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() { fetchCurrentWeather() {
this.fetchData(this.getUrl()) this.fetchData(this.getUrl())
.then(data => { .then((data) => {
if (!data || !data.main || typeof data.main.temp === "undefined") { if (!data || !data.main || typeof data.main.temp === "undefined") {
// Did not receive usable new data. // Did not receive usable new data.
// Maybe this needs a better check? // Maybe this needs a better check?
@ -31,16 +29,16 @@ WeatherProvider.register("openweathermap", {
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data); const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
this.setCurrentWeather(currentWeather); this.setCurrentWeather(currentWeather);
}) })
.catch(function(request) { .catch(function (request) {
Log.error("Could not load data ... ", request); Log.error("Could not load data ... ", request);
}) })
.finally(() => this.updateAvailable()) .finally(() => this.updateAvailable());
}, },
// Overwrite the fetchCurrentWeather method. // Overwrite the fetchCurrentWeather method.
fetchWeatherForecast() { fetchWeatherForecast() {
this.fetchData(this.getUrl()) this.fetchData(this.getUrl())
.then(data => { .then((data) => {
if (!data || !data.list || !data.list.length) { if (!data || !data.list || !data.list.length) {
// Did not receive usable new data. // Did not receive usable new data.
// Maybe this needs a better check? // Maybe this needs a better check?
@ -52,10 +50,10 @@ WeatherProvider.register("openweathermap", {
const forecast = this.generateWeatherObjectsFromForecast(data.list); const forecast = this.generateWeatherObjectsFromForecast(data.list);
this.setWeatherForecast(forecast); this.setWeatherForecast(forecast);
}) })
.catch(function(request) { .catch(function (request) {
Log.error("Could not load data ... ", request); Log.error("Could not load data ... ", request);
}) })
.finally(() => this.updateAvailable()) .finally(() => this.updateAvailable());
}, },
/** OpenWeatherMap Specific Methods - These are not part of the default provider methods */ /** OpenWeatherMap Specific Methods - These are not part of the default provider methods */
@ -87,7 +85,6 @@ WeatherProvider.register("openweathermap", {
* Generate WeatherObjects based on forecast information * Generate WeatherObjects based on forecast information
*/ */
generateWeatherObjectsFromForecast(forecasts) { generateWeatherObjectsFromForecast(forecasts) {
if (this.config.weatherEndpoint === "/forecast") { if (this.config.weatherEndpoint === "/forecast") {
return this.fetchForecastHourly(forecasts); return this.fetchForecastHourly(forecasts);
} else if (this.config.weatherEndpoint === "/forecast/daily") { } else if (this.config.weatherEndpoint === "/forecast/daily") {
@ -114,7 +111,6 @@ WeatherProvider.register("openweathermap", {
let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
for (const forecast of forecasts) { for (const forecast of forecasts) {
if (date !== moment(forecast.dt, "X").format("YYYY-MM-DD")) { if (date !== moment(forecast.dt, "X").format("YYYY-MM-DD")) {
// calculate minimum/maximum temperature, specify rain amount // calculate minimum/maximum temperature, specify rain amount
weather.minTemperature = Math.min.apply(null, minTemp); weather.minTemperature = Math.min.apply(null, minTemp);
@ -140,7 +136,6 @@ WeatherProvider.register("openweathermap", {
// If the first value of today is later than 17:00, we have an icon at least! // If the first value of today is later than 17:00, we have an icon at least!
weather.weatherType = this.convertWeatherType(forecast.weather[0].icon); weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
} }
if (moment(forecast.dt, "X").format("H") >= 8 && moment(forecast.dt, "X").format("H") <= 17) { if (moment(forecast.dt, "X").format("H") >= 8 && moment(forecast.dt, "X").format("H") <= 17) {
@ -223,7 +218,7 @@ WeatherProvider.register("openweathermap", {
days.push(weather); days.push(weather);
} }
return days; return days;
}, },
/* /*
@ -261,16 +256,16 @@ WeatherProvider.register("openweathermap", {
*/ */
getParams() { getParams() {
let params = "?"; let params = "?";
if(this.config.locationID) { if (this.config.locationID) {
params += "id=" + this.config.locationID; params += "id=" + this.config.locationID;
} else if(this.config.location) { } else if (this.config.location) {
params += "q=" + this.config.location; params += "q=" + this.config.location;
} else if (this.firstEvent && this.firstEvent.geo) { } else if (this.firstEvent && this.firstEvent.geo) {
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon; params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
} else if (this.firstEvent && this.firstEvent.location) { } else if (this.firstEvent && this.firstEvent.location) {
params += "q=" + this.firstEvent.location; params += "q=" + this.firstEvent.location;
} else { } else {
this.hide(this.config.animationSpeed, {lockString:this.identifier}); this.hide(this.config.animationSpeed, { lockString: this.identifier });
return; return;
} }

View File

@ -1,4 +1,4 @@
/* global WeatherProvider, WeatherObject */ /* global WeatherProvider, WeatherObject, SunCalc */
/* Magic Mirror /* Magic Mirror
* Module: Weather * Module: Weather
@ -8,10 +8,7 @@
* *
* This class is a provider for UK Met Office Datapoint. * This class is a provider for UK Met Office Datapoint.
*/ */
WeatherProvider.register("ukmetoffice", { WeatherProvider.register("ukmetoffice", {
// Set the name of the provider. // Set the name of the provider.
// This isn't strictly necessary, since it will fallback to the provider identifier // This isn't strictly necessary, since it will fallback to the provider identifier
// But for debugging (and future alerts) it would be nice to have the real name. // But for debugging (and future alerts) it would be nice to have the real name.
@ -25,9 +22,8 @@ WeatherProvider.register("ukmetoffice", {
// Overwrite the fetchCurrentWeather method. // Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() { fetchCurrentWeather() {
this.fetchData(this.getUrl("3hourly")) this.fetchData(this.getUrl("3hourly"))
.then(data => { .then((data) => {
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location || if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location || !data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length === 0) {
!data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length == 0) {
// Did not receive usable new data. // Did not receive usable new data.
// Maybe this needs a better check? // Maybe this needs a better check?
return; return;
@ -38,18 +34,17 @@ WeatherProvider.register("ukmetoffice", {
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data); const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
this.setCurrentWeather(currentWeather); this.setCurrentWeather(currentWeather);
}) })
.catch(function(request) { .catch(function (request) {
Log.error("Could not load data ... ", request); Log.error("Could not load data ... ", request);
}) })
.finally(() => this.updateAvailable()) .finally(() => this.updateAvailable());
}, },
// Overwrite the fetchCurrentWeather method. // Overwrite the fetchCurrentWeather method.
fetchWeatherForecast() { fetchWeatherForecast() {
this.fetchData(this.getUrl("daily")) this.fetchData(this.getUrl("daily"))
.then(data => { .then((data) => {
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location || if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location || !data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length === 0) {
!data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length == 0) {
// Did not receive usable new data. // Did not receive usable new data.
// Maybe this needs a better check? // Maybe this needs a better check?
return; return;
@ -60,14 +55,12 @@ WeatherProvider.register("ukmetoffice", {
const forecast = this.generateWeatherObjectsFromForecast(data); const forecast = this.generateWeatherObjectsFromForecast(data);
this.setWeatherForecast(forecast); this.setWeatherForecast(forecast);
}) })
.catch(function(request) { .catch(function (request) {
Log.error("Could not load data ... ", request); Log.error("Could not load data ... ", request);
}) })
.finally(() => this.updateAvailable()) .finally(() => this.updateAvailable());
}, },
/** UK Met Office Specific Methods - These are not part of the default provider methods */ /** UK Met Office Specific Methods - These are not part of the default provider methods */
/* /*
* Gets the complete url for the request * Gets the complete url for the request
@ -83,24 +76,23 @@ WeatherProvider.register("ukmetoffice", {
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
// data times are always UTC // data times are always UTC
let nowUtc = moment.utc() let nowUtc = moment.utc();
let midnightUtc = nowUtc.clone().startOf("day") let midnightUtc = nowUtc.clone().startOf("day");
let timeInMins = nowUtc.diff(midnightUtc, "minutes"); let timeInMins = nowUtc.diff(midnightUtc, "minutes");
// loop round each of the (5) periods, look for today (the first period may be yesterday) // loop round each of the (5) periods, look for today (the first period may be yesterday)
for (i in currentWeatherData.SiteRep.DV.Location.Period) { 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") let periodDate = moment.utc(currentWeatherData.SiteRep.DV.Location.Period[i].value.substr(0, 10), "YYYY-MM-DD");
// ignore if period is before today // ignore if period is before today
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) { if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
// check this is the period we want, after today the diff will be -ve // check this is the period we want, after today the diff will be -ve
if (moment().diff(periodDate, "minutes") > 0) { if (moment().diff(periodDate, "minutes") > 0) {
// loop round the reports looking for the one we are in // loop round the reports looking for the one we are in
// $ value specifies the time in minutes-of-the-day: 0, 180, 360,...1260 // $ value specifies the time in minutes-of-the-day: 0, 180, 360,...1260
for (j in currentWeatherData.SiteRep.DV.Location.Period[i].Rep){ for (var j in currentWeatherData.SiteRep.DV.Location.Period[i].Rep) {
let p = currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].$; let p = currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].$;
if (timeInMins >= p && timeInMins-180 < p) { if (timeInMins >= p && timeInMins - 180 < p) {
// finally got the one we want, so populate weather object // finally got the one we want, so populate weather object
currentWeather.humidity = currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].H; 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.temperature = this.convertTemp(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].T);
@ -116,7 +108,7 @@ WeatherProvider.register("ukmetoffice", {
} }
// determine the sunrise/sunset times - not supplied in UK Met Office data // determine the sunrise/sunset times - not supplied in UK Met Office data
let times = this.calcAstroData(currentWeatherData.SiteRep.DV.Location) let times = this.calcAstroData(currentWeatherData.SiteRep.DV.Location);
currentWeather.sunrise = times[0]; currentWeather.sunrise = times[0];
currentWeather.sunset = times[1]; currentWeather.sunset = times[1];
@ -127,22 +119,21 @@ WeatherProvider.register("ukmetoffice", {
* Generate WeatherObjects based on forecast information * Generate WeatherObjects based on forecast information
*/ */
generateWeatherObjectsFromForecast(forecasts) { generateWeatherObjectsFromForecast(forecasts) {
const days = []; const days = [];
// loop round the (5) periods getting the data // loop round the (5) periods getting the data
// for each period array, Day is [0], Night is [1] // for each period array, Day is [0], Night is [1]
for (j in forecasts.SiteRep.DV.Location.Period) { for (var j in forecasts.SiteRep.DV.Location.Period) {
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
// data times are always UTC // data times are always UTC
dateStr = forecasts.SiteRep.DV.Location.Period[j].value const dateStr = forecasts.SiteRep.DV.Location.Period[j].value;
let periodDate = moment.utc(dateStr.substr(0,10), "YYYY-MM-DD") let periodDate = moment.utc(dateStr.substr(0, 10), "YYYY-MM-DD");
// ignore if period is before today // ignore if period is before today
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) { if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
// populate the weather object // populate the weather object
weather.date = moment.utc(dateStr.substr(0,10), "YYYY-MM-DD"); 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.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.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.weatherType = this.convertWeatherType(forecasts.SiteRep.DV.Location.Period[j].Rep[0].W);
@ -213,7 +204,7 @@ WeatherProvider.register("ukmetoffice", {
* Convert temp (from degrees C) if required * Convert temp (from degrees C) if required
*/ */
convertTemp(tempInC) { convertTemp(tempInC) {
return this.tempUnits === "imperial" ? tempInC * 9 / 5 + 32 : tempInC; return this.tempUnits === "imperial" ? (tempInC * 9) / 5 + 32 : tempInC;
}, },
/* /*
@ -228,22 +219,22 @@ WeatherProvider.register("ukmetoffice", {
*/ */
convertWindDirection(windDirection) { convertWindDirection(windDirection) {
const windCardinals = { const windCardinals = {
"N": 0, N: 0,
"NNE": 22, NNE: 22,
"NE": 45, NE: 45,
"ENE": 67, ENE: 67,
"E": 90, E: 90,
"ESE": 112, ESE: 112,
"SE": 135, SE: 135,
"SSE": 157, SSE: 157,
"S": 180, S: 180,
"SSW": 202, SSW: 202,
"SW": 225, SW: 225,
"WSW": 247, WSW: 247,
"W": 270, W: 270,
"WNW": 292, WNW: 292,
"NW": 315, NW: 315,
"NNW": 337 NNW: 337
}; };
return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null; return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null;

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