mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-07-03 06:15:59 +00:00
commit
5e68a8200d
3
.jscsrc
3
.jscsrc
@ -4,5 +4,6 @@
|
||||
"validateQuoteMarks": "\"",
|
||||
"maximumLineLength": 250,
|
||||
"requireCurlyBraces": [],
|
||||
"requireCamelCaseOrUpperCaseIdentifiers": false
|
||||
"requireCamelCaseOrUpperCaseIdentifiers": false,
|
||||
"excludeFiles": [".jscsrc"],
|
||||
}
|
@ -20,6 +20,7 @@ MagicMirror² focuses on a modular plugin system and uses [Electron](http://elec
|
||||
- [Configuration](#configuration)
|
||||
- [Modules](#modules)
|
||||
- [Known Issues](#known-issues)
|
||||
- [community](#community)
|
||||
- [Contributing Guidelines](#contributing-guidelines)
|
||||
|
||||
## 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).
|
||||
- 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
|
||||
|
||||
Contributions of all kinds are welcome, not only in the form of code but also with regards bug reports and documentation.
|
||||
|
@ -6,8 +6,8 @@
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<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/custom.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>
|
||||
<body>
|
||||
<div class="region fullscreen below"><div class="container"></div></div>
|
||||
|
@ -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 ..."
|
||||
|
@ -38,6 +38,14 @@ var defaults = {
|
||||
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",
|
||||
position: "bottom_bar",
|
||||
|
10
js/loader.js
10
js/loader.js
@ -34,7 +34,15 @@ var Loader = (function() {
|
||||
loadNextModule();
|
||||
});
|
||||
} else {
|
||||
startModules();
|
||||
// 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();
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
21
js/logger.js
21
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);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
@ -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.<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).
|
||||
|
||||
####`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');
|
||||
```
|
||||
````
|
||||
|
@ -18,7 +18,7 @@ Module.register("alert",{
|
||||
//Position
|
||||
position: "center",
|
||||
//shown at startup
|
||||
welcome_message: true,
|
||||
welcome_message: false,
|
||||
},
|
||||
getScripts: function() {
|
||||
return ["classie.js", "modernizr.custom.js", "notificationFx.js"];
|
||||
|
@ -144,7 +144,10 @@
|
||||
if (ev.target !== self.ntf) return false;
|
||||
this.removeEventListener(animEndEventName, onEndAnimationFn);
|
||||
}
|
||||
self.options.wrapper.removeChild(this);
|
||||
|
||||
if (this.parentNode === self.options.wrapper) {
|
||||
self.options.wrapper.removeChild(this);
|
||||
}
|
||||
};
|
||||
|
||||
if (support.animations) {
|
||||
|
@ -112,6 +112,14 @@ The following properties can be configured:
|
||||
</code>
|
||||
</td>
|
||||
</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>
|
||||
</table>
|
||||
|
||||
@ -154,5 +162,12 @@ config: {
|
||||
<br><b>Possible values:</b> See <a href="http://fontawesome.io/icons/" target="_blank">Font Awsome</a> website.
|
||||
</td>
|
||||
</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>
|
||||
</table>
|
||||
|
@ -15,6 +15,8 @@ Module.register("calendar",{
|
||||
maximumNumberOfDays: 365,
|
||||
displaySymbol: true,
|
||||
defaultSymbol: "calendar", // Fontawsome Symbol see http://fontawesome.io/cheatsheet/
|
||||
displayRepeatingCountTitle: false,
|
||||
defaultRepeatingCountTitle: '',
|
||||
maxTitleLength: 25,
|
||||
fetchInterval: 5 * 60 * 1000, // Update every 5 minutes.
|
||||
animationSpeed: 2000,
|
||||
@ -46,7 +48,8 @@ Module.register("calendar",{
|
||||
return {
|
||||
en: "translations/en.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);
|
||||
}
|
||||
|
||||
var titleWrapper = document.createElement("td");
|
||||
titleWrapper.innerHTML = this.titleTransform(event.title);
|
||||
var titleWrapper = document.createElement("td"),
|
||||
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";
|
||||
eventWrapper.appendChild(titleWrapper);
|
||||
|
||||
@ -239,6 +257,23 @@ Module.register("calendar",{
|
||||
|
||||
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)
|
||||
* Shortens a sting if it's longer than maxLenthg.
|
||||
|
@ -55,14 +55,16 @@ var CalendarFetcher = function(url, reloadInterval, maximumEntries, maximumNumbe
|
||||
|
||||
if (event.type === "VEVENT") {
|
||||
|
||||
//console.log(event);
|
||||
|
||||
var startDate = (event.start.length === 8) ? moment(event.start, "YYYYMMDD") : moment(new Date(event.start));
|
||||
var endDate;
|
||||
if (typeof event.end !== "undefined") {
|
||||
endDate = (event.end.length === 8) ? moment(event.end, "YYYYMMDD") : moment(new Date(event.end));
|
||||
} else {
|
||||
endDate = startDate;
|
||||
if (!isFacebookBirthday) {
|
||||
endDate = startDate;
|
||||
} else {
|
||||
endDate = moment(startDate).add(1, 'days');
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
startDate: startDate.format("x"),
|
||||
endDate: endDate.format("x"),
|
||||
fullDayEvent: isFullDayEvent(event)
|
||||
fullDayEvent: isFullDayEvent(event),
|
||||
firstYear: event.start.getFullYear()
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// console.log("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;
|
||||
|
||||
if (!fullDayEvent && endDate < new Date()) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"TODAY": "Heute"
|
||||
, "TOMORROW": "Morgen"
|
||||
, "RUNNING": "Noch"
|
||||
, "RUNNING": "noch"
|
||||
, "LOADING": "Lade Termine …"
|
||||
, "EMPTY": "Keine Termine."
|
||||
}
|
||||
|
7
modules/default/calendar/translations/fr.json
Normal file
7
modules/default/calendar/translations/fr.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"TODAY": "Aujourd'hui"
|
||||
, "TOMORROW": "Demain"
|
||||
, "RUNNING": "Se termine dans"
|
||||
, "LOADING": "Chargement des RDV …"
|
||||
, "EMPTY": "Aucun RDV."
|
||||
}
|
@ -32,7 +32,7 @@ ical.objectHandlers['END'] = function(val, params, curr, stack){
|
||||
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]);
|
||||
curr.start = new Date (comps[1], comps[2] - 1, comps[3]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,14 +47,21 @@ The following properties can be configured:
|
||||
</tr>
|
||||
<tr>
|
||||
<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>Default value:</b> <code>true</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<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>Default value:</b> <code>false</code>
|
||||
</td>
|
||||
|
@ -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[<span class=\"bold\">]mm[</span>]");
|
||||
} 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("[<span class=\"bold\">]mm[</span>]");
|
||||
} 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;
|
||||
}
|
||||
|
@ -55,6 +55,14 @@ The following properties can be configured:
|
||||
<br><b>Default value:</b> <code>config.units</code>
|
||||
</td>
|
||||
</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>
|
||||
<td><code>updateInterval</code></td>
|
||||
<td>How often does the content needs to be fetched? (Milliseconds)<br>
|
||||
|
@ -14,6 +14,7 @@ Module.register("weatherforecast",{
|
||||
location: "",
|
||||
appid: "",
|
||||
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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user