diff --git a/.jscsrc b/.jscsrc index bc1fcea6..4638b44f 100644 --- a/.jscsrc +++ b/.jscsrc @@ -4,5 +4,6 @@ "validateQuoteMarks": "\"", "maximumLineLength": 250, "requireCurlyBraces": [], - "requireCamelCaseOrUpperCaseIdentifiers": false + "requireCamelCaseOrUpperCaseIdentifiers": false, + "excludeFiles": [".jscsrc"], } \ No newline at end of file diff --git a/README.md b/README.md index 1d79c219..4c935d31 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ MagicMirror² focuses on a modular plugin system and uses [Electron](http://elec ## Usage #### Raspberry Pi Support -Electron, the app wrapper around MagicMirror², only supports the Raspberry Pi 2 & 3. The Raspberry Pi 1 is currently **not** supported. If you want to run this on a Raspberry Pi 1, use the [server only](#server-only) feature and setup a fullscreen browser yourself. Note the only Jessie is currently supported. If you want to use Wheezy, check out [this issue](https://github.com/MichMich/MagicMirror/issues/188). +Electron, the app wrapper around MagicMirror², only supports the Raspberry Pi 2 & 3. The Raspberry Pi 1 is currently **not** supported. If you want to run this on a Raspberry Pi 1, use the [server only](#server-only) feature and setup a fullscreen browser yourself. #### Automatic Installer (Raspberry Pi Only!) @@ -39,7 +39,7 @@ curl -sL https://raw.githubusercontent.com/MichMich/MagicMirror/v2-beta/installe 1. Download and install the latest Node.js version. 2. Clone the repository and check out the beta branch: `git clone -b v2-beta https://github.com/MichMich/MagicMirror` 3. Enter the repository: `cd ~/MagicMirror` -4. Install and run the app: `npm install && npm start` (You may have to restart your terminal before this works!) +4. Install and run the app: `npm install && npm start` **Important:** `npm start` does **not** work via SSH, use `DISPLAY=:0 nohup npm start &` instead. This starts the mirror on the remote display. @@ -77,6 +77,7 @@ The following properties can be configured: | `port` | The port on which the MagicMirror² server will run on. The default value is `8080`. | | `language` | The language of the interface. (Note: Not all elements will be localized.) Possible values are `en`, `nl`, `ru`, `fr`, etc., but the default value is `en`. | | `timeFormat` | The form of time notation that will be used. Possible values are `12` or `24`. The default is `24`. | +| `units` | The units that will be used in the default weather modules. Possible values are `metric` or `imperial`. The default is `metric`. | | `modules` | An array of active modules. **The array must contain objects. See the next table below for more information.** | Module configuration: diff --git a/config/config.js.sample b/config/config.js.sample index 99ab2ead..0b5f45d4 100644 --- a/config/config.js.sample +++ b/config/config.js.sample @@ -9,6 +9,7 @@ var config = { language: 'en', timeFormat: 24, + units: 'metric', modules: [ { diff --git a/css/custom.css b/css/custom.css index 5871da5c..7d0d0681 100644 --- a/css/custom.css +++ b/css/custom.css @@ -6,9 +6,9 @@ * MIT Licensed. * * * * Add any custom CSS below. * - * Changes to this files will not be ignored by GIT. * + * Changes to this files will be ignored by GIT. * *****************************************************/ body { - } \ No newline at end of file + } diff --git a/installers/raspberry.sh b/installers/raspberry.sh index 4f1d2e2c..3c1687c0 100644 --- a/installers/raspberry.sh +++ b/installers/raspberry.sh @@ -18,7 +18,7 @@ echo "Installing helper tools ..." sudo apt-get install curl wget build-essential unzip || exit ARM=$(uname -m) # Determine which Pi is running. -NODE_LATEST="v5.11.0" # Set the latest version here. +NODE_LATEST="v6.0.0" # Set the latest version here. DOWNLOAD_URL="https://nodejs.org/dist/latest/node-$NODE_LATEST-linux-$ARM.tar.gz" # Construct the download URL. echo "Installing Latest Node.js ..." diff --git a/js/defaults.js b/js/defaults.js index e3dc3d2b..28cc2613 100644 --- a/js/defaults.js +++ b/js/defaults.js @@ -12,7 +12,8 @@ var defaults = { language: "en", timeFormat: 24, - + units: "metric", + modules: [ { module: "helloworld", diff --git a/js/logger.js b/js/logger.js index 994fe534..5b2df990 100644 --- a/js/logger.js +++ b/js/logger.js @@ -21,6 +21,27 @@ var Log = (function() { }, error: function(message) { console.error(message); + }, + warn: function(message) { + console.warn(message); + }, + group: function(message) { + console.group(message); + }, + groupCollapsed: function(message) { + console.groupCollapsed(message); + }, + groupEnd: function() { + console.groupEnd(); + }, + time: function(message) { + console.time(message); + }, + timeEnd: function(message) { + console.timeEnd(message); + }, + timeStamp: function(message) { + console.timeStamp(message); } }; })(); diff --git a/modules/README.md b/modules/README.md index 965051b0..b2caa2af 100644 --- a/modules/README.md +++ b/modules/README.md @@ -97,7 +97,7 @@ start: function() { ####`getScripts()` **Should return: Array** -The getScripts method is called to request any additional scripts that need to be loaded. This method should therefor return an array with strings. If you want to return a full path to a file in the module folder, use the `this.file('filename.js')` method. In all cases the loader will only load a file once. It even checks if the file is available in the default vendor folder. +The getScripts method is called to request any additional scripts that need to be loaded. This method should therefore return an array with strings. If you want to return a full path to a file in the module folder, use the `this.file('filename.js')` method. In all cases the loader will only load a file once. It even checks if the file is available in the default vendor folder. **Example:** ````javascript @@ -117,7 +117,7 @@ getScripts: function() { ####`getStyles()` **Should return: Array** -The getStyles method is called to request any additional scripts that need to be loaded. This method should therefor return an array with strings. If you want to return a full path to a file in the module folder, use the `this.file('filename.css')` method. In all cases the loader will only load a file once. It even checks if the file is available in the default vendor folder. +The getStyles method is called to request any additional stylesheets that need to be loaded. This method should therefore return an array with strings. If you want to return a full path to a file in the module folder, use the `this.file('filename.css')` method. In all cases the loader will only load a file once. It even checks if the file is available in the default vendor folder. **Example:** ````javascript @@ -133,6 +133,22 @@ getStyles: function() { ```` **Note:** If a file can not be loaded, the boot up of the mirror will stall. Therefore it's advised not to use any external urls. +####`getTranslations()` +**Should return: Dictionary** + +The getTranslations method is called to request translation files that need to be loaded. This method should therefore return a dictionary with the files to load, identified by the country's short name. + +**Example:** +````javascript +getTranslations: function() { + return { + en: "translations/en.json", + de: "translations/de.json" + } +} + +```` + ####`getDom()` **Should return:** Dom Object @@ -261,6 +277,25 @@ To show a module, you can call the `show(speed, callback)` method. You can call **Note 2:** If the show animation is hijacked (an other method calls show on the same module), the callback will not be called.
**Note 3:** If the dom is not yet created, the show method won't work. Wait for the `DOM_OBJECTS_CREATED` [notification](#notificationreceivednotification-payload-sender). +####`this.translate(identifier)` +***identifier* String** - Identifier of the string that should be translated. + +The Magic Mirror contains a convenience wrapper for `l18n`. You can use this to automatically serve different translations for your modules based on the user's `language` configuration. + +**Example:** +````javascript +this.translate("INFO") //Will return a translated string for the identifier INFO +```` + +**Example json file:** +````javascript +{ + "INFO": "Really important information!" +} +```` + +**Note:** Currently there is no fallback if a translation identifier does not exist in one language. Right now you always have to add all identifier to all your translations even if they are not translated yet (see [#191](https://github.com/MichMich/MagicMirror/issues/191)). + ## The Node Helper: node_helper.js @@ -458,4 +493,4 @@ The Magic Mirror contains a convenience wrapper for logging. Currently, this log Log.info('error'); Log.log('log'); Log.error('info'); -``` \ No newline at end of file +```` diff --git a/modules/default/calendar/README.md b/modules/default/calendar/README.md index df0bb0d2..cfe4e2b8 100644 --- a/modules/default/calendar/README.md +++ b/modules/default/calendar/README.md @@ -112,36 +112,6 @@ The following properties can be configured: - - loadingText - Text to display while loading item.
-
Default value: 'Loading events …' - - - - emptyCalendarText - Text to display when there are no upcoming events.
-
Default value: 'No upcoming events.' - - - - todayText - Text to display when a fullday event is planned for today.
-
Default value: 'Today' - - - - tomorrowText - Text to display when a fullday event is planned for tomorrow.
-
Default value: 'Tomorrow' - - - - runningText - Text to display when an event is still running.
-
Default value: 'Ends in' - - diff --git a/modules/default/calendar/calendar.js b/modules/default/calendar/calendar.js index c1ffc492..5e315586 100644 --- a/modules/default/calendar/calendar.js +++ b/modules/default/calendar/calendar.js @@ -124,7 +124,7 @@ Module.register("calendar",{ if (event.fullDayEvent) { if (event.today) { timeWrapper.innerHTML = this.translate("TODAY"); - } else if (event.startDate - now < 24 * 60 * 60 * 1000) { + } else if (event.startDate - now < 24 * 60 * 60 * 1000 && event.startDate - now > 0) { timeWrapper.innerHTML = this.translate("TOMORROW"); } else { timeWrapper.innerHTML = moment(event.startDate,"x").fromNow(); @@ -141,7 +141,8 @@ Module.register("calendar",{ timeWrapper.innerHTML = this.translate("RUNNING") + ' ' + moment(event.endDate,"x").fromNow(true); } } - // timeWrapper.innerHTML = moment(event.startDate,'x').format('lll'); + //timeWrapper.innerHTML += ' - '+ moment(event.startDate,'x').format('lll'); + //console.log(event); timeWrapper.className = "time light"; eventWrapper.appendChild(timeWrapper); diff --git a/modules/default/calendar/calendarfetcher.js b/modules/default/calendar/calendarfetcher.js index 771ee5b3..ac558e2f 100644 --- a/modules/default/calendar/calendarfetcher.js +++ b/modules/default/calendar/calendarfetcher.js @@ -99,7 +99,7 @@ var CalendarFetcher = function(url, reloadInterval, maximumEntries, maximumNumbe continue; } - if (fullDayEvent && endDate < today) { + if (fullDayEvent && endDate <= today) { //console.log("It's a fullday event, and it is before today. So skip: " + title); continue; } diff --git a/modules/default/calendar/translations/de.json b/modules/default/calendar/translations/de.json index 53530085..cea5588d 100644 --- a/modules/default/calendar/translations/de.json +++ b/modules/default/calendar/translations/de.json @@ -1,7 +1,7 @@ { "TODAY": "Heute" , "TOMORROW": "Morgen" - , "RUNNING": "Endet in" + , "RUNNING": "noch" , "LOADING": "Lade Termine …" , "EMPTY": "Keine Termine." } diff --git a/modules/default/calendar/vendor/ical.js/node-ical.js b/modules/default/calendar/vendor/ical.js/node-ical.js index 6ce04892..294908ea 100644 --- a/modules/default/calendar/vendor/ical.js/node-ical.js +++ b/modules/default/calendar/vendor/ical.js/node-ical.js @@ -28,6 +28,14 @@ ical.objectHandlers['END'] = function(val, params, curr, stack){ if (curr.rrule) { var rule = curr.rrule.replace('RRULE:', ''); if (rule.indexOf('DTSTART') === -1) { + + if (curr.start.length === 8) { + var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(curr.start); + if (comps) { + curr.start = new Date (comps[1], comps[2], comps[3]); + } + } + rule += ';DTSTART=' + curr.start.toISOString().replace(/[-:]/g, ''); rule = rule.replace(/\.[0-9]{3}/, ''); } diff --git a/modules/default/clock/README.md b/modules/default/clock/README.md index 522b1ad2..8808825c 100644 --- a/modules/default/clock/README.md +++ b/modules/default/clock/README.md @@ -47,14 +47,21 @@ The following properties can be configured: showPeriod - Show the period (am/pm) with 12 hour format
+ Show the period (am/pm) with 12 hour format.

Possible values: true or false
Default value: true showPeriodUpper - Show the period (AM/PM) with 12 hour format as uppercase
+ Show the period (AM/PM) with 12 hour format as uppercase.
+
Possible values: true or false +
Default value: false + + + + clockBold + Remove the colon and bold the minutes to make a more modern look.

Possible values: true or false
Default value: false diff --git a/modules/default/clock/clock.js b/modules/default/clock/clock.js index 611170f4..7f76324f 100644 --- a/modules/default/clock/clock.js +++ b/modules/default/clock/clock.js @@ -1,41 +1,34 @@ /* global Log, Module, moment, config */ - /* Magic Mirror * Module: Clock * * By Michael Teeuw http://michaelteeuw.nl * MIT Licensed. */ - Module.register("clock",{ - // Module config defaults. defaults: { timeFormat: config.timeFormat, displaySeconds: true, showPeriod: true, showPeriodUpper: false, + clockBold: false }, - // Define required scripts. getScripts: function() { return ["moment.js"]; }, - // Define start sequence. start: function() { Log.info("Starting module: " + this.name); - // Schedule update interval. var self = this; setInterval(function() { self.updateDom(); }, 1000); - // Set locale. moment.locale(config.language); }, - // Override dom generator. getDom: function() { // Create wrappers. @@ -44,35 +37,36 @@ Module.register("clock",{ var timeWrapper = document.createElement("div"); var secondsWrapper = document.createElement("sup"); var periodWrapper = document.createElement("span"); - // Style Wrappers dateWrapper.className = "date normal medium"; timeWrapper.className = "time bright large light"; secondsWrapper.className = "dimmed"; - // Set content of wrappers. - // The moment().format('h') method has a bug on the Raspberry Pi. + // The moment().format("h") method has a bug on the Raspberry Pi. // So we need to generate the timestring manually. // See issue: https://github.com/MichMich/MagicMirror/issues/181 - var timeString = moment().format('HH:mm'); + if (this.config.clockBold === true) { + var timeString = moment().format("HH[]mm[]"); + } else { + var timeString = moment().format("HH:mm"); + } if (this.config.timeFormat !== 24) { var now = new Date(); var hours = now.getHours() % 12 || 12; - timeString = hours + moment().format(':mm'); + if (this.config.clockBold === true) { + timeString = hours + moment().format("[]mm[]"); + } else { + timeString = hours + moment().format(":mm"); + } } - dateWrapper.innerHTML = moment().format("dddd, LL"); timeWrapper.innerHTML = timeString; secondsWrapper.innerHTML = moment().format("ss"); - - if (this.config.showPeriodUpper) { - periodWrapper.innerHTML = moment().format('A'); + periodWrapper.innerHTML = moment().format("A"); } else { - periodWrapper.innerHTML = moment().format('a'); + periodWrapper.innerHTML = moment().format("a"); } - - // Combine wrappers. wrapper.appendChild(dateWrapper); wrapper.appendChild(timeWrapper); @@ -82,7 +76,6 @@ Module.register("clock",{ if (this.config.showPeriod && this.config.timeFormat !== 24) { timeWrapper.appendChild(periodWrapper); } - // Return the wrapper to the dom. return wrapper; } diff --git a/modules/default/currentweather/README.md b/modules/default/currentweather/README.md index 3c818eee..2ce54aac 100644 --- a/modules/default/currentweather/README.md +++ b/modules/default/currentweather/README.md @@ -50,9 +50,9 @@ The following properties can be configured: units - What units to use?
-
Possible values: default = Kelvin, metric = Celsius, imperial =Fahrenheit -
Default value: metric + What units to use. Specified by config.js
+
Possible values: config.units = Specified by config.js, default = Kelvin, metric = Celsius, imperial =Fahrenheit +
Default value: config.units @@ -90,6 +90,13 @@ The following properties can be configured:
Default value: false + + showWindDirection + Show the wind direction next to the wind speed.
+
Possible values: true or false +
Default value: false + + lang The language of the days.
diff --git a/modules/default/currentweather/currentweather.js b/modules/default/currentweather/currentweather.js index 3a7520ce..4ab04c4f 100644 --- a/modules/default/currentweather/currentweather.js +++ b/modules/default/currentweather/currentweather.js @@ -13,12 +13,13 @@ Module.register("currentweather",{ defaults: { location: "", appid: "", - units: "metric", + units: config.units, updateInterval: 10 * 60 * 1000, // every 10 minutes animationSpeed: 1000, timeFormat: config.timeFormat, showPeriod: true, showPeriodUpper: false, + showWindDirection: false, lang: config.language, initialLoadDelay: 0, // 0 seconds delay @@ -68,6 +69,7 @@ Module.register("currentweather",{ moment.locale(config.language); this.windSpeed = null; + this.windDirection = null; this.sunriseSunsetTime = null; this.sunriseSunsetIcon = null; this.temperature = null; @@ -108,11 +110,16 @@ Module.register("currentweather",{ var windIcon = document.createElement("span"); windIcon.className = "wi wi-strong-wind dimmed"; small.appendChild(windIcon); - + var windSpeed = document.createElement("span"); windSpeed.innerHTML = " " + this.windSpeed; small.appendChild(windSpeed); - + + if (this.config.showWindDirection) { + var windDirection = document.createElement("span"); + windDirection.innerHTML = " " + this.windDirection; + small.appendChild(windDirection); + } var spacer = document.createElement("span"); spacer.innerHTML = " "; small.appendChild(spacer); @@ -198,6 +205,7 @@ Module.register("currentweather",{ processWeather: function(data) { this.temperature = this.roundValue(data.main.temp); this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed)); + this.windDirection = this.deg2Cardinal(data.wind.deg); this.weatherType = this.config.iconTable[data.weather[0].icon]; var now = new Date(); @@ -274,6 +282,44 @@ Module.register("currentweather",{ * * return number - Rounded Temperature. */ + + deg2Cardinal: function(deg) { + if (deg>11.25 && deg<33.75){ + return "NNE"; + }else if (deg>33.75 && deg<56.25){ + return "ENE"; + }else if (deg>56.25 && deg<78.75){ + return "E"; + }else if (deg>78.75 && deg<101.25){ + return "ESE"; + }else if (deg>101.25 && deg<123.75){ + return "ESE"; + }else if (deg>123.75 && deg<146.25){ + return "SE"; + }else if (deg>146.25 && deg<168.75){ + return "SSE"; + }else if (deg>168.75 && deg<191.25){ + return "S"; + }else if (deg>191.25 && deg<213.75){ + return "SSW"; + }else if (deg>213.75 && deg<236.25){ + return "SW"; + }else if (deg>236.25 && deg<258.75){ + return "WSW"; + }else if (deg>258.75 && deg<281.25){ + return "W"; + }else if (deg>281.25 && deg<303.75){ + return "WNW"; + }else if (deg>303.75 && deg<326.25){ + return "NW"; + }else if (deg>326.25 && deg<348.75){ + return "NNW"; + }else{ + return "N"; + } + }, + + roundValue: function(temperature) { return parseFloat(temperature).toFixed(1); } diff --git a/modules/default/weatherforecast/README.md b/modules/default/weatherforecast/README.md index bea80514..9e93bd14 100644 --- a/modules/default/weatherforecast/README.md +++ b/modules/default/weatherforecast/README.md @@ -50,9 +50,17 @@ The following properties can be configured: units - What units to use?
-
Possible values: default = Kelvin, metric = Celsius, imperial =Fahrenheit -
Default value: metric + What units to use. Specified by config.js
+
Possible values: config.units = Specified by config.js, default = Kelvin, metric = Celsius, imperial =Fahrenheit +
Default value: config.units + + + + maxNumberOfDays + How many days of forecast to return. Specified by config.js
+
Possible values: 1 - 16 +
Default value: 7 (7 days) +
This value is optional. By default the weatherforecast module will return 7 days. diff --git a/modules/default/weatherforecast/weatherforecast.js b/modules/default/weatherforecast/weatherforecast.js index ff7c4190..c5b8762b 100644 --- a/modules/default/weatherforecast/weatherforecast.js +++ b/modules/default/weatherforecast/weatherforecast.js @@ -13,7 +13,8 @@ Module.register("weatherforecast",{ defaults: { location: "", appid: "", - units: "metric", + units: config.units, + maxNumberOfDays: 7, updateInterval: 10 * 60 * 1000, // every 10 minutes animationSpeed: 1000, timeFormat: config.timeFormat, @@ -189,6 +190,12 @@ Module.register("weatherforecast",{ params += "q=" + this.config.location; params += "&units=" + this.config.units; params += "&lang=" + this.config.lang; + /* + * Submit a specific number of days to forecast, between 1 to 16 days. + * The OpenWeatherMap API properly handles values outside of the 1 - 16 range and returns 7 days by default. + * This is simply being pedantic and doing it ourselves. + */ + params += "&cnt=" + (((this.config.maxNumberOfDays < 1) || (this.config.maxNumberOfDays > 16)) ? 7 : this.config.maxNumberOfDays); params += "&APPID=" + this.config.appid; return params;