Merge pull request #1627 from MichMich/develop

Release 2.7.0
This commit is contained in:
Michael Teeuw 2019-04-01 21:54:05 +02:00 committed by GitHub
commit e6fb18df56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 1307 additions and 599 deletions

View File

@ -1,6 +1,6 @@
language: node_js
node_js:
- "7"
- "8"
before_script:
- yarn danger ci
- npm install grunt-cli -g

View File

@ -5,6 +5,71 @@ This project adheres to [Semantic Versioning](http://semver.org/).
---
❤️ **Donate:** Enjoying MagicMirror²? [Please consider a donation!](https://magicmirror.builders/donate) With your help we can continue to improve the MagicMirror² core.
## [2.8.0] - Unreleased
*This release is scheduled to be released on 2019-04-01.*
### Added
### Updated
### Fixed
## [2.7.0] - 2019-04-01
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues running Electron, make sure your [Raspbian is up to date](https://www.raspberrypi.org/documentation/raspbian/updating.md).
### Added
- Italian translation for "Feels"
- Basic Klingon (tlhIngan Hol) translations
- Disabled the screensaver on raspbian with installation script
- Added option to truncate the number of vertical lines a calendar item can span if `wrapEvents` is enabled.
- Danish translation for "Feels" and "Weeks"
- Added option to split multiple day events in calendar to separate numbered events
- Slovakian translation
- Alerts now can contain Font Awesome icons
- Notifications display time can be set in request
- Newsfeed: added support for `ARTICLE_INFO_REQUEST` notification
- Add `name` config option for calendars to be sent along with event broadcasts
### Updated
- Bumped the Electron dependency to v3.0.13 to support the most recent Raspbian. [#1500](https://github.com/MichMich/MagicMirror/issues/1500)
- Updated modernizr code in alert module, fixed a small typo there too
- More verbose error message on console if the config is malformed
- Updated installer script to install Node.js version 10.x
### Fixed
- Fixed temperature displays in currentweather and weatherforecast modules [#1503](https://github.com/MichMich/MagicMirror/issues/1503), [#1511](https://github.com/MichMich/MagicMirror/issues/1511).
- Fixed unhandled error on bad git data in updatenotification module [#1285](https://github.com/MichMich/MagicMirror/issues/1285).
- Weather forecast now works with openweathermap in new weather module. Daily data are displayed, see issue [#1504](https://github.com/MichMich/MagicMirror/issues/1504).
- Fixed analogue clock border display issue where non-black backgrounds used (previous fix for issue 611)
- Fixed compatibility issues caused when modules request different versions of Font Awesome, see issue [#1522](https://github.com/MichMich/MagicMirror/issues/1522). MagicMirror now uses [Font Awesome 5 with v4 shims included for backwards compatibility](https://fontawesome.com/how-to-use/on-the-web/setup/upgrading-from-version-4#shims).
- Installation script problems with raspbian
- Calendar: only show repeating count if the event is actually repeating [#1534](https://github.com/MichMich/MagicMirror/pull/1534)
- Calendar: Fix exdate handling when multiple values are specified (comma separated)
- Calendar: Fix relative date handling for fulldate events, calculate difference always from start of day [#1572](https://github.com/MichMich/MagicMirror/issues/1572)
- Fix null dereference in moduleNeedsUpdate when the module isn't visible
- Calendar: Fixed event end times by setting default calendarEndTime to "LT" (Local time format). [#1479]
- Calendar: Fixed missing calendar fetchers after server process restarts [#1589](https://github.com/MichMich/MagicMirror/issues/1589)
- Notification: fixed background color (was white text on white background)
- Use getHeader instead of data.header when creating the DOM so overwriting the function also propagates into it
- Fix documentation of `useKMPHwind` option in currentweather
### New weather module
- Fixed weather forecast table display [#1499](https://github.com/MichMich/MagicMirror/issues/1499).
- Dimmed loading indicator for weather forecast.
- Implemented config option `decimalSymbol` [#1499](https://github.com/MichMich/MagicMirror/issues/1499).
- Aligned indoor values in current weather vertical [#1499](https://github.com/MichMich/MagicMirror/issues/1499).
- Added humidity support to nunjuck unit filter.
- Do not display degree symbol for temperature in Kelvin [#1503](https://github.com/MichMich/MagicMirror/issues/1503).
- Weather forecast now works with openweathermap for both, `/forecast` and `/forecast/daily`, in new weather module. If you use the `/forecast`-weatherEndpoint, the hourly data are converted to daily data, see issues [#1504](https://github.com/MichMich/MagicMirror/issues/1504), [#1513](https://github.com/MichMich/MagicMirror/issues/1513).
- Added fade, fadePoint and maxNumberOfDays properties to the forecast mode [#1516](https://github.com/MichMich/MagicMirror/issues/1516)
- Fixed Loading string and decimalSymbol string replace [#1538](https://github.com/MichMich/MagicMirror/issues/1538)
- Show Snow amounts in new weather module [#1545](https://github.com/MichMich/MagicMirror/issues/1545)
- Added weather.gov as a new weather provider for US locations
## [2.6.0] - 2019-01-01
**Note:** This update uses new dependencies. Please update using the following command: `git pull && npm install`. If you are having issues updating, make sure you are running the latest version of Node.
@ -12,6 +77,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### ✨ Experimental ✨
- New default [module weather](modules/default/weather). This module will eventually replace the current `currentweather` and `weatherforecast` modules. The new module is still pretty experimental, but it's included so you can give it a try and help us improve this module. Please give us you feedback using [this forum post](https://forum.magicmirror.builders/topic/9335/default-weather-module-refactoring).
A huge, huge, huge thanks to user @fewieden for all his hard work on the new `weather` module!
### Added
- Possibility to add classes to the cell of symbol, title and time of the events of calendar.
- Font-awesome 5, still has 4 for backwards compatibility.
@ -60,7 +127,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Fixed gzip encoded calendar loading issue #1400.
- Mixup between german and spanish translation for newsfeed.
- Fixed close dates to be absolute, if no configured in the config.js - module Calendar
- Fixed the UpdateNotification module message about new commits in the repository, so they can be correctly localized in singular and plural form.
- Fixed the updatenotification module message about new commits in the repository, so they can be correctly localized in singular and plural form.
- Fix for weatherforecast rainfall rounding [#1374](https://github.com/MichMich/MagicMirror/issues/1374)
- Fix calendar parsing issue for Midori on RasperryPi Zero w, related to issue #694.
- Fix weather city ID link in sample config
@ -210,7 +277,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Add `clientonly` script to start only the electron client for a remote server.
- Add symbol and color properties of event when `CALENDAR_EVENTS` notification is broadcasted from `default/calendar` module.
- Add `.vscode/` folder to `.gitignore` to keep custom Visual Studio Code config out of git.
- Add unit test the capitalizeFirstLetter function of newfeed module.
- Add unit test the capitalizeFirstLetter function of newsfeed module.
- Add new unit tests for function `shorten` in calendar module.
- Add new unit tests for function `getLocaleSpecification` in calendar module.
- Add unit test for js/class.js.
@ -231,7 +298,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Set version of the `express-ipfilter` on 0.3.1.
### Fixed
- Fixed issue with incorrect allignment of analog clock when displayed in the center column of the MM.
- Fixed issue with incorrect alignment of analog clock when displayed in the center column of the MM.
- Fixed ipWhitelist behaviour to make empty whitelist ([]) allow any and all hosts access to the MM.
- Fixed issue with calendar module where 'excludedEvents' count towards 'maximumEntries'.
- Fixed issue with calendar module where global configuration of maximumEntries was not overridden by calendar specific config (see module doc).
@ -255,7 +322,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Add unit test calendar_modules function capFirst.
- Add test for check if exists the directories present in defaults modules.
- Add support for showing wind direction as an arrow instead of abbreviation in currentWeather module.
- Add support for writing translation fucntions to support flexible word order
- Add support for writing translation functions to support flexible word order
- Add test for check if exits the directories present in defaults modules.
- Add calendar option to set a separate date format for full day events.
- Add ability for `currentweather` module to display indoor temperature via INDOOR_TEMPERATURE notification
@ -274,7 +341,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Fix double message about port when server is starting
- Corrected Swedish translations for TODAY/TOMORROW/DAYAFTERTOMORROW.
- Removed unused import from js/electron.js
- Made calendar.js respect config.timeFormat irrespecive of locale setting.
- Made calendar.js respect config.timeFormat irrespective of locale setting.
- Fixed alignment of analog clock when a large calendar is displayed in the same side bar.
## [2.1.1] - 2017-04-01
@ -292,7 +359,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Added `DAYAFTERTOMORROW`, `UPDATE_NOTIFICATION` and `UPDATE_NOTIFICATION_MODULE` to Finnish translations.
- Run `npm test` on Travis automatically.
- Show the splash screen image even when is reboot or halted.
- Added some missing translaton strings in the sv.json file.
- Added some missing translation strings in the sv.json file.
- Run task jsonlint to check translation files.
- Restructured Test Suite.
@ -309,12 +376,12 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Option to use RegExp in Calendar's titleReplace.
- Hungarian Translation.
- Icelandic Translation.
- Add use a script to prevent when is run by SSH session set DISPLAY enviroment.
- Enable ability to set configuration file by the enviroment variable called MM_CONFIG_FILE.
- Add use a script to prevent when is run by SSH session set DISPLAY environment.
- Enable ability to set configuration file by the environment variable called MM_CONFIG_FILE.
- Option to give each calendar a different color.
- Option for colored min-temp and max-temp.
- Add test e2e helloworld.
- Add test e2e enviroment.
- Add test e2e environment.
- Add `chai-as-promised` npm module to devDependencies.
- Basic set of tests for clock module.
- Run e2e test in Travis.
@ -332,10 +399,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Added tests for Translations, dev argument, version, dev console.
- Added test anytime feature compliments module.
- Added test ipwhitelist configuration directive.
- Added test for calendar module: default, basic-auth, backward compability, fail-basic-auth.
- Added test for calendar module: default, basic-auth, backward compatibility, fail-basic-auth.
- Added meta tags to support fullscreen mode on iOS (for server mode)
- Added `ignoreOldItems` and `ignoreOlderThan` options to the News Feed module
- Added test for MM_PORT enviroment variable.
- Added test for MM_PORT environment variable.
- Added a configurable Week section to the clock module.
### Fixed
@ -347,7 +414,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Module currentWeather: check if temperature received from api is defined.
- Fix an issue with module hidden status changing to `true` although lock string prevented showing it.
- Fix newsfeed module bug (removeStartTags)
- Fix when is set MM_PORT enviroment variable.
- Fix when is set MM_PORT environment variable.
- Fixed missing animation on `this.show(speed)` when module is alone in a region.
## [2.1.0] - 2016-12-31
@ -369,8 +436,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Calendar module now broadcasts the event list to all other modules using the notification system. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/calendar) for more information.
- Possibility to use the the calendar feed as the source for the weather (currentweather & weatherforecast) location data. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/weatherforecast) for more information.
- Added option to show rain amount in the weatherforecast default module
- Add module `updatenotification` to get an update whenever a new version is availabe. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/updatenotification) for more information.
- Add the abilty to set timezone on the date display in the Clock Module
- Add module `updatenotification` to get an update whenever a new version is available. [See documentation](https://github.com/MichMich/MagicMirror/tree/develop/modules/default/updatenotification) for more information.
- Add the ability to set timezone on the date display in the Clock Module
- Ability to set date format in calendar module
- Possibility to use currentweather for the compliments
- Added option `disabled` for modules.
@ -409,7 +476,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Added ability to define "the day after tomorrow" for calendar events (Definition for German and Dutch already included).
- Added CII Badge (we are compliant with the CII Best Practices)
- Add support for doing http basic auth when loading calendars
- Add the abilty to turn off and on the date display in the Clock Module
- Add the ability to turn off and on the date display in the Clock Module
### Fixed
- Fix typo in installer.
@ -432,8 +499,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
- Prevent `getModules()` selectors from returning duplicate entries.
- Append endpoints of weather modules with `/` to retreive the correct data. (Issue [#337](https://github.com/MichMich/MagicMirror/issues/337))
- Corrected grammer in `module.js` from 'suspend' to 'suspended'.
- Append endpoints of weather modules with `/` to retrieve the correct data. (Issue [#337](https://github.com/MichMich/MagicMirror/issues/337))
- Corrected grammar in `module.js` from 'suspend' to 'suspended'.
- Fixed openweathermap.org URL in config sample.
- Prevent currentweather module from crashing when received data object is incorrect.
- Fix issue where translation loading prevented the UI start-up when the language was set to 'en'. (Issue [#388](https://github.com/MichMich/MagicMirror/issues/388))

View File

@ -7,7 +7,6 @@
<a href="http://choosealicense.com/licenses/mit"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
<a href="https://travis-ci.org/MichMich/MagicMirror"><img src="https://travis-ci.org/MichMich/MagicMirror.svg" alt="Travis"></a>
<a href="https://snyk.io/test/github/MichMich/MagicMirror"><img src="https://snyk.io/test/github/MichMich/MagicMirror/badge.svg" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/MichMich/MagicMirror" style="max-width:100%;"></a>
<a href="http://slack.magicmirror.builders"><img src="http://slack.magicmirror.builders:3000/badge.svg" alt="Slack Status"></a>
</p>
**MagicMirror²** is an open source modular smart mirror platform. With a growing list of installable modules, the **MagicMirror²** allows you to convert your hallway or bathroom mirror into your personal assistant. **MagicMirror²** is built by the creator of [the original MagicMirror](http://michaelteeuw.nl/tagged/magicmirror) with the incredible help of a [growing community of contributors](https://github.com/MichMich/MagicMirror/graphs/contributors).
@ -16,18 +15,22 @@ MagicMirror² focuses on a modular plugin system and uses [Electron](http://elec
## Table Of Contents
- [Table Of Contents](#table-of-contents)
- [Installation](#installation)
- [Raspberry Pi](#raspberry-pi)
- [General](#general)
- [Automatic Installation (Raspberry Pi only!)](#automatic-installation-raspberry-pi-only)
- [Manual Installation](#manual-installation)
- [Server Only](#server-only)
- [Client Only](#client-only)
- [Docker](#docker)
- [Configuration](#configuration)
- [Raspberry Specific](#raspberry-specific)
- [General](#general)
- [Modules](#modules)
- [Updating](#updating)
- [Known Issues](#known-issues)
- [Community](#community)
- [Contributing Guidelines](#contributing-guidelines)
- [Enjoying MagicMirror? Consider a donation!](#enjoying-magicmirror-consider-a-donation)
- [Manifesto](#manifesto)
## Installation
@ -125,7 +128,7 @@ The following wiki links are helpful for the initial configuration of your Magic
**Note:** If you used the installer script. This step is already done for you.
2. Modify your required settings. \
Note: You'll can check your configuration running `npm run config:check` in `/home/pi/MagicMirror`.
Note: You can check your configuration running `npm run config:check` in `/home/pi/MagicMirror`.
The following properties can be configured:
@ -199,6 +202,16 @@ Please keep the following in mind:
Thanks for your help in making MagicMirror² better!
## Enjoying MagicMirror? Consider a donation!
MagicMirror² is opensource and free. That doesn't mean we don't need any money.
Please consider a donation to help us cover the ongoing costs like webservers and email services.
If we recieve enough donations we might even be able to free up some working hours and spend some extra time improving the MagicMirror² core.
To donate, please follow [this](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=G5D8E9MR5DTD2&source=url) link.
## Manifesto
A real Manifesto is still to be written. Till then, Michael's response on [one of the repository issues](https://github.com/MichMich/MagicMirror/issues/1174) gives a great summary:

View File

@ -47,7 +47,7 @@ sudo apt-get --assume-yes install curl wget git build-essential unzip || exit
# Check if we need to install or upgrade Node.js.
echo -e "\e[96mCheck current Node installation ...\e[0m"
NODE_INSTALL=false
if command_exists node; then
if command_exists node && command_exists npm; then
echo -e "\e[0mNode currently installed. Checking version number.";
NODE_CURRENT=$(node -v)
echo -e "\e[0mMinimum Node version: \e[1m$NODE_TESTED\e[0m"
@ -82,7 +82,7 @@ if $NODE_INSTALL; then
# The NODE_STABLE_BRANCH variable will need to be manually adjusted when a new branch is released. (e.g. 7.x)
# Only tested (stable) versions are recommended as newer versions could break MagicMirror.
NODE_STABLE_BRANCH="9.x"
NODE_STABLE_BRANCH="10.x"
curl -sL https://deb.nodesource.com/setup_$NODE_STABLE_BRANCH | sudo -E bash -
sudo apt-get install -y nodejs
echo -e "\e[92mNode.js installation Done!\e[0m"
@ -152,9 +152,19 @@ fi
read -p "Do you want use pm2 for auto starting of your MagicMirror (y/N)?" choice
if [[ $choice =~ ^[Yy]$ ]]; then
sudo npm install -g pm2
sudo su -c "env PATH=$PATH:/usr/bin pm2 startup linux -u pi --hp /home/pi"
pm2 start ~/MagicMirror/installers/pm2_MagicMirror.json
pm2 save
if [[ "$(ps --no-headers -o comm 1)" =~ systemd ]]; then #Checking for systemd
sudo pm2 startup systemd -u pi --hp /home/pi
else
sudo su -c "env PATH=$PATH:/usr/bin pm2 startup linux -u pi --hp /home/pi"
fi
pm2 start ~/MagicMirror/installers/pm2_MagicMirror.json
pm2 save
fi
# Disable Screensaver
read -p "Do you want to disable the screen saver? (y/N)?" choice
if [[ $choice =~ ^[Yy]$ ]]; then
sudo su -c "echo -e '@xset s noblank\n@xset s off\n@xset -dpms' >> /etc/xdg/lxsession/LXDE-pi/autostart"
export DISPLAY=:0; xset s noblank;xset s off;xset -dpms
fi
echo " "

View File

@ -70,7 +70,7 @@ var App = function() {
if (e.code == "ENOENT") {
console.error(Utils.colors.error("WARNING! Could not find config file. Please create one. Starting with default configuration."));
} else if (e instanceof ReferenceError || e instanceof SyntaxError) {
console.error(Utils.colors.error("WARNING! Could not validate config file. Please correct syntax errors. Starting with default configuration."));
console.error(Utils.colors.error("WARNING! Could not validate config file. Starting with default configuration. Please correct syntax errors at or above this line: " + e.stack));
} else {
console.error(Utils.colors.error("WARNING! Could not load config file. Starting with default configuration. Error found: " + e));
}

View File

@ -39,9 +39,9 @@ var MM = (function() {
dom.opacity = 0;
wrapper.appendChild(dom);
if (typeof module.data.header !== "undefined" && module.data.header !== "") {
if (typeof module.getHeader() !== "undefined" && module.getHeader() !== "") {
var moduleHeader = document.createElement("header");
moduleHeader.innerHTML = module.data.header;
moduleHeader.innerHTML = module.getHeader();
moduleHeader.className = "module-header";
dom.appendChild(moduleHeader);
}
@ -173,6 +173,10 @@ var MM = (function() {
*/
var moduleNeedsUpdate = function(module, newHeader, newContent) {
var moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper === null) {
return false;
}
var contentWrapper = moduleWrapper.getElementsByClassName("module-content");
var headerWrapper = moduleWrapper.getElementsByClassName("module-header");

View File

@ -212,7 +212,7 @@ var Module = Class.extend({
/* setData(data)
* Set the module data.
*
* argument data obejct - Module data.
* argument data object - Module data.
*/
setData: function (data) {
this.data = data;
@ -226,7 +226,7 @@ var Module = Class.extend({
/* setConfig(config)
* Set the module config and combine it with the module defaults.
*
* argument config obejct - Module config.
* argument config object - Module config.
*/
setConfig: function (config) {
this.config = Object.assign({}, this.defaults, config);

View File

@ -33,8 +33,8 @@ Therefore **we highly recommend you to include the following information in your
- A high quality screenshot of your working module
- A short, one sentence, clear description what it does (duh!)
- What external API's it depend on, including web links to those
- Wheteher the API/request require a key and the user limitations of those. (Is it free?)
- What external API's it depends upon, including web links to those
- Whether the API/request require a key and the user limitations of those. (Is it free?)
Surely this also help you get better recognition and feedback for your work.
@ -46,8 +46,8 @@ A module can be placed in one single folder. Or multiple modules can be grouped
### Files
- **modulename/modulename.js** - This is your core module script.
- **modulename/node_helper.js** - This is an optional helper that will be loaded by the node script. The node helper and module script can communicate with each other using an intergrated socket system.
- **modulename/public** - Any files in this folder can be accesed via the browser on `/modulename/filename.ext`.
- **modulename/node_helper.js** - This is an optional helper that will be loaded by the node script. The node helper and module script can communicate with each other using an integrated socket system.
- **modulename/public** - Any files in this folder can be accessed via the browser on `/modulename/filename.ext`.
- **modulename/anyfileorfolder** Any other file or folder in the module folder can be used by the core module script. For example: *modulename/css/modulename.css* would be a good path for your additional module styles.
## The Core module file: modulename.js
@ -89,7 +89,7 @@ After the module is initialized, the module instance has a few available module
| `this.data` | Object | The data object contain additional metadata about the module instance. (See below) |
The `this.data` data object contain the follwoing metadata:
The `this.data` data object contain the following metadata:
- `data.classes` - The classes which are added to the module dom wrapper.
- `data.file` - The filename of the core module file.
- `data.path` - The path of the module folder.
@ -98,7 +98,7 @@ The `this.data` data object contain the follwoing metadata:
#### `defaults: {}`
Any properties defined in the defaults object, will be merged with the module config as defined in the user's config.js file. This is the best place to set your modules's configuration defaults. Any of the module configuration properties can be accessed using `this.config.propertyName`, but more about that later.
Any properties defined in the defaults object, will be merged with the module config as defined in the user's config.js file. This is the best place to set your modules' configuration defaults. Any of the module configuration properties can be accessed using `this.config.propertyName`, but more about that later.
#### `requiresVersion:`
@ -134,7 +134,7 @@ loaded: function(callback) {
````
#### `start()`
This method is called when all modules are loaded an the system is ready to boot up. Keep in mind that the dom object for the module is not yet created. The start method is a perfect place to define any additional module properties:
This method is called when all modules are loaded and the system is ready to boot up. Keep in mind that the dom object for the module is not yet created. The start method is a perfect place to define any additional module properties:
**Example:**
````javascript
@ -161,7 +161,7 @@ getScripts: function() {
}
````
**Note:** If a file can not be loaded, the boot up of the mirror will stall. Therefore it's advised not to use any external urls.
**Note:** If a file can not be loaded, the boot up of the mirror will stall. Therefore, it's advised not to use any external urls.
#### `getStyles()`
@ -174,14 +174,14 @@ The getStyles method is called to request any additional stylesheets that need t
getStyles: function() {
return [
'script.css', // will try to load it from the vendor folder, otherwise it will load is from the module folder.
'font-awesome.css', // this file is available in the vendor folder, so it doesn't need to be avialable in the module folder.
'font-awesome.css', // this file is available in the vendor folder, so it doesn't need to be available in the module folder.
this.file('anotherfile.css'), // this file will be loaded straight from the module folder.
'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css', // this file will be loaded from the bootstrapcdn servers.
]
}
````
**Note:** If a file can not be loaded, the boot up of the mirror will stall. Therefore it's advised not to use any external urls.
**Note:** If a file can not be loaded, the boot up of the mirror will stall. Therefore, it's advised not to use any external URLs.
#### `getTranslations()`
**Should return: Dictionary**
@ -239,7 +239,7 @@ That MagicMirror core has the ability to send notifications to modules. Or even
- `notification` - String - The notification identifier.
- `payload` - AnyType - The payload of a notification.
- `sender` - Module - The sender of the notification. If this argument is `undefined`, the sender of the notififiction is the core system.
- `sender` - Module - The sender of the notification. If this argument is `undefined`, the sender of the notification is the core system.
**Example:**
````javascript
@ -346,7 +346,7 @@ Possible configurable options:
- `lockString` - String - When setting lock string, the module can not be shown without passing the correct lockstring. This way (multiple) modules can prevent a module from showing. It's considered best practice to use your modules identifier as the locksString: `this.identifier`. See *visibility locking* below.
**Note 1:** If the hide animation is canceled, for instance because the show method is called before the hide animation was finished, the callback will not be called.<br>
**Note 1:** If the hide animation is cancelled, for instance because the show method is called before the hide animation was finished, the callback will not be called.<br>
**Note 2:** If the hide animation is hijacked (an other method calls hide on the same module), the callback will not be called.<br>
**Note 3:** If the dom is not yet created, the hide method won't work. Wait for the `DOM_OBJECTS_CREATED` [notification](#notificationreceivednotification-payload-sender).
@ -371,7 +371,7 @@ Possible configurable options:
(*Introduced in version: 2.1.0.*)
Visiblity locking helps the module system to prevent unwanted hide/show actions. The following scenario explains the concept:
Visibility locking helps the module system to prevent unwanted hide/show actions. The following scenario explains the concept:
**Module B asks module A to hide:**
````javascript
@ -436,7 +436,7 @@ If no translation is found, a fallback will be used. The fallback sequence is as
- 4. Translation as defined in core translation file of the fallback language (the first defined core translation file).
- 5. The key (identifier) of the translation.
When adding translations to your module, it's a good idea to see if an apropriate translation is already available in the [core translation files](https://github.com/MichMich/MagicMirror/tree/master/translations). This way, your module can benefit from the existing translations.
When adding translations to your module, it's a good idea to see if an appropriate translation is already available in the [core translation files](https://github.com/MichMich/MagicMirror/tree/master/translations). This way, your module can benefit from the existing translations.
**Example:**
````javascript
@ -490,7 +490,7 @@ this.translate("RUNNING", {
)}); // Will return a translated string for the identifier RUNNING, replacing `{timeUntilEnd}` with the contents of the variable `timeUntilEnd` in the order that translator intended. (has a fallback)
````
**Example swedish .json file that does not have the variable in it:**
**Example Swedish .json file that does not have the variable in it:**
````javascript
{
"RUNNING": "Slutar",

View File

@ -43,10 +43,11 @@ self.sendNotification("SHOW_ALERT", {});
```
### Notification params
| Option | Description
| --------- | -----------
| `title` | The title of the notification. <br><br> **Possible values:** `text` or `html`
| `message` | The message of the notification. <br><br> **Possible values:** `text` or `html`
| Option | Description
| ------------------ | -----------
| `title` | The title of the notification. <br><br> **Possible values:** `text` or `html`
| `message` | The message of the notification. <br><br> **Possible values:** `text` or `html`
| `timer` (optional) | How long the notification should stay visible in ms. <br> If absent, the default `display_time` is used. <br> **Possible values:** `int` `float`
### Alert params
@ -61,4 +62,4 @@ self.sendNotification("SHOW_ALERT", {});
## Open Source Licenses
### [NotificationStyles](https://github.com/codrops/NotificationStyles)
See [ympanus.net](http://tympanus.net/codrops/licensing/) for license.
See [tympanus.net](http://tympanus.net/codrops/licensing/) for license.

View File

@ -24,7 +24,7 @@ Module.register("alert",{
return ["classie.js", "modernizr.custom.js", "notificationFx.js"];
},
getStyles: function() {
return ["ns-default.css"];
return ["ns-default.css", "font-awesome.css"];
},
// Define required translations.
getTranslations: function() {
@ -51,7 +51,7 @@ Module.register("alert",{
message: msg,
layout: "growl",
effect: this.config.effect,
ttl: this.config.display_time
ttl: message.timer !== undefined ? message.timer : this.config.display_time
}).show();
},
show_alert: function(params, sender) {

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
/* Based on work by http://tympanus.net/codrops/licensing/ */
.ns-box {
background: #fff;
background-color: rgba(0, 0, 0, 0.93);
padding: 17px;
line-height: 1.4;
margin-bottom: 10px;
@ -12,7 +12,10 @@
display: table;
word-wrap: break-word;
max-width: 100%;
border-width: 1px;
border-radius: 5px;
border-style: solid;
border-color: #666;
}
.ns-alert {

View File

@ -32,6 +32,7 @@ The following properties can be configured:
| `defaultSymbol` | The default symbol. <br><br> **Possible values:** See [Font Awsome](http://fontawesome.io/icons/) website. <br> **Default value:** `calendar`
| `maxTitleLength` | The maximum title length. <br><br> **Possible values:** `10` - `50` <br> **Default value:** `25`
| `wrapEvents` | Wrap event titles to multiple lines. Breaks lines at the length defined by `maxTitleLength`. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `maxTitleLines` | The maximum number of lines a title will wrap vertically before being cut (Only enabled if `wrapEvents` is also enabled). <br><br> **Possible values:** `0` - `10` <br> **Default value:** `3`
| `fetchInterval` | How often does the content needs to be fetched? (Milliseconds) <br><br> **Possible values:** `1000` - `86400000` <br> **Default value:** `300000` (5 minutes)
| `animationSpeed` | Speed of the update animation. (Milliseconds) <br><br> **Possible values:** `0` - `5000` <br> **Default value:** `2000` (2 seconds)
| `fade` | Fade the future events to black. (Gradient) <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
@ -52,6 +53,8 @@ The following properties can be configured:
| `hidePrivate` | Hides private calendar events. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `hideOngoing` | Hides calendar events that have already started. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `excludedEvents` | An array of words / phrases from event titles that will be excluded from being shown. <br><br>Additionally advanced filter objects can be passed in. Below is the configuration for the advance filtering object.<br>**Required**<br>`filterBy` - string used to determine if filter is applied.<br>**Optional**<br>`until` - Time before an event to display it Ex: [`'3 days'`, `'2 months'`, `'1 week'`]<br>`caseSensitive` - By default, excludedEvents are case insensitive, set this to true to enforce case sensitivity<br>`regex` - set to `true` if filterBy is a regex. For those not familiar with regex it is used for pattern matching, please see [here](https://regexr.com/) for more info.<br><br> **Example:** `['Birthday', 'Hide This Event', {filterBy: 'Payment', until: '6 days', caseSensitive: true}, {filterBy: '^[0-9]{1,}.*', regex: true}]` <br> **Default value:** `[]`
| `sliceMultiDayEvents` | If this is set to true, events exceeding at least one midnight will be sliced into separate events including a counter like (1/2). This is especially helpful in "dateheaders" mode. Events will be sliced at midnight, end time for all events but the last will be 23:59 **Default value:** `true`
### Calendar configuration
@ -87,6 +90,7 @@ config: {
| `repeatingCountTitle` | The count title for yearly repating events in this calendar. <br><br> **Example:** `'Birthday'`
| `maximumEntries` | The maximum number of events shown. Overrides global setting. **Possible values:** `0` - `100`
| `maximumNumberOfDays` | The maximum number of days in the future. Overrides global setting
| `name` | The name of the calendar. Included in event broadcasts as `calendarName`.
| `auth` | The object containing options for authentication against the calendar.
| `symbolClass` | Add a class to the cell of symbol.
| `titleClass` | Add a class to the title's cell.

View File

@ -19,13 +19,14 @@ Module.register("calendar", {
defaultRepeatingCountTitle: "",
maxTitleLength: 25,
wrapEvents: false, // wrap events to multiple lines breaking at maxTitleLength
maxTitleLines: 3,
fetchInterval: 5 * 60 * 1000, // Update every 5 minutes.
animationSpeed: 2000,
fade: true,
urgency: 7,
timeFormat: "relative",
dateFormat: "MMM Do",
dateEndFormat: "HH:mm",
dateEndFormat: "LT",
fullDayEventDateFormat: "MMM Do",
showEnd: false,
getRelative: 6,
@ -46,12 +47,13 @@ Module.register("calendar", {
"'s birthday": ""
},
broadcastEvents: true,
excludedEvents: []
excludedEvents: [],
sliceMultiDayEvents: false
},
// Define required scripts.
getStyles: function () {
return ["calendar.css", "font-awesome5.css", "font-awesome5.v4shims.css"];
return ["calendar.css", "font-awesome.css"];
},
// Define required scripts.
@ -103,6 +105,13 @@ Module.register("calendar", {
}
this.addCalendar(calendar.url, calendar.auth, calendarConfig);
// Trigger ADD_CALENDAR every fetchInterval to make sure there is always a calendar
// fetcher running on the server side.
var self = this;
setInterval(function() {
self.addCalendar(calendar.url, calendar.auth, calendarConfig);
}, self.config.fetchInterval);
}
this.calendarData = {};
@ -220,7 +229,7 @@ Module.register("calendar", {
var titleWrapper = document.createElement("td"),
repeatingCountTitle = "";
if (this.config.displayRepeatingCountTitle) {
if (this.config.displayRepeatingCountTitle && event.firstYear !== undefined) {
repeatingCountTitle = this.countTitleForUrl(event.url);
@ -296,12 +305,12 @@ Module.register("calendar", {
if (this.config.timeFormat === "absolute") {
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * oneDay))) {
// This event falls within the config.urgency period that the user has set
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").from(moment().format("YYYYMMDD")));
} else {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.fullDayEventDateFormat));
}
} else {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").from(moment().format("YYYYMMDD")));
}
}
if(this.config.showEnd){
@ -447,7 +456,31 @@ Module.register("calendar", {
}
event.url = c;
event.today = event.startDate >= today && event.startDate < (today + 24 * 60 * 60 * 1000);
events.push(event);
/* 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.
*/
if (this.config.sliceMultiDayEvents) {
var midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x"); //next midnight
var count = 1;
var maxCount = Math.ceil(((event.endDate - 1) - moment(event.startDate, "x").endOf("day").format("x"))/(1000*60*60*24)) + 1
if (event.endDate > midnight) {
while (event.endDate > midnight) {
var nextEvent = JSON.parse(JSON.stringify(event)); //make a copy without reference to the original event
nextEvent.startDate = midnight;
event.endDate = midnight;
event.title += " (" + count + "/" + maxCount + ")";
events.push(event);
event = nextEvent;
count += 1;
midnight = moment(midnight, "x").add(1, "day").format("x"); //move further one day for next split
}
event.title += " ("+count+"/"+maxCount+")";
}
events.push(event);
} else {
events.push(event);
}
}
}
@ -536,6 +569,17 @@ Module.register("calendar", {
return this.getCalendarProperty(url, "timeClass", "");
},
/* calendarNameForUrl(url)
* Retrieves the calendar name for a specific url.
*
* argument url string - Url to look for.
*
* return string - The name of the calendar
*/
calendarNameForUrl: function (url) {
return this.getCalendarProperty(url, "name", "");
},
/* colorForUrl(url)
* Retrieves the color for a specific url.
*
@ -584,9 +628,10 @@ Module.register("calendar", {
* @param {string} string Text string to shorten
* @param {number} maxLength The max length of the string
* @param {boolean} wrapEvents Wrap the text after the line has reached maxLength
* @param {number} maxTitleLines The max number of vertical lines before cutting event title
* @returns {string} The shortened string
*/
shorten: function (string, maxLength, wrapEvents) {
shorten: function (string, maxLength, wrapEvents, maxTitleLines) {
if (typeof string !== "string") {
return "";
}
@ -595,12 +640,21 @@ Module.register("calendar", {
var temp = "";
var currentLine = "";
var words = string.split(" ");
var line = 0;
for (var i = 0; i < words.length; i++) {
var word = words[i];
if (currentLine.length + word.length < (typeof maxLength === "number" ? maxLength : 25) - 1) { // max - 1 to account for a space
currentLine += (word + " ");
} else {
line++;
if (line > maxTitleLines - 1) {
if (i < words.length) {
currentLine += "&hellip;";
}
break;
}
if (currentLine.length > 0) {
temp += (currentLine + "<br>" + word + " ");
} else {
@ -651,7 +705,7 @@ Module.register("calendar", {
title = title.replace(needle, replacement);
}
title = this.shorten(title, this.config.maxTitleLength, this.config.wrapEvents);
title = this.shorten(title, this.config.maxTitleLength, this.config.wrapEvents, this.config.maxTitleLines);
return title;
},
@ -666,6 +720,7 @@ Module.register("calendar", {
for (var e in calendar) {
var event = cloneObject(calendar[e]);
event.symbol = this.symbolsForUrl(url);
event.calendarName = this.calendarNameForUrl(url);
event.color = this.colorForUrl(url);
delete event.url;
eventList.push(event);

View File

@ -273,7 +273,7 @@ var CalendarFetcher = function(url, reloadInterval, excludedEvents, maximumEntri
/* isFullDayEvent(event)
* Checks if an event is a fullday event.
*
* argument event obejct - The event object to check.
* argument event object - The event object to check.
*
* return bool - The event is a fullday event.
*/

View File

@ -80,16 +80,45 @@
}
}
var addTZ = function(dt, name, params){
var addTZ = function(dt, params){
var p = parseParams(params);
if (params && p){
dt[name].tz = p.TZID
if (params && p && dt){
dt.tz = p.TZID
}
return dt
}
var parseTimestamp = function(val){
//typical RFC date-time format
var comps = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?$/.exec(val);
if (comps !== null) {
if (comps[7] == 'Z'){ // GMT
return new Date(Date.UTC(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10 )
));
// TODO add tz
} else {
return new Date(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10)
);
}
}
return undefined;
}
var dateParam = function(name){
return function(val, params, curr){
@ -108,37 +137,24 @@
comps[3]
);
return addTZ(curr, name, params);
curr[name] = addTZ(curr[name], params);
return curr;
}
}
curr[name] = []
val.split(',').forEach(function(val){
var newDate = parseTimestamp(val);
curr[name].push(addTZ(newDate, params));
});
//typical RFC date-time format
var comps = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?$/.exec(val);
if (comps !== null) {
if (comps[7] == 'Z'){ // GMT
curr[name] = new Date(Date.UTC(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10 )
));
// TODO add tz
} else {
curr[name] = new Date(
parseInt(comps[1], 10),
parseInt(comps[2], 10)-1,
parseInt(comps[3], 10),
parseInt(comps[4], 10),
parseInt(comps[5], 10),
parseInt(comps[6], 10)
);
}
if (curr[name].length === 0){
delete curr[name];
} else if (curr[name].length === 1){
curr[name] = curr[name][0];
}
return addTZ(curr, name, params)
return curr;
}
}
@ -148,7 +164,11 @@
if (date.exdates === undefined) {
date.exdates = [];
}
date.exdates.push(date.exdate);
if (Array.isArray(date.exdate)){
date.exdates = date.exdates.concat(date.exdate);
} else {
date.exdates.push(date.exdate);
}
return date;
}
}

View File

@ -137,7 +137,8 @@ Module.register("clock",{
clockCircle.style.backgroundSize = "100%";
// 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
} else if (this.config.analogFace != "none") {
clockCircle.style.border = "2px solid white";

View File

@ -50,7 +50,7 @@ The following properties can be configured:
| `showIndoorTemperature` | If you have another module that emits the INDOOR_TEMPERATURE notification, the indoor temperature will be displayed <br> **Default value:** `false`
| `onlyTemp` | Show only current Temperature and weather icon without windspeed, sunset, sunrise time and feels like. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `showFeelsLike` | Shows the Feels like temperature weather. <br><br> **Possible values:**`true` or `false`<br>**Default value:** `true`
| `useKMPHWind` | Uses KMPH as units for windspeed. <br><br> **Possible values:**`true` or `false`<br>**Default value:** `false`
| `useKMPHwind` | Uses KMPH as units for windspeed. <br><br> **Possible values:**`true` or `false`<br>**Default value:** `false`
| `useBeaufort` | Pick between using the Beaufort scale for wind speed or using the default units. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `lang` | The language of the days. <br><br> **Possible values:** `en`, `nl`, `ru`, etc ... <br> **Default value:** uses value of _config.language_
| `decimalSymbol` | The decimal symbol to use.<br><br> **Possible values:** `.`, `,` or any other symbol.<br> **Default value:** `.`

View File

@ -71,7 +71,7 @@ Module.register("currentweather",{
firstEvent: false,
// create a variable to hold the location name based on the API result.
fetchedLocatioName: "",
fetchedLocationName: "",
// Define required scripts.
getScripts: function() {
@ -198,16 +198,19 @@ Module.register("currentweather",{
large.appendChild(weatherIcon);
var degreeLabel = "";
if (this.config.degreeLabel) {
switch (this.config.units ) {
if (this.config.units === "metric" || this.config.units === "imperial") {
degreeLabel += "°";
}
if(this.config.degreeLabel) {
switch(this.config.units) {
case "metric":
degreeLabel = "C";
degreeLabel += "C";
break;
case "imperial":
degreeLabel = "F";
degreeLabel += "F";
break;
case "default":
degreeLabel = "K";
degreeLabel += "K";
break;
}
}
@ -218,7 +221,7 @@ Module.register("currentweather",{
var temperature = document.createElement("span");
temperature.className = "bright";
temperature.innerHTML = " " + this.temperature.replace(".", this.config.decimalSymbol) + "&deg;" + degreeLabel;
temperature.innerHTML = " " + this.temperature.replace(".", this.config.decimalSymbol) + degreeLabel;
large.appendChild(temperature);
if (this.config.showIndoorTemperature && this.indoorTemperature) {
@ -228,7 +231,7 @@ Module.register("currentweather",{
var indoorTemperatureElem = document.createElement("span");
indoorTemperatureElem.className = "bright";
indoorTemperatureElem.innerHTML = " " + this.indoorTemperature.replace(".", this.config.decimalSymbol) + "&deg;" + degreeLabel;
indoorTemperatureElem.innerHTML = " " + this.indoorTemperature.replace(".", this.config.decimalSymbol) + degreeLabel;
large.appendChild(indoorTemperatureElem);
}
@ -251,7 +254,7 @@ Module.register("currentweather",{
var feelsLike = document.createElement("span");
feelsLike.className = "dimmed";
feelsLike.innerHTML = this.translate("FEELS") + " " + this.feelsLike + "&deg;" + degreeLabel;
feelsLike.innerHTML = this.translate("FEELS") + " " + this.feelsLike + degreeLabel;
small.appendChild(feelsLike);
wrapper.appendChild(small);
@ -262,8 +265,8 @@ Module.register("currentweather",{
// Override getHeader method.
getHeader: function() {
if (this.config.appendLocationNameToHeader) {
return this.data.header + " " + this.fetchedLocatioName;
if (this.config.appendLocationNameToHeader && this.data.header !== undefined) {
return this.data.header + " " + this.fetchedLocationName;
}
return this.data.header;

View File

@ -46,6 +46,7 @@ MagicMirror's [notification mechanism](https://github.com/MichMich/MagicMirror/t
| `ARTICLE_MORE_DETAILS` | When received the _first time_, shows the corresponding description of the currently displayed news title. <br> The module expects that the module's configuration option `showDescription` is set to `false` (default value). <br><br> When received a _second consecutive time_, shows the full news article in an IFRAME. <br> This requires that the news page can be embedded in an IFRAME, e.g. doesn't have the HTTP response header [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) set to e.g. `DENY`.<br><br>When received the _next consecutive times_, reloads the page and scrolls down by `scrollLength` pixels to paginate through the article.
| `ARTICLE_LESS_DETAILS` | Hides the summary or full news article and only displays the news title of the currently viewed news item.
| `ARTICLE_TOGGLE_FULL` | Toogles article in fullscreen.
| `ARTICLE_INFO_REQUEST` | Causes `newsfeed` to respond with the notification `ARTICLE_INFO_RESPONSE`, the payload of which provides the `title`, `source`, `date`, `desc` and `url` of the current news title.
Note the payload of the sent notification event is ignored.

View File

@ -189,7 +189,7 @@ Module.register("newsfeed",{
fullArticle.style.top = "0";
fullArticle.style.left = "0";
fullArticle.style.border = "none";
fullArticle.src = typeof this.newsItems[this.activeItem].url === "string" ? this.newsItems[this.activeItem].url : this.newsItems[this.activeItem].url.href;
fullArticle.src = this.getActiveItemURL()
fullArticle.style.zIndex = 1;
wrapper.appendChild(fullArticle);
}
@ -210,6 +210,10 @@ Module.register("newsfeed",{
return wrapper;
},
getActiveItemURL: function() {
return typeof this.newsItems[this.activeItem].url === "string" ? this.newsItems[this.activeItem].url : this.newsItems[this.activeItem].url.href;
},
/* registerFeeds()
* registers the feeds to be used by the backend.
*/
@ -387,6 +391,14 @@ Module.register("newsfeed",{
} else {
this.showFullArticle();
}
} else if (notification === "ARTICLE_INFO_REQUEST"){
this.sendNotification("ARTICLE_INFO_RESPONSE", {
title: this.newsItems[this.activeItem].title,
source: this.newsItems[this.activeItem].sourceTitle,
date: this.newsItems[this.activeItem].pubdate,
desc: this.newsItems[this.activeItem].description,
url: this.getActiveItemURL()
})
} else {
Log.info(this.name + " - unknown notification, ignoring: " + notification);
}

View File

@ -65,8 +65,10 @@ module.exports = NodeHelper.create({
data.module = sg.module;
if (!err) {
sg.git.log({"-1": null}, function(err, data2) {
data.hash = data2.latest.hash;
self.sendSocketNotification("STATUS", data);
if (!err && data2.latest && "hash" in data2.latest) {
data.hash = data2.latest.hash;
self.sendSocketNotification("STATUS", data);
}
});
}
});

View File

@ -2,7 +2,7 @@
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.
The biggest cange is the use of weather providers. This way we are not bound to one API source. And users can choose which API they want to use as their source.
The biggest change is the use of weather providers. This way we are not bound to one API source. And users can choose which API they want to use as their source.
The module is in a very early stage, and needs a lot of work. It's API isn't set in stone, so keep that in mind when you want to contribute.
@ -35,21 +35,21 @@ The following properties can be configured:
| Option | Description
| ---------------------------- | -----------
| `weatherProvider` | Which weather provider should be used. <br><br> **Possible values:** `openweathermap` and `darksky` <br> **Default value:** `openweathermap`
| `type` | Which type of weather data should be displayed. <br><br> **Possible values:** `current` and `forecast` <br> **Default value:** `current`
| `units` | What units to use. Specified by config.js <br><br> **Possible values:** `config.units` = Specified by config.js, `default` = Kelvin, `metric` = Celsius, `imperial` =Fahrenheit <br> **Default value:** `config.units`
| `weatherProvider` | Which weather provider should be used. <br><br> **Possible values:** `openweathermap` , `darksky` , or `weathergov` <br> **Default value:** `openweathermap`
| `type` | Which type of weather data should be displayed. <br><br> **Possible values:** `current` or `forecast` <br> **Default value:** `current`
| `units` | What units to use. Specified by config.js <br><br> **Possible values:** `config.units` = Specified by config.js, `default` = Kelvin, `metric` = Celsius, `imperial` = Fahrenheit <br> **Default value:** `config.units`
| `roundTemp` | Round temperature value to nearest integer. <br><br> **Possible values:** `true` (round to integer) or `false` (display exact value with decimal point) <br> **Default value:** `false`
| `degreeLabel` | Show the degree label for your chosen units (Metric = C, Imperial = F, Kelvins = K). <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `degreeLabel` | Show the degree label for your chosen units (Metric = C, Imperial = F, Kelvin = K). <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `updateInterval` | How often does the content needs to be fetched? (Milliseconds) <br><br> **Possible values:** `1000` - `86400000` <br> **Default value:** `600000` (10 minutes)
| `animationSpeed` | Speed of the update animation. (Milliseconds) <br><br> **Possible values:**`0` - `5000` <br> **Default value:** `1000` (1 second)
| `animationSpeed` | Speed of the update animation. (Milliseconds) <br><br> **Possible values:** `0` - `5000` <br> **Default value:** `1000` (1 second)
| `timeFormat` | Use 12 or 24 hour format. <br><br> **Possible values:** `12` or `24` <br> **Default value:** uses value of _config.timeFormat_
| `showPeriod` | Show the period (am/pm) with 12 hour format <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `showPeriodUpper` | Show the period (AM/PM) with 12 hour format as uppercase <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `lang` | The language of the days. <br><br> **Possible values:** `en`, `nl`, `ru`, etc ... <br> **Default value:** uses value of _config.language_
| `decimalSymbol` | The decimal symbol to use.<br><br> **Possible values:** `.`, `,` or any other symbol.<br> **Default value:** `.`
| `initialLoadDelay` | The initial delay before loading. If you have multiple modules that use the same API key, you might want to delay one of the requests. (Milliseconds) <br><br> **Possible values:** `1000` - `5000` <br> **Default value:** `0`
| `appendLocationNameToHeader` | If set to `true`, the returned location name will be appended to the header of the module, if the header is enabled. This is mainly intresting when using calender based weather. <br><br> **Default value:** `true`
| `calendarClass` | The class for the calender module to base the event based weather information on. <br><br> **Default value:** `'calendar'`
| `initialLoadDelay` | The initial delay before loading. If you have multiple modules that use the same API key, you might want to delay one of the requests. (Milliseconds) <br><br> **Possible values:** `1000` - `5000` <br> **Default value:** `0`
| `appendLocationNameToHeader` | If set to `true`, the returned location name will be appended to the header of the module, if the header is enabled. This is mainly interesting when using calender based weather. <br><br> **Default value:** `true`
| `calendarClass` | The class for the calender module to base the event based weather information on. <br><br> **Default value:** `'calendar'`
#### Current weather options
@ -62,36 +62,48 @@ The following properties can be configured:
| `showHumidity` | Show the current humidity <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `showIndoorTemperature` | If you have another module that emits the `INDOOR_TEMPERATURE` notification, the indoor temperature will be displayed <br> **Default value:** `false`
| `showIndoorHumidity` | If you have another module that emits the `INDOOR_HUMIDITY` notification, the indoor humidity will be displayed <br> **Default value:** `false`
| `showFeelsLike` | Shows the Feels like temperature weather. <br><br> **Possible values:**`true` or `false`<br>**Default value:** `true`
| `showFeelsLike` | Shows the Feels like temperature weather. <br><br> **Possible values:** `true` or `false`<br>**Default value:** `true`
#### Weather forecast options
| Option | Description
| ---------------------------- | -----------
| `tableClass` | The class for the forecast table. <br><br> **Default value:** `'small'`
| `colored` | If set to `true`, the min and max temperature are color coded. <br><br> **Default value:** `false`
| `showRainAmount` | Show the amount of rain in the forecast <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `tableClass` | The class for the forecast table. <br><br> **Default value:** `'small'`
| `colored` | If set to `true`, the min and max temperature are color coded. <br><br> **Default value:** `false`
| `showPrecipitationAmount` | Show the amount of rain/snow in the forecast <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `fade` | Fade the future events to black. (Gradient) <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `fadePoint` | Where to start fade? <br><br> **Possible values:** `0` (top of the list) - `1` (bottom of list) <br> **Default value:** `0.25`
| `maxNumberOfDays` | How many days of forecast to return. Specified by config.js <br><br> **Possible values:** `1` - `16` <br> **Default value:** `5` (5 days) <br> This value is optional. By default the weatherforecast module will return 5 days.
### Openweathermap options
| Option | Description
| ---------------------------- | -----------
| `apiVersion` | The OpenWeatherMap API version to use. <br><br> **Default value:** `2.5`
| `apiBase` | The OpenWeatherMap base URL. <br><br> **Default value:** `'http://api.openweathermap.org/data/'`
| `weatherEndpoint` | The OpenWeatherMap API endPoint. <br><br> **Possible values:** `/weather` or `/forecast/daily` <br> **Default value:** `'/weather'`
| `apiVersion` | The OpenWeatherMap API version to use. <br><br> **Default value:** `2.5`
| `apiBase` | The OpenWeatherMap base URL. <br><br> **Default value:** `'http://api.openweathermap.org/data/'`
| `weatherEndpoint` | The OpenWeatherMap API endPoint. <br><br> **Possible values:** `/weather`, `/forecast` (free users) or `/forecast/daily` (paying users or old apiKey only) <br> **Default value:** `'/weather'`
| `locationID` | Location ID from [OpenWeatherMap](https://openweathermap.org/find) **This will override anything you put in location.** <br> Leave blank if you want to use location. <br> **Example:** `1234567` <br> **Default value:** `false` <br><br> **Note:** When the `location` and `locationID` are both not set, the location will be based on the information provided by the calendar module. The first upcoming event with location data will be used.
| `location` | The location used for weather information. <br><br> **Example:** `'Amsterdam,Netherlands'` <br> **Default value:** `false` <br><br> **Note:** When the `location` and `locationID` are both not set, the location will be based on the information provided by the calendar module. The first upcoming event with location data will be used.
| `apiKey` | The [OpenWeatherMap](https://home.openweathermap.org) API key, which can be obtained by creating an OpenWeatherMap account. <br><br> This value is **REQUIRED**
| `apiKey` | The [OpenWeatherMap](https://home.openweathermap.org) API key, which can be obtained by creating an OpenWeatherMap account. <br><br> This value is **REQUIRED**
### Darksky options
| Option | Description
| ---------------------------- | -----------
| `apiBase` | The DarkSky base URL. The darksky api has disabled [cors](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), therefore a proxy is required. <br><br> **Possible value:** `'https://cors-anywhere.herokuapp.com/https://api.darksky.net'` <br> This value is **REQUIRED**
| `weatherEndpoint` | The DarkSky API endPoint. <br><br> **Possible values:** `/forecast` <br> This value is **REQUIRED**
| `apiKey` | The [DarkSky](https://darksky.net/dev/register) API key, which can be obtained by creating an DarkSky account. <br><br> This value is **REQUIRED**
| `lat` | The geo coordinate latitude. <br><br> This value is **REQUIRED**
| `lon` | The geo coordinate longitude. <br><br> This value is **REQUIRED**
| `apiBase` | The DarkSky base URL. The darksky api has disabled [cors](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), therefore a proxy is required. <br><br> **Possible value:** `'https://cors-anywhere.herokuapp.com/https://api.darksky.net'` <br> This value is **REQUIRED**
| `weatherEndpoint` | The DarkSky API endPoint. <br><br> **Possible values:** `/forecast` <br> This value is **REQUIRED**
| `apiKey` | The [DarkSky](https://darksky.net/dev/register) API key, which can be obtained by creating an DarkSky account. <br><br> This value is **REQUIRED**
| `lat` | The geo coordinate latitude. <br><br> This value is **REQUIRED**
| `lon` | The geo coordinate longitude. <br><br> This value is **REQUIRED**
### Weather.gov options
| Option | Description
| ---------------------------- | -----------
| `apiBase` | The weather.gov base URL. <br><br> **Possible value:** `'https://api.weather.gov/points/'` <br> This value is **REQUIRED**
| `weatherEndpoint` | The weather.gov API endPoint. <br><br> **Possible values:** `/forecast` for forecast and `/forecast/hourly` for current. <br> This value is **REQUIRED**
| `lat` | The geo coordinate latitude. <br><br> This value is **REQUIRED**
| `lon` | The geo coordinate longitude. <br><br> This value is **REQUIRED**
## API Provider Development

View File

@ -4,31 +4,30 @@
<span class="wi wi-strong-wind dimmed"></span>
<span>
{% if config.useBeaufort %}
{{current.beaufortWindSpeed() | round}}
{{ current.beaufortWindSpeed() | round }}
{% else %}
{{current.windSpeed | round}}
{{ current.windSpeed | round }}
{% endif %}
{% if config.showWindDirection %}
<sup>
{% if config.showWindDirectionAsArrow %}
<i class="fa fa-long-arrow-up" style="transform:rotate({{current.windDirection}}deg);"></i>
<i class="fa fa-long-arrow-up" style="transform:rotate({{ current.windDirection }}deg);"></i>
{% else %}
{{current.cardinalWindDirection() | translate}}
{{ current.cardinalWindDirection() | translate }}
{% endif %}
&nbsp;
</sup>
{% endif %}
</span>
{% if config.showHumidity and current.humidity %}
<span>{{ current.humidity }}</span><sup>&nbsp;<i class="wi wi-humidity humidityIcon"></i></sup>
<span>{{ current.humidity | decimalSymbol }}</span><sup>&nbsp;<i class="wi wi-humidity humidityIcon"></i></sup>
{% endif %}
<span class="wi dimmed wi-{{current.nextSunAction()}}"></span>
<span class="wi dimmed wi-{{ current.nextSunAction() }}"></span>
<span>
{% if current.nextSunAction() == "sunset" %}
{{current.sunset | formatTime}}
{{ current.sunset | formatTime }}
{% else %}
{{current.sunrise | formatTime}}
{{ current.sunrise | formatTime }}
{% endif %}
</span>
</div>
@ -36,33 +35,39 @@
<div class="large light">
<span class="wi weathericon wi-{{current.weatherType}}"></span>
<span class="bright">
{{current.temperature | roundValue | unit("temperature")}}
{{ current.temperature | roundValue | unit("temperature") | decimalSymbol }}
</span>
</div>
<div class="normal light indoor">
{% if config.showIndoorTemperature and indoor.temperature %}
<span class="fa fa-home"></span>
<span class="bright">
{{indoor.temperature | roundValue | unit("temperature")}}
</span>
<div>
<span class="fa fa-home"></span>
<span class="bright">
{{ indoor.temperature | roundValue | unit("temperature") | decimalSymbol }}
</span>
</div>
{% endif %}
{% if config.showIndoorHumidity and indoor.humidity %}
<span class="fa fa-tint"></span>
<span class="bright">
{{indoor.humidity | roundValue}}%
</span>
<div>
<span class="fa fa-tint"></span>
<span class="bright">
{{ indoor.humidity | roundValue | unit("humidity") | decimalSymbol }}
</span>
</div>
{% endif %}
</div>
{% if config.showFeelsLike and not config.onlyTemp %}
<div class="normal medium">
<span class="dimmed">
{{ "FEELS" | translate }} {{ current.feelsLike() | roundValue | unit("temperature") }}
{{ "FEELS" | translate }} {{ current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }}
</span>
</div>
{% endif %}
{% else %}
<div class="dimmed light small">
{{"LOADING" | translate}}
{{ "LOADING" | translate | safe }}
</div>
{% endif %}
<!-- Unclomment the line below to see the contents of the `current` object. -->
<!-- Uncomment the line below to see the contents of the `current` object. -->
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{current | dump}}</div> -->

View File

@ -1,26 +1,32 @@
{% if forecast %}
<table class="{{config.tableClass}}">
{% set numSteps = forecast | calcNumSteps %}
{% set currentStep = 0 %}
<table class="{{ config.tableClass }}">
{% set forecast = forecast.slice(0, numSteps) %}
{% for f in forecast %}
<tr {% if config.colored %}class="colored"{% endif %}>
<td class="day">{{f.date.format('ddd')}}</td>
<td class="bright weather-icon"><span class="wi weathericon wi-{{f.weatherType}}"></span></td>
<tr {% if config.colored %}class="colored"{% endif %} {% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}>
<td class="day">{{ f.date.format('ddd') }}</td>
<td class="bright weather-icon"><span class="wi weathericon wi-{{ f.weatherType }}"></span></td>
<td class="align-right bright max-temp">
{{f.maxTemperature | roundValue | unit("temperature")}}
{{ f.maxTemperature | roundValue | unit("temperature") }}
</td>
<td class="align-right min-temp">
{{f.minTemperature | roundValue | unit("temperature")}}
{{ f.minTemperature | roundValue | unit("temperature") }}
</td>
{% if config.showRainAmount %}
<td class="align-right bright rain">
{{f.rain | unit("rain")}}
{% if config.showPrecipitationAmount %}
<td class="align-right bright precipitation">
{{ f.precipitation | unit("precip") }}
</td>
{% endif %}
</tr>
{% set currentStep = currentStep + 1 %}
{% endfor %}
</table>
{% else %}
{{"LOADING" | translate}}
<div class="dimmed light small">
{{ "LOADING" | translate | safe }}
</div>
{% endif %}
<!-- Unclomment the line below to see the contents of the `current` object. -->
<!-- Uncomment the line below to see the contents of the `current` object. -->
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{forecast | dump}}</div> -->

View File

@ -85,7 +85,7 @@ Notify the delegate that new weather is available.
#### `fetchData(url, method, data)`
A convinience function to make requests. It returns a promise.
A convenience function to make requests. It returns a promise.
### WeatherObject
@ -103,6 +103,8 @@ A convinience function to make requests. It returns a promise.
| weatherType | `string` | Icon name of the weather type. <br> Possible values: [WeatherIcons](https://www.npmjs.com/package/weathericons) |
| humidity | `number` | Percentage of humidity |
| rain | `number` | Metric: `millimeters` <br> Imperial: `inches` |
| snow | `number` | Metric: `millimeters` <br> Imperial: `inches` |
| precipitation | `number` | Metric: `millimeters` <br> Imperial: `inches` |
#### Current weather

View File

@ -8,6 +8,7 @@
* MIT Licensed
*
* This class is a provider for Dark Sky.
* Note that the Dark Sky API does not provide rainfall. Instead it provides snowfall and precipitation probability
*/
WeatherProvider.register("darksky", {
// Set the name of the provider.
@ -81,12 +82,20 @@ WeatherProvider.register("darksky", {
weather.minTemperature = forecast.temperatureMin;
weather.maxTemperature = forecast.temperatureMax;
weather.weatherType = this.convertWeatherType(forecast.icon);
if (this.config.units === "metric" && !isNaN(forecast.precipAccumulation)) {
weather.rain = forecast.precipAccumulation * 10;
} else {
weather.rain = forecast.precipAccumulation;
weather.snow = 0;
// The API will return centimeters if units is 'si' and will return inches for 'us'
// Note that the Dark Sky API does not provide rainfall. Instead it provides snowfall and precipitation probability
if (forecast.hasOwnProperty("precipAccumulation")) {
if (this.config.units === "imperial" && !isNaN(forecast.precipAccumulation)) {
weather.snow = forecast.precipAccumulation;
} else if (!isNaN(forecast.precipAccumulation)) {
weather.snow = forecast.precipAccumulation * 10;
}
}
weather.precipitation = weather.snow;
days.push(weather);
}

View File

@ -5,7 +5,7 @@
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*
*
* This class is the blueprint for a weather provider.
*/
@ -56,8 +56,6 @@ WeatherProvider.register("openweathermap", {
})
},
/** OpenWeatherMap Specific Methods - These are not part of the default provider methods */
/*
* Gets the complete url for the request
@ -66,7 +64,7 @@ WeatherProvider.register("openweathermap", {
return this.config.apiBase + this.config.apiVersion + this.config.weatherEndpoint + this.getParams();
},
/*
/*
* Generate a WeatherObject based on currentWeatherInformation
*/
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
@ -87,6 +85,105 @@ WeatherProvider.register("openweathermap", {
* Generate WeatherObjects based on forecast information
*/
generateWeatherObjectsFromForecast(forecasts) {
if (this.config.weatherEndpoint == "/forecast") {
return this.fetchForecastHourly(forecasts);
} else if (this.config.weatherEndpoint == "/forecast/daily") {
return this.fetchForecastDaily(forecasts);
}
// if weatherEndpoint does not match forecast or forecast/daily, what should be returned?
const days = [new WeatherObject(this.config.units)];
return days;
},
/*
* fetch forecast information for 3-hourly forecast (available for free subscription).
*/
fetchForecastHourly(forecasts) {
// initial variable declaration
const days = [];
// variables for temperature range and rain
let minTemp = [];
let maxTemp = [];
let rain = 0;
let snow = 0;
// variable for date
let date = "";
let weather = new WeatherObject(this.config.units);
for (const forecast of forecasts) {
if (date !== moment(forecast.dt, "X").format("YYYY-MM-DD")) {
// calculate minimum/maximum temperature, specify rain amount
weather.minTemperature = Math.min.apply(null, minTemp);
weather.maxTemperature = Math.max.apply(null, maxTemp);
weather.rain = rain;
weather.snow = snow;
weather.precipitation = weather.rain + weather.snow;
// push weather information to days array
days.push(weather);
// create new weather-object
weather = new WeatherObject(this.config.units);
minTemp = [];
maxTemp = [];
rain = 0;
snow = 0;
// set new date
date = moment(forecast.dt, "X").format("YYYY-MM-DD");
// specify date
weather.date = moment(forecast.dt, "X");
// 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);
}
if (moment(forecast.dt, "X").format("H") >= 8 && moment(forecast.dt, "X").format("H") <= 17) {
weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
}
// the same day as before
// add values from forecast to corresponding variables
minTemp.push(forecast.main.temp_min);
maxTemp.push(forecast.main.temp_max);
if (forecast.hasOwnProperty("rain")) {
if (this.config.units === "imperial" && !isNaN(forecast.rain["3h"])) {
rain += forecast.rain["3h"] / 25.4;
} else if (!isNaN(forecast.rain["3h"])) {
rain += forecast.rain["3h"];
}
}
if (forecast.hasOwnProperty("snow")) {
if (this.config.units === "imperial" && !isNaN(forecast.snow["3h"])) {
snow += forecast.snow["3h"] / 25.4;
} else if (!isNaN(forecast.snow["3h"])) {
snow += forecast.snow["3h"];
}
}
}
// last day
// calculate minimum/maximum temperature, specify rain amount
weather.minTemperature = Math.min.apply(null, minTemp);
weather.maxTemperature = Math.max.apply(null, maxTemp);
weather.rain = rain;
weather.snow = snow;
weather.precipitation = weather.rain + weather.snow;
// push weather information to days array
days.push(weather);
return days.slice(1);
},
/*
* fetch forecast information for daily forecast (available for paid subscription or old apiKey).
*/
fetchForecastDaily(forecasts) {
// initial variable declaration
const days = [];
for (const forecast of forecasts) {
@ -96,16 +193,35 @@ WeatherProvider.register("openweathermap", {
weather.minTemperature = forecast.temp.min;
weather.maxTemperature = forecast.temp.max;
weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
if (this.config.units === "imperial" && !isNaN(forecast.rain)) {
weather.rain = forecast.rain / 25.4
} else {
weather.rain = forecast.rain;
weather.rain = 0;
weather.snow = 0;
// forecast.rain not available if amount is zero
// The API always returns in millimeters
if (forecast.hasOwnProperty("rain")) {
if (this.config.units === "imperial" && !isNaN(forecast.rain)) {
weather.rain = forecast.rain / 25.4;
} else if (!isNaN(forecast.rain)) {
weather.rain = forecast.rain;
}
}
// forecast.snow not available if amount is zero
// The API always returns in millimeters
if (forecast.hasOwnProperty("snow")) {
if (this.config.units === "imperial" && !isNaN(forecast.snow)) {
weather.snow = forecast.snow / 25.4;
} else if (!isNaN(forecast.snow)) {
weather.snow = forecast.snow;
}
}
weather.precipitation = weather.rain + weather.snow;
days.push(weather);
}
return days;
return days;
},
/*

View File

@ -0,0 +1,256 @@
/* global WeatherProvider, WeatherObject */
/* Magic Mirror
* Module: Weather
* Provider: weather.gov
*
* By Vince Peri
* MIT Licensed.
*
* This class is a provider for weather.gov.
* Note that this is only for US locations (lat and lon) and does not require an API key
* Since it is free, there are some items missing - like sunrise, sunset, humidity, etc.
*/
WeatherProvider.register("weathergov", {
// Set the name of the provider.
// 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.
providerName: "Weather.gov",
// Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() {
this.fetchData(this.getUrl())
.then(data => {
if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
}
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data.properties.periods[0]);
this.setCurrentWeather(currentWeather);
})
.catch(function(request) {
Log.error("Could not load data ... ", request);
})
},
// Overwrite the fetchCurrentWeather method.
fetchWeatherForecast() {
this.fetchData(this.getUrl())
.then(data => {
if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
}
const forecast = this.generateWeatherObjectsFromForecast(data.properties.periods);
this.setWeatherForecast(forecast);
})
.catch(function(request) {
Log.error("Could not load data ... ", request);
})
},
/** Weather.gov Specific Methods - These are not part of the default provider methods */
/*
* Gets the complete url for the request
*/
getUrl() {
return this.config.apiBase + this.config.lat + "," + this.config.lon + this.config.weatherEndpoint;
},
/*
* Generate a WeatherObject based on currentWeatherInformation
*/
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
const currentWeather = new WeatherObject(this.config.units);
currentWeather.temperature = currentWeatherData.temperature;
currentWeather.windSpeed = currentWeatherData.windSpeed.split(" ", 1);
currentWeather.windDirection = this.convertDirectiontoDegrees(currentWeatherData.windDirection);
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.shortForecast, currentWeatherData.isDaytime);
return currentWeather;
},
/*
* Generate WeatherObjects based on forecast information
*/
generateWeatherObjectsFromForecast(forecasts) {
return this.fetchForecastDaily(forecasts);
},
/*
* fetch forecast information for daily forecast.
*/
fetchForecastDaily(forecasts) {
// initial variable declaration
const days = [];
// variables for temperature range and rain
let minTemp = [];
let maxTemp = [];
// variable for date
let date = "";
let weather = new WeatherObject(this.config.units);
weather.precipitation = 0;
for (const forecast of forecasts) {
if (date !== moment(forecast.startTime).format("YYYY-MM-DD")) {
// calculate minimum/maximum temperature, specify rain amount
weather.minTemperature = Math.min.apply(null, minTemp);
weather.maxTemperature = Math.max.apply(null, maxTemp);
// push weather information to days array
days.push(weather);
// create new weather-object
weather = new WeatherObject(this.config.units);
minTemp = [];
maxTemp = [];
weather.precipitation = 0;
// set new date
date = moment(forecast.startTime).format("YYYY-MM-DD");
// specify date
weather.date = moment(forecast.startTime);
// If the first value of today is later than 17:00, we have an icon at least!
weather.weatherType = this.convertWeatherType(forecast.shortForecast, forecast.isDaytime);
}
if (moment(forecast.startTime).format("H") >= 8 && moment(forecast.startTime).format("H") <= 17) {
weather.weatherType = this.convertWeatherType(forecast.shortForecast, forecast.isDaytime);
}
// the same day as before
// add values from forecast to corresponding variables
minTemp.push(forecast.temperature);
maxTemp.push(forecast.temperature);
}
// last day
// calculate minimum/maximum temperature, specify rain amount
weather.minTemperature = Math.min.apply(null, minTemp);
weather.maxTemperature = Math.max.apply(null, maxTemp);
// push weather information to days array
days.push(weather);
return days.slice(1);
},
/*
* Convert the icons to a more usable name.
*/
convertWeatherType(weatherType, isDaytime) {
//https://w1.weather.gov/xml/current_obs/weather.php
// There are way too many types to create, so lets just look for certain strings
if (weatherType.includes("Cloudy") || weatherType.includes("Partly")) {
if (isDaytime) {
return "day-cloudy";
}
return "night-cloudy";
} else if (weatherType.includes("Overcast")) {
if (isDaytime) {
return "cloudy";
}
return "night-cloudy";
} else if (weatherType.includes("Freezing") || weatherType.includes("Ice")) {
return "rain-mix";
} else if (weatherType.includes("Snow")) {
if (isDaytime) {
return "snow";
}
return "night-snow";
} else if (weatherType.includes("Thunderstorm")) {
if (isDaytime) {
return "thunderstorm";
}
return "night-thunderstorm";
} else if (weatherType.includes("Showers")) {
if (isDaytime) {
return "showers";
}
return "night-showers";
} else if (weatherType.includes("Rain") || weatherType.includes("Drizzle")) {
if (isDaytime) {
return "rain";
}
return "night-rain";
} else if (weatherType.includes("Breezy") || weatherType.includes("Windy")) {
if (isDaytime) {
return "cloudy-windy";
}
return "night-alt-cloudy-windy";
} else if (weatherType.includes("Fair") || weatherType.includes("Clear") || weatherType.includes("Few") || weatherType.includes("Sunny")) {
if (isDaytime) {
return "day-sunny";
}
return "night-clear";
} else if (weatherType.includes("Dust") || weatherType.includes("Sand")) {
return "dust";
} else if (weatherType.includes("Fog")) {
return "fog";
} else if (weatherType.includes("Smoke")) {
return "smoke";
} else if (weatherType.includes("Haze")) {
return "day-haze";
}
return null;
},
/*
Convert the direction into Degrees
*/
convertDirectiontoDegrees(direction) {
if (direction === "NNE"){
return 33.75;
} else if (direction === "NE") {
return 56.25;
} else if (direction === "ENE") {
return 78.75;
} else if (direction === "E") {
return 101.25;
} else if (direction === "ESE") {
return 123.75;
} else if (direction === "SE") {
return 146.25;
} else if (direction === "SSE") {
return 168.75;
} else if (direction === "S") {
return 191.25;
} else if (direction === "SSW") {
return 213.75;
} else if (direction === "SW") {
return 236.25;
} else if (direction === "WSW") {
return 258.75;
} else if (direction === "W") {
return 281.25;
} else if (direction === "WNW") {
return 303.75;
} else if (direction === "NW") {
return 326.25;
} else if (direction === "NNW") {
return 348.75;
} else {
return 0;
}
}
});

View File

@ -31,11 +31,15 @@
padding-right: 0;
}
.weather .rain {
.weather .precipitation {
padding-left: 20px;
padding-right: 0;
}
.weather tr .weathericon {
line-height: 25px;
}
.weather tr.colored .min-temp {
color: #bcddff;
}

View File

@ -30,8 +30,12 @@ Module.register("weather",{
lang: config.language,
showHumidity: false,
degreeLabel: false,
decimalSymbol: ".",
showIndoorTemperature: false,
showIndoorHumidity: false,
maxNumberOfDays: 5,
fade: true,
fadePoint: 0.25, // Start on 1/4th of the list.
initialLoadDelay: 0, // 0 seconds delay
retryDelay: 2500,
@ -45,7 +49,7 @@ Module.register("weather",{
tableClass: "small",
onlyTemp: false,
showRainAmount: true,
showPrecipitationAmount: false,
colored: false,
showFeelsLike: true
},
@ -58,7 +62,7 @@ Module.register("weather",{
return ["font-awesome.css", "weather-icons.css", "weather.css"];
},
// Return the scripts that are nessecery for the weather module.
// Return the scripts that are necessary for the weather module.
getScripts: function () {
return [
"moment.js",
@ -184,7 +188,9 @@ Module.register("weather",{
this.nunjucksEnvironment().addFilter("unit", function (value, type) {
if (type === "temperature") {
value += "°";
if (this.config.units === "metric" || this.config.units === "imperial") {
value += "°";
}
if (this.config.degreeLabel) {
if (this.config.units === "metric") {
value += "C";
@ -194,12 +200,14 @@ Module.register("weather",{
value += "K";
}
}
} else if (type === "rain") {
if (isNaN(value)) {
} else if (type === "precip") {
if (isNaN(value) || value === 0 || value.toFixed(2) === "0.00") {
value = "";
} else {
value = `${value.toFixed(2)} ${this.config.units === "imperial" ? "in" : "mm"}`;
}
} else if (type === "humidity") {
value += "%"
}
return value;
@ -208,5 +216,30 @@ Module.register("weather",{
this.nunjucksEnvironment().addFilter("roundValue", function(value) {
return this.roundValue(value);
}.bind(this));
this.nunjucksEnvironment().addFilter("decimalSymbol", function(value) {
return value.toString().replace(/\./g, this.config.decimalSymbol);
}.bind(this));
this.nunjucksEnvironment().addFilter("calcNumSteps", function(forecast) {
return Math.min(forecast.length, this.config.maxNumberOfDays);
}.bind(this));
this.nunjucksEnvironment().addFilter("opacity", function(currentStep, numSteps) {
if (this.config.fade && this.config.fadePoint < 1) {
if (this.config.fadePoint < 0) {
this.config.fadePoint = 0;
}
var startingPoint = numSteps * this.config.fadePoint;
var numFadesteps = numSteps - startingPoint;
if (currentStep >= startingPoint) {
return 1 - (currentStep - startingPoint) / numFadesteps;
} else {
return 1;
}
} else {
return 1;
}
}.bind(this));
}
});

View File

@ -26,6 +26,8 @@ class WeatherObject {
this.weatherType = null;
this.humidity = null;
this.rain = null;
this.snow = null;
this.precipitation = null;
}
cardinalWindDirection() {

View File

@ -142,14 +142,17 @@ Module.register("weatherforecast",{
icon.className = "wi weathericon " + forecast.icon;
iconCell.appendChild(icon);
var degreeLabel = "&deg;";
var degreeLabel = "";
if (this.config.units === "metric" || this.config.units === "imperial") {
degreeLabel += "°";
}
if(this.config.scale) {
switch(this.config.units) {
case "metric":
degreeLabel += " C";
degreeLabel += "C";
break;
case "imperial":
degreeLabel += " F";
degreeLabel += "F";
break;
case "default":
degreeLabel = "K";

715
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "magicmirror",
"version": "2.6.0",
"version": "2.8.0-develop",
"description": "The open source modular smart mirror platform.",
"main": "js/electron.js",
"scripts": {
@ -58,7 +58,7 @@
"ajv": "6.5.5",
"body-parser": "^1.18.2",
"colors": "^1.1.2",
"electron": "^2.0.16",
"electron": "^3.0.13",
"express": "^4.16.2",
"express-ipfilter": "0.3.1",
"feedme": "latest",

View File

@ -1,6 +1,5 @@
var app = require("../js/app.js");
app.start(function(config) {
console.log("");
var bindAddress = config.address ? config.address : "localhost";
console.log("Ready to go! Please point your browser to: http://" + bindAddress + ":" + config.port);
console.log("\nReady to go! Please point your browser to: http://" + bindAddress + ":" + config.port);
});

View File

@ -36,7 +36,7 @@ describe("Electron app environment", function() {
it("should open a browserwindow", function() {
return app.client
.waitUntilWindowLoaded()
.browserWindow.focus()
// .browserWindow.focus()
.getWindowCount()
.should.eventually.equal(1)
.browserWindow.isMinimized()

View File

@ -6,6 +6,8 @@
"DAYAFTERTOMORROW": "I overmorgen",
"RUNNING": "Slutter om",
"EMPTY": "Ingen kommende begivenheder.",
"FEELS": "Føles som",
"WEEK": "Uge {weekNumber}",
"N": "N",
"NNE": "NNØ",

View File

@ -29,5 +29,7 @@
"UPDATE_NOTIFICATION": "E' disponibile un aggiornamento di MagicMirror².",
"UPDATE_NOTIFICATION_MODULE": "E' disponibile un aggiornamento del modulo {MODULE_NAME}.",
"UPDATE_INFO_SINGLE": "L'installazione è {COMMIT_COUNT} commit indietro rispetto all'attuale branch {BRANCH_NAME}.",
"UPDATE_INFO_MULTIPLE": "L'installazione è {COMMIT_COUNT} commits indietro rispetto all'attuale branch {BRANCH_NAME}."
"UPDATE_INFO_MULTIPLE": "L'installazione è {COMMIT_COUNT} commits indietro rispetto all'attuale branch {BRANCH_NAME}.",
"FEELS": "Percepiti"
}

33
translations/sk.json Normal file
View File

@ -0,0 +1,33 @@
{
"LOADING": "Načítanie &hellip;",
"TODAY": "Dnes",
"TOMORROW": "Zajtra",
"DAYAFTERTOMORROW": "Pozajtra",
"RUNNING": "Končí o",
"EMPTY": "Žiadne nadchádzajúce udalosti.",
"WEEK": "{weekNumber}. týždeň",
"N": "S",
"NNE": "SSV",
"NE": "SV",
"ENE": "VSV",
"E": "V",
"ESE": "VJV",
"SE": "JV",
"SSE": "JJV",
"S": "J",
"SSW": "JJZ",
"SW": "JZ",
"WSW": "ZJZ",
"W": "Z",
"WNW": "ZSZ",
"NW": "SZ",
"NNW": "SSZ",
"UPDATE_NOTIFICATION": "Dostupná aktualizácia pre MagicMirror².",
"UPDATE_NOTIFICATION_MODULE": "Dostupná aktualizácia pre modul {MODULE_NAME}.",
"UPDATE_INFO_SINGLE": "Súčasná inštalácia je na vetve {BRANCH_NAME} pozadu o {COMMIT_COUNT} commit.",
"UPDATE_INFO_MULTIPLE": "Súčasná inštalácia je na vetve {BRANCH_NAME} pozadu o {COMMIT_COUNT} commitov."
}

35
translations/tlh.json Normal file
View File

@ -0,0 +1,35 @@
{
"LOADING": "loS &hellip;",
"TODAY": "DaHjaj",
"TOMORROW": "wa'leS",
"DAYAFTERTOMORROW": "cha'leS",
"RUNNING": "Dor",
"EMPTY": "Sumbe' wanI'.",
"WEEK": "Hogh (terran) {weekNumber}",
"N": "N",
"NNE": "NNE",
"NE": "NE",
"ENE": "ENE",
"E": "chan",
"ESE": "ESE",
"SE": "SE",
"SSE": "SSE",
"S": "S",
"SSW": "SSW",
"SW": "tIng",
"WSW": "WSW",
"W": "W",
"WNW": "WNW",
"NW": "'ev",
"NNW": "NNW",
"UPDATE_NOTIFICATION": " De'chu' MagicMirror² lI'laH.",
"UPDATE_NOTIFICATION_MODULE": "bobcho' {MODULE_NAME} lI'laH De'chu.",
"UPDATE_INFO_SINGLE": "The current installation is {COMMIT_COUNT} commit behind on the {BRANCH_NAME} branch.",
"UPDATE_INFO_MULTIPLE": "The current installation is {COMMIT_COUNT} commits behind on the {BRANCH_NAME} branch.",
"FEELS": "jem"
}

View File

@ -38,7 +38,9 @@ var translations = {
"cy" : "translations/cy.json", // Welsh (Cymraeg)
"bg" : "translations/bg.json", // Bulgarian
"cs" : "translations/cs.json", // Czech
"hr" : "translations/hr.json" // Croatian
"hr" : "translations/hr.json", // Croatian
"sk" : "translations/sk.json", // Slovak
"tlh" : "translations/tlh.json" // Klingon
};
if (typeof module !== "undefined") {module.exports = translations;}

2
vendor/css/font-awesome.css vendored Normal file
View File

@ -0,0 +1,2 @@
@import url("../node_modules/@fortawesome/fontawesome-free/css/all.min.css");
@import url("../node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css");

71
vendor/package-lock.json generated vendored
View File

@ -193,11 +193,6 @@
"repeat-string": "^1.5.2"
}
},
"font-awesome": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
"integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM="
},
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@ -230,7 +225,8 @@
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true
"bundled": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -248,11 +244,13 @@
},
"balanced-match": {
"version": "1.0.0",
"bundled": true
"bundled": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -265,15 +263,18 @@
},
"code-point-at": {
"version": "1.1.0",
"bundled": true
"bundled": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true
"bundled": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true
"bundled": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -376,7 +377,8 @@
},
"inherits": {
"version": "2.0.3",
"bundled": true
"bundled": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -386,6 +388,7 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -398,17 +401,20 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true
"bundled": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -425,6 +431,7 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -497,7 +504,8 @@
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true
"bundled": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -507,6 +515,7 @@
"once": {
"version": "1.4.0",
"bundled": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -582,7 +591,8 @@
},
"safe-buffer": {
"version": "5.1.1",
"bundled": true
"bundled": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -612,6 +622,7 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -629,6 +640,7 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -667,11 +679,13 @@
},
"wrappy": {
"version": "1.0.2",
"bundled": true
"bundled": true,
"optional": true
},
"yallist": {
"version": "3.0.2",
"bundled": true
"bundled": true,
"optional": true
}
}
},
@ -689,6 +703,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
"integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
"optional": true,
"requires": {
"is-glob": "^2.0.0"
}
@ -702,7 +717,8 @@
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"optional": true
},
"invert-kv": {
"version": "1.0.0",
@ -721,7 +737,8 @@
"is-buffer": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
"integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw="
"integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=",
"optional": true
},
"is-dotfile": {
"version": "1.0.3",
@ -747,7 +764,8 @@
"is-extglob": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA="
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
@ -761,6 +779,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
"optional": true,
"requires": {
"is-extglob": "^1.0.0"
}
@ -789,7 +808,8 @@
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"optional": true
},
"isobject": {
"version": "2.1.0",
@ -804,6 +824,7 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"optional": true,
"requires": {
"is-buffer": "^1.1.5"
}
@ -869,6 +890,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
"integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
"optional": true,
"requires": {
"remove-trailing-separator": "^1.0.1"
}
@ -1017,12 +1039,14 @@
"remove-trailing-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
"integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8="
"integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
"optional": true
},
"repeat-element": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz",
"integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo="
"integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=",
"optional": true
},
"repeat-string": {
"version": "1.6.1",
@ -1033,7 +1057,8 @@
"safe-buffer": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
"optional": true
},
"set-immediate-shim": {
"version": "1.0.1",

1
vendor/package.json vendored
View File

@ -11,7 +11,6 @@
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.3.1",
"font-awesome": "^4.7.0",
"moment": "^2.17.1",
"moment-timezone": "^0.5.11",
"nunjucks": "^3.0.1",

4
vendor/vendor.js vendored
View File

@ -12,9 +12,7 @@ var vendor = {
"moment-timezone.js" : "node_modules/moment-timezone/builds/moment-timezone-with-data.js",
"weather-icons.css": "node_modules/weathericons/css/weather-icons.css",
"weather-icons-wind.css": "node_modules/weathericons/css/weather-icons-wind.css",
"font-awesome.css": "node_modules/font-awesome/css/font-awesome.min.css",
"font-awesome5.css": "node_modules/@fortawesome/fontawesome-free/css/all.min.css",
"font-awesome5.v4shims.css": "node_modules/@fortawesome/fontawesome-free/css/v4-shims.min.css",
"font-awesome.css": "css/font-awesome.css",
"nunjucks.js": "node_modules/nunjucks/browser/nunjucks.min.js"
};