Merge pull request #1 from MichMich/v2-beta

V2 beta
This commit is contained in:
roxasvalor 2016-05-03 18:38:38 -05:00
commit 5e68a8200d
20 changed files with 198 additions and 42 deletions

View File

@ -4,5 +4,6 @@
"validateQuoteMarks": "\"", "validateQuoteMarks": "\"",
"maximumLineLength": 250, "maximumLineLength": 250,
"requireCurlyBraces": [], "requireCurlyBraces": [],
"requireCamelCaseOrUpperCaseIdentifiers": false "requireCamelCaseOrUpperCaseIdentifiers": false,
"excludeFiles": [".jscsrc"],
} }

View File

@ -20,6 +20,7 @@ MagicMirror² focuses on a modular plugin system and uses [Electron](http://elec
- [Configuration](#configuration) - [Configuration](#configuration)
- [Modules](#modules) - [Modules](#modules)
- [Known Issues](#known-issues) - [Known Issues](#known-issues)
- [community](#community)
- [Contributing Guidelines](#contributing-guidelines) - [Contributing Guidelines](#contributing-guidelines)
## Usage ## Usage
@ -110,6 +111,10 @@ For more available modules, check out out the wiki page: [MagicMirror² Modules]
- Electron seems to have some issues on certain Raspberry Pi 2's. See [#145](https://github.com/MichMich/MagicMirror/issues/145). - Electron seems to have some issues on certain Raspberry Pi 2's. See [#145](https://github.com/MichMich/MagicMirror/issues/145).
- MagicMirror² (Electron) sometimes quits without an error after an extended period of use. See [#150](https://github.com/MichMich/MagicMirror/issues/150). - MagicMirror² (Electron) sometimes quits without an error after an extended period of use. See [#150](https://github.com/MichMich/MagicMirror/issues/150).
## Community
The community around the MagicMirror² is constantly growing. We even have a [forum](https://forum.magicmirror.builders) now where you can share your ideas, ask questions, help others and get inspired by other builders. We would love to see you there!
## Contributing Guidelines ## Contributing Guidelines
Contributions of all kinds are welcome, not only in the form of code but also with regards bug reports and documentation. Contributions of all kinds are welcome, not only in the form of code but also with regards bug reports and documentation.

View File

@ -6,8 +6,8 @@
<meta http-equiv="Content-type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<link rel="icon" href="data:;base64,iVBORw0KGgo="> <link rel="icon" href="data:;base64,iVBORw0KGgo=">
<link rel="stylesheet" type="text/css" href="css/main.css"> <link rel="stylesheet" type="text/css" href="css/main.css">
<link rel="stylesheet" type="text/css" href="css/custom.css">
<link rel="stylesheet" type="text/css" href="fonts/roboto.css"> <link rel="stylesheet" type="text/css" href="fonts/roboto.css">
<!-- custom.css is loaded by the loader.js to make sure it's loaded after the module css files. -->
</head> </head>
<body> <body>
<div class="region fullscreen below"><div class="container"></div></div> <div class="region fullscreen below"><div class="container"></div></div>

View File

@ -18,7 +18,7 @@
echo "Installing helper tools ..." echo "Installing helper tools ..."
sudo apt-get install curl wget build-essential unzip || exit sudo apt-get install curl wget build-essential unzip || exit
ARM=$(uname -m) # Determine which Pi is running. 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. DOWNLOAD_URL="https://nodejs.org/dist/latest/node-$NODE_LATEST-linux-$ARM.tar.gz" # Construct the download URL.
echo "Installing Latest Node.js ..." echo "Installing Latest Node.js ..."

View File

@ -38,6 +38,14 @@ var defaults = {
text: "See README for more information." text: "See README for more information."
} }
}, },
{
module: "helloworld",
position: "middle_center",
classes: "xsmall",
config: {
text: "If you get this message while your config file is already<br>created, your config file probably contains an error.<br>Use a JavaScript linter to validate your file."
}
},
{ {
module: "helloworld", module: "helloworld",
position: "bottom_bar", position: "bottom_bar",

View File

@ -34,7 +34,15 @@ var Loader = (function() {
loadNextModule(); loadNextModule();
}); });
} else { } else {
// All modules loaded. Load custom.css
// This is done after all the moduels so we can
// overwrite all the defined styls.
loadFile('css/custom.css', function() {
// custom.css loaded. Start all modules.
startModules(); startModules();
});
} }
}; };

View File

@ -21,6 +21,27 @@ var Log = (function() {
}, },
error: function(message) { error: function(message) {
console.error(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);
} }
}; };
})(); })();

View File

@ -97,7 +97,7 @@ start: function() {
####`getScripts()` ####`getScripts()`
**Should return: Array** **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:** **Example:**
````javascript ````javascript
@ -117,7 +117,7 @@ getScripts: function() {
####`getStyles()` ####`getStyles()`
**Should return: Array** **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:** **Example:**
````javascript ````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. **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()` ####`getDom()`
**Should return:** Dom Object **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.<br> **Note 2:** If the show animation is hijacked (an other method calls show on the same module), the callback will not be called.<br>
**Note 3:** If the dom is not yet created, the show method won't work. Wait for the `DOM_OBJECTS_CREATED` [notification](#notificationreceivednotification-payload-sender). **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 ## 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.info('error');
Log.log('log'); Log.log('log');
Log.error('info'); Log.error('info');
``` ````

View File

@ -18,7 +18,7 @@ Module.register("alert",{
//Position //Position
position: "center", position: "center",
//shown at startup //shown at startup
welcome_message: true, welcome_message: false,
}, },
getScripts: function() { getScripts: function() {
return ["classie.js", "modernizr.custom.js", "notificationFx.js"]; return ["classie.js", "modernizr.custom.js", "notificationFx.js"];

View File

@ -144,7 +144,10 @@
if (ev.target !== self.ntf) return false; if (ev.target !== self.ntf) return false;
this.removeEventListener(animEndEventName, onEndAnimationFn); this.removeEventListener(animEndEventName, onEndAnimationFn);
} }
if (this.parentNode === self.options.wrapper) {
self.options.wrapper.removeChild(this); self.options.wrapper.removeChild(this);
}
}; };
if (support.animations) { if (support.animations) {

View File

@ -112,6 +112,14 @@ The following properties can be configured:
</code> </code>
</td> </td>
</tr> </tr>
<tr>
<td><code>displayRepeatingCountTitle</code></td>
<td>Show count title for yearly repeating events (e.g. "X. Birthday", "X. Anniversary")<br>
<br><b>Possible values:</b> <code>true</code> or <code>false</code>
<br><b>Default value:</b> <code>false</code>
</td>
</tr>
</tbody> </tbody>
</table> </table>
@ -154,5 +162,12 @@ config: {
<br><b>Possible values:</b> See <a href="http://fontawesome.io/icons/" target="_blank">Font Awsome</a> website. <br><b>Possible values:</b> See <a href="http://fontawesome.io/icons/" target="_blank">Font Awsome</a> website.
</td> </td>
</tr> </tr>
<tr>
<td><code> repeatingCountTitle </code></td>
<td>The count title for yearly repating events in this calendar. <br>
<br><b>Example:</b> <br>
<code>'Birthday'</code>
</td>
</tr>
</tbody> </tbody>
</table> </table>

View File

@ -15,6 +15,8 @@ Module.register("calendar",{
maximumNumberOfDays: 365, maximumNumberOfDays: 365,
displaySymbol: true, displaySymbol: true,
defaultSymbol: "calendar", // Fontawsome Symbol see http://fontawesome.io/cheatsheet/ defaultSymbol: "calendar", // Fontawsome Symbol see http://fontawesome.io/cheatsheet/
displayRepeatingCountTitle: false,
defaultRepeatingCountTitle: '',
maxTitleLength: 25, maxTitleLength: 25,
fetchInterval: 5 * 60 * 1000, // Update every 5 minutes. fetchInterval: 5 * 60 * 1000, // Update every 5 minutes.
animationSpeed: 2000, animationSpeed: 2000,
@ -46,7 +48,8 @@ Module.register("calendar",{
return { return {
en: "translations/en.json", en: "translations/en.json",
de: "translations/de.json", de: "translations/de.json",
nl: "translations/nl.json" nl: "translations/nl.json",
fr: "translations/fr.json"
}; };
}, },
@ -113,8 +116,23 @@ Module.register("calendar",{
eventWrapper.appendChild(symbolWrapper); eventWrapper.appendChild(symbolWrapper);
} }
var titleWrapper = document.createElement("td"); var titleWrapper = document.createElement("td"),
titleWrapper.innerHTML = this.titleTransform(event.title); repeatingCountTitle = '';
if (this.config.displayRepeatingCountTitle) {
repeatingCountTitle = this.countTitleForUrl(event.url);
if(repeatingCountTitle !== '') {
var thisYear = new Date().getFullYear(),
yearDiff = thisYear - event.firstYear;
repeatingCountTitle = ', '+ yearDiff + '. ' + repeatingCountTitle;
}
}
titleWrapper.innerHTML = this.titleTransform(event.title) + repeatingCountTitle;
titleWrapper.className = "title bright"; titleWrapper.className = "title bright";
eventWrapper.appendChild(titleWrapper); eventWrapper.appendChild(titleWrapper);
@ -239,6 +257,23 @@ Module.register("calendar",{
return this.config.defaultSymbol; return this.config.defaultSymbol;
}, },
/* countTitleForUrl(url)
* Retrieves the name for a specific url.
*
* argument url sting - Url to look for.
*
* return string - The Symbol
*/
countTitleForUrl: function(url) {
for (var c in this.config.calendars) {
var calendar = this.config.calendars[c];
if (calendar.url === url && typeof calendar.repeatingCountTitle === "string") {
return calendar.repeatingCountTitle;
}
}
return this.config.defaultRepeatingCountTitle;
},
/* shorten(string, maxLength) /* shorten(string, maxLength)
* Shortens a sting if it's longer than maxLenthg. * Shortens a sting if it's longer than maxLenthg.

View File

@ -55,14 +55,16 @@ var CalendarFetcher = function(url, reloadInterval, maximumEntries, maximumNumbe
if (event.type === "VEVENT") { if (event.type === "VEVENT") {
//console.log(event);
var startDate = (event.start.length === 8) ? moment(event.start, "YYYYMMDD") : moment(new Date(event.start)); var startDate = (event.start.length === 8) ? moment(event.start, "YYYYMMDD") : moment(new Date(event.start));
var endDate; var endDate;
if (typeof event.end !== "undefined") { if (typeof event.end !== "undefined") {
endDate = (event.end.length === 8) ? moment(event.end, "YYYYMMDD") : moment(new Date(event.end)); endDate = (event.end.length === 8) ? moment(event.end, "YYYYMMDD") : moment(new Date(event.end));
} else { } else {
if (!isFacebookBirthday) {
endDate = startDate; endDate = startDate;
} else {
endDate = moment(startDate).add(1, 'days');
}
} }
// calculate the duration f the event for use with recurring events. // calculate the duration f the event for use with recurring events.
@ -84,14 +86,15 @@ var CalendarFetcher = function(url, reloadInterval, maximumEntries, maximumNumbe
title: (typeof event.summary.val !== "undefined") ? event.summary.val : event.summary, title: (typeof event.summary.val !== "undefined") ? event.summary.val : event.summary,
startDate: startDate.format("x"), startDate: startDate.format("x"),
endDate: endDate.format("x"), endDate: endDate.format("x"),
fullDayEvent: isFullDayEvent(event) fullDayEvent: isFullDayEvent(event),
firstYear: event.start.getFullYear()
}); });
} }
} }
} else { } else {
// console.log("Single event ..."); // console.log("Single event ...");
// Single event. // Single event.
var fullDayEvent = isFullDayEvent(event); var fullDayEvent = (isFacebookBirthday) ? true : isFullDayEvent(event);
var title = (typeof event.summary.val !== "undefined") ? event.summary.val : event.summary; var title = (typeof event.summary.val !== "undefined") ? event.summary.val : event.summary;
if (!fullDayEvent && endDate < new Date()) { if (!fullDayEvent && endDate < new Date()) {

View File

@ -1,7 +1,7 @@
{ {
"TODAY": "Heute" "TODAY": "Heute"
, "TOMORROW": "Morgen" , "TOMORROW": "Morgen"
, "RUNNING": "Noch" , "RUNNING": "noch"
, "LOADING": "Lade Termine &hellip;" , "LOADING": "Lade Termine &hellip;"
, "EMPTY": "Keine Termine." , "EMPTY": "Keine Termine."
} }

View File

@ -0,0 +1,7 @@
{
"TODAY": "Aujourd'hui"
, "TOMORROW": "Demain"
, "RUNNING": "Se termine dans"
, "LOADING": "Chargement des RDV &hellip;"
, "EMPTY": "Aucun RDV."
}

View File

@ -32,7 +32,7 @@ ical.objectHandlers['END'] = function(val, params, curr, stack){
if (curr.start.length === 8) { if (curr.start.length === 8) {
var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(curr.start); var comps = /^(\d{4})(\d{2})(\d{2})$/.exec(curr.start);
if (comps) { if (comps) {
curr.start = new Date (comps[1], comps[2], comps[3]); curr.start = new Date (comps[1], comps[2] - 1, comps[3]);
} }
} }

View File

@ -47,14 +47,21 @@ The following properties can be configured:
</tr> </tr>
<tr> <tr>
<td><code>showPeriod</code></td> <td><code>showPeriod</code></td>
<td>Show the period (am/pm) with 12 hour format<br> <td>Show the period (am/pm) with 12 hour format.<br>
<br><b>Possible values:</b> <code>true</code> or <code>false</code> <br><b>Possible values:</b> <code>true</code> or <code>false</code>
<br><b>Default value:</b> <code>true</code> <br><b>Default value:</b> <code>true</code>
</td> </td>
</tr> </tr>
<tr> <tr>
<td><code>showPeriodUpper</code></td> <td><code>showPeriodUpper</code></td>
<td>Show the period (AM/PM) with 12 hour format as uppercase<br> <td>Show the period (AM/PM) with 12 hour format as uppercase.<br>
<br><b>Possible values:</b> <code>true</code> or <code>false</code>
<br><b>Default value:</b> <code>false</code>
</td>
</tr>
<tr>
<td><code>clockBold</code></td>
<td>Remove the colon and bold the minutes to make a more modern look.<br>
<br><b>Possible values:</b> <code>true</code> or <code>false</code> <br><b>Possible values:</b> <code>true</code> or <code>false</code>
<br><b>Default value:</b> <code>false</code> <br><b>Default value:</b> <code>false</code>
</td> </td>

View File

@ -1,41 +1,34 @@
/* global Log, Module, moment, config */ /* global Log, Module, moment, config */
/* Magic Mirror /* Magic Mirror
* Module: Clock * Module: Clock
* *
* By Michael Teeuw http://michaelteeuw.nl * By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed. * MIT Licensed.
*/ */
Module.register("clock",{ Module.register("clock",{
// Module config defaults. // Module config defaults.
defaults: { defaults: {
timeFormat: config.timeFormat, timeFormat: config.timeFormat,
displaySeconds: true, displaySeconds: true,
showPeriod: true, showPeriod: true,
showPeriodUpper: false, showPeriodUpper: false,
clockBold: false
}, },
// Define required scripts. // Define required scripts.
getScripts: function() { getScripts: function() {
return ["moment.js"]; return ["moment.js"];
}, },
// Define start sequence. // Define start sequence.
start: function() { start: function() {
Log.info("Starting module: " + this.name); Log.info("Starting module: " + this.name);
// Schedule update interval. // Schedule update interval.
var self = this; var self = this;
setInterval(function() { setInterval(function() {
self.updateDom(); self.updateDom();
}, 1000); }, 1000);
// Set locale. // Set locale.
moment.locale(config.language); moment.locale(config.language);
}, },
// Override dom generator. // Override dom generator.
getDom: function() { getDom: function() {
// Create wrappers. // Create wrappers.
@ -44,35 +37,36 @@ Module.register("clock",{
var timeWrapper = document.createElement("div"); var timeWrapper = document.createElement("div");
var secondsWrapper = document.createElement("sup"); var secondsWrapper = document.createElement("sup");
var periodWrapper = document.createElement("span"); var periodWrapper = document.createElement("span");
// Style Wrappers // Style Wrappers
dateWrapper.className = "date normal medium"; dateWrapper.className = "date normal medium";
timeWrapper.className = "time bright large light"; timeWrapper.className = "time bright large light";
secondsWrapper.className = "dimmed"; secondsWrapper.className = "dimmed";
// Set content of wrappers. // 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. // So we need to generate the timestring manually.
// See issue: https://github.com/MichMich/MagicMirror/issues/181 // See issue: https://github.com/MichMich/MagicMirror/issues/181
var timeString = moment().format('HH:mm'); if (this.config.clockBold === true) {
var timeString = moment().format("HH[<span class=\"bold\">]mm[</span>]");
} else {
var timeString = moment().format("HH:mm");
}
if (this.config.timeFormat !== 24) { if (this.config.timeFormat !== 24) {
var now = new Date(); var now = new Date();
var hours = now.getHours() % 12 || 12; var hours = now.getHours() % 12 || 12;
timeString = hours + moment().format(':mm'); if (this.config.clockBold === true) {
timeString = hours + moment().format("[<span class=\"bold\">]mm[</span>]");
} else {
timeString = hours + moment().format(":mm");
}
} }
dateWrapper.innerHTML = moment().format("dddd, LL"); dateWrapper.innerHTML = moment().format("dddd, LL");
timeWrapper.innerHTML = timeString; timeWrapper.innerHTML = timeString;
secondsWrapper.innerHTML = moment().format("ss"); secondsWrapper.innerHTML = moment().format("ss");
if (this.config.showPeriodUpper) { if (this.config.showPeriodUpper) {
periodWrapper.innerHTML = moment().format('A'); periodWrapper.innerHTML = moment().format("A");
} else { } else {
periodWrapper.innerHTML = moment().format('a'); periodWrapper.innerHTML = moment().format("a");
} }
// Combine wrappers. // Combine wrappers.
wrapper.appendChild(dateWrapper); wrapper.appendChild(dateWrapper);
wrapper.appendChild(timeWrapper); wrapper.appendChild(timeWrapper);
@ -82,7 +76,6 @@ Module.register("clock",{
if (this.config.showPeriod && this.config.timeFormat !== 24) { if (this.config.showPeriod && this.config.timeFormat !== 24) {
timeWrapper.appendChild(periodWrapper); timeWrapper.appendChild(periodWrapper);
} }
// Return the wrapper to the dom. // Return the wrapper to the dom.
return wrapper; return wrapper;
} }

View File

@ -55,6 +55,14 @@ The following properties can be configured:
<br><b>Default value:</b> <code>config.units</code> <br><b>Default value:</b> <code>config.units</code>
</td> </td>
</tr> </tr>
<tr>
<td><code>maxNumberOfDays</code></td>
<td>How many days of forecast to return. Specified by config.js<br>
<br><b>Possible values:</b> <code>1</code> - <code>16</code>
<br><b>Default value:</b> <code>7</code> (7 days)
<br>This value is optional. By default the weatherforecast module will return 7 days.
</td>
</tr>
<tr> <tr>
<td><code>updateInterval</code></td> <td><code>updateInterval</code></td>
<td>How often does the content needs to be fetched? (Milliseconds)<br> <td>How often does the content needs to be fetched? (Milliseconds)<br>

View File

@ -14,6 +14,7 @@ Module.register("weatherforecast",{
location: "", location: "",
appid: "", appid: "",
units: config.units, units: config.units,
maxNumberOfDays: 7,
updateInterval: 10 * 60 * 1000, // every 10 minutes updateInterval: 10 * 60 * 1000, // every 10 minutes
animationSpeed: 1000, animationSpeed: 1000,
timeFormat: config.timeFormat, timeFormat: config.timeFormat,
@ -189,6 +190,12 @@ Module.register("weatherforecast",{
params += "q=" + this.config.location; params += "q=" + this.config.location;
params += "&units=" + this.config.units; params += "&units=" + this.config.units;
params += "&lang=" + this.config.lang; 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; params += "&APPID=" + this.config.appid;
return params; return params;