Merge pull request #2013 from rejas/prettier

Add Prettier for an even cleaner code-experience
This commit is contained in:
Michael Teeuw
2020-05-25 18:46:36 +02:00
committed by GitHub
170 changed files with 2992 additions and 2626 deletions

View File

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

View File

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

View File

@@ -235,8 +235,12 @@
}
@keyframes animFade {
0% { opacity: 0; }
100% { opacity: 1; }
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes animJelly {

View File

@@ -10,8 +10,7 @@
* Copyright 2014, Codrops
* https://tympanus.net/codrops/
*/
(function(window) {
(function (window) {
/**
* extend obj function
*/
@@ -58,19 +57,23 @@
ttl: 6000,
al_no: "ns-box",
// callbacks
onClose: function() { return false; },
onOpen: function() { return false; }
onClose: function () {
return false;
},
onOpen: function () {
return false;
}
};
/**
* init function
* initialize and cache some vars
*/
NotificationFx.prototype._init = function() {
NotificationFx.prototype._init = function () {
// create HTML structure
this.ntf = document.createElement("div");
this.ntf.className = this.options.al_no + " ns-" + this.options.layout + " ns-effect-" + this.options.effect + " ns-type-" + this.options.type;
let strinner = "<div class=\"ns-box-inner\">";
let strinner = '<div class="ns-box-inner">';
strinner += this.options.message;
strinner += "</div>";
this.ntf.innerHTML = strinner;
@@ -94,15 +97,17 @@
/**
* init events
*/
NotificationFx.prototype._initEvents = function() {
NotificationFx.prototype._initEvents = function () {
// dismiss notification by tapping on it if someone has a touchscreen
this.ntf.querySelector(".ns-box-inner").addEventListener("click", () => { this.dismiss(); });
this.ntf.querySelector(".ns-box-inner").addEventListener("click", () => {
this.dismiss();
});
};
/**
* show the notification
*/
NotificationFx.prototype.show = function() {
NotificationFx.prototype.show = function () {
this.active = true;
this.ntf.classList.remove("ns-hide");
this.ntf.classList.add("ns-show");
@@ -112,7 +117,7 @@
/**
* dismiss the notification
*/
NotificationFx.prototype.dismiss = function() {
NotificationFx.prototype.dismiss = function () {
this.active = false;
clearTimeout(this.dismissttl);
this.ntf.classList.remove("ns-show");
@@ -125,7 +130,9 @@
// after animation ends remove ntf from the DOM
const onEndAnimationFn = (ev) => {
if (ev.target !== this.ntf) {return false;}
if (ev.target !== this.ntf) {
return false;
}
this.ntf.removeEventListener("animationend", onEndAnimationFn);
if (ev.target.parentNode === this.options.wrapper) {
@@ -140,5 +147,4 @@
* add to global namespace
*/
window.NotificationFx = NotificationFx;
})(window);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,6 @@
* MIT Licensed.
*/
Module.register("calendar", {
// Define module defaults
defaults: {
maximumEntries: 10, // Total Maximum Entries
@@ -39,8 +38,8 @@ Module.register("calendar", {
calendars: [
{
symbol: "calendar",
url: "https://www.calendarlabs.com/templates/ical/US-Holidays.ics",
},
url: "https://www.calendarlabs.com/templates/ical/US-Holidays.ics"
}
],
titleReplace: {
"De verjaardag van ": "",
@@ -85,7 +84,7 @@ Module.register("calendar", {
var calendarConfig = {
maximumEntries: calendar.maximumEntries,
maximumNumberOfDays: calendar.maximumNumberOfDays,
broadcastPastEvents: calendar.broadcastPastEvents,
broadcastPastEvents: calendar.broadcastPastEvents
};
if (calendar.symbolClass === "undefined" || calendar.symbolClass === null) {
calendarConfig.symbolClass = "";
@@ -98,7 +97,7 @@ Module.register("calendar", {
}
// we check user and password here for backwards compatibility with old configs
if(calendar.user && calendar.pass) {
if (calendar.user && calendar.pass) {
Log.warn("Deprecation warning: Please update your calendar authentication configuration.");
Log.warn("https://github.com/MichMich/MagicMirror/tree/v2.1.2/modules/default/calendar#calendar-authentication-options");
calendar.auth = {
@@ -112,7 +111,7 @@ Module.register("calendar", {
// Trigger ADD_CALENDAR every fetchInterval to make sure there is always a calendar
// fetcher running on the server side.
var self = this;
setInterval(function() {
setInterval(function () {
self.addCalendar(calendar.url, calendar.auth, calendarConfig);
}, self.config.fetchInterval);
}
@@ -144,13 +143,12 @@ Module.register("calendar", {
// Override dom generator.
getDom: function () {
var events = this.createEventList();
var wrapper = document.createElement("table");
wrapper.className = this.config.tableClass;
if (events.length === 0) {
wrapper.innerHTML = (this.loaded) ? this.translate("EMPTY") : this.translate("LOADING");
wrapper.innerHTML = this.loaded ? this.translate("EMPTY") : this.translate("LOADING");
wrapper.className = this.config.tableClass + " dimmed";
return wrapper;
}
@@ -169,8 +167,8 @@ Module.register("calendar", {
for (var e in events) {
var event = events[e];
var dateAsString = moment(event.startDate, "x").format(this.config.dateFormat);
if(this.config.timeFormat === "dateheaders"){
if(lastSeenDate !== dateAsString){
if (this.config.timeFormat === "dateheaders") {
if (lastSeenDate !== dateAsString) {
var dateRow = document.createElement("tr");
dateRow.className = "normal";
var dateCell = document.createElement("td");
@@ -181,9 +179,10 @@ Module.register("calendar", {
dateRow.appendChild(dateCell);
wrapper.appendChild(dateRow);
if (e >= startFade) { //fading
if (e >= startFade) {
//fading
currentFadeStep = e - startFade;
dateRow.style.opacity = 1 - (1 / fadeSteps * currentFadeStep);
dateRow.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
}
lastSeenDate = dateAsString;
@@ -209,20 +208,20 @@ Module.register("calendar", {
symbolWrapper.className = "symbol align-right " + symbolClass;
var symbols = this.symbolsForUrl(event.url);
if(typeof symbols === "string") {
if (typeof symbols === "string") {
symbols = [symbols];
}
for(var i = 0; i < symbols.length; i++) {
for (var i = 0; i < symbols.length; i++) {
var symbol = document.createElement("span");
symbol.className = "fa fa-fw fa-" + symbols[i];
if(i > 0){
if (i > 0) {
symbol.style.paddingLeft = "5px";
}
symbolWrapper.appendChild(symbol);
}
eventWrapper.appendChild(symbolWrapper);
} else if(this.config.timeFormat === "dateheaders"){
} else if (this.config.timeFormat === "dateheaders") {
var blankCell = document.createElement("td");
blankCell.innerHTML = "&nbsp;&nbsp;&nbsp;";
eventWrapper.appendChild(blankCell);
@@ -232,7 +231,6 @@ Module.register("calendar", {
repeatingCountTitle = "";
if (this.config.displayRepeatingCountTitle && event.firstYear !== undefined) {
repeatingCountTitle = this.countTitleForUrl(event.url);
if (repeatingCountTitle !== "") {
@@ -255,12 +253,10 @@ Module.register("calendar", {
var timeWrapper;
if(this.config.timeFormat === "dateheaders"){
if (this.config.timeFormat === "dateheaders") {
if (event.fullDayEvent) {
titleWrapper.colSpan = "2";
titleWrapper.align = "left";
} else {
timeWrapper = document.createElement("td");
timeWrapper.className = "time light " + this.timeClassForUrl(event.url);
@@ -298,14 +294,14 @@ Module.register("calendar", {
}
} else {
/* Check to see if the user displays absolute or relative dates with their events
* Also check to see if an event is happening within an 'urgency' time frameElement
* For example, if the user set an .urgency of 7 days, those events that fall within that
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
*
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
*/
* Also check to see if an event is happening within an 'urgency' time frameElement
* For example, if the user set an .urgency of 7 days, those events that fall within that
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
*
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
*/
if (this.config.timeFormat === "absolute") {
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * oneDay))) {
if (this.config.urgency > 1 && event.startDate - now < this.config.urgency * oneDay) {
// This event falls within the config.urgency period that the user has set
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").from(moment().format("YYYYMMDD")));
} else {
@@ -315,9 +311,9 @@ Module.register("calendar", {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").from(moment().format("YYYYMMDD")));
}
}
if(this.config.showEnd){
timeWrapper.innerHTML += "-" ;
timeWrapper.innerHTML += this.capFirst(moment(event.endDate , "x").format(this.config.fullDayEventDateFormat));
if (this.config.showEnd) {
timeWrapper.innerHTML += "-";
timeWrapper.innerHTML += this.capFirst(moment(event.endDate, "x").format(this.config.fullDayEventDateFormat));
}
} else {
if (event.startDate >= new Date()) {
@@ -327,7 +323,7 @@ Module.register("calendar", {
// If event is within 6 hour, display 'in xxx' time format or moment.fromNow()
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
} else {
if(this.config.timeFormat === "absolute" && !this.config.nextDaysRelative) {
if (this.config.timeFormat === "absolute" && !this.config.nextDaysRelative) {
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").format(this.config.dateFormat));
} else {
// Otherwise just say 'Today/Tomorrow at such-n-such time'
@@ -336,14 +332,14 @@ Module.register("calendar", {
}
} else {
/* Check to see if the user displays absolute or relative dates with their events
* Also check to see if an event is happening within an 'urgency' time frameElement
* For example, if the user set an .urgency of 7 days, those events that fall within that
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
*
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
*/
* Also check to see if an event is happening within an 'urgency' time frameElement
* For example, if the user set an .urgency of 7 days, those events that fall within that
* time frame will be displayed with 'in xxx' time format or moment.fromNow()
*
* Note: this needs to be put in its own function, as the whole thing repeats again verbatim
*/
if (this.config.timeFormat === "absolute") {
if ((this.config.urgency > 1) && (event.startDate - now < (this.config.urgency * oneDay))) {
if (this.config.urgency > 1 && event.startDate - now < this.config.urgency * oneDay) {
// This event falls within the config.urgency period that the user has set
timeWrapper.innerHTML = this.capFirst(moment(event.startDate, "x").fromNow());
} else {
@@ -364,7 +360,6 @@ Module.register("calendar", {
if (this.config.showEnd) {
timeWrapper.innerHTML += "-";
timeWrapper.innerHTML += this.capFirst(moment(event.endDate, "x").format(this.config.dateEndFormat));
}
}
//timeWrapper.innerHTML += ' - '+ moment(event.startDate,'x').format('lll');
@@ -378,7 +373,7 @@ Module.register("calendar", {
// Create fade effect.
if (e >= startFade) {
currentFadeStep = e - startFade;
eventWrapper.style.opacity = 1 - (1 / fadeSteps * currentFadeStep);
eventWrapper.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
}
if (this.config.showLocation) {
@@ -401,7 +396,7 @@ Module.register("calendar", {
if (e >= startFade) {
currentFadeStep = e - startFade;
locationRow.style.opacity = 1 - (1 / fadeSteps * currentFadeStep);
locationRow.style.opacity = 1 - (1 / fadeSteps) * currentFadeStep;
}
}
}
@@ -418,17 +413,17 @@ Module.register("calendar", {
* @param {number} timeFormat Specifies either 12 or 24 hour time format
* @returns {moment.LocaleSpecification}
*/
getLocaleSpecification: function(timeFormat) {
getLocaleSpecification: function (timeFormat) {
switch (timeFormat) {
case 12: {
return { longDateFormat: {LT: "h:mm A"} };
}
case 24: {
return { longDateFormat: {LT: "HH:mm"} };
}
default: {
return { longDateFormat: {LT: moment.localeData().longDateFormat("LT")} };
}
case 12: {
return { longDateFormat: { LT: "h:mm A" } };
}
case 24: {
return { longDateFormat: { LT: "HH:mm" } };
}
default: {
return { longDateFormat: { LT: moment.localeData().longDateFormat("LT") } };
}
}
},
@@ -464,37 +459,37 @@ Module.register("calendar", {
var calendar = this.calendarData[c];
for (var e in calendar) {
var event = JSON.parse(JSON.stringify(calendar[e])); // clone object
if(event.endDate < now) {
if (event.endDate < now) {
continue;
}
if(this.config.hidePrivate) {
if(event.class === "PRIVATE") {
if (this.config.hidePrivate) {
if (event.class === "PRIVATE") {
// do not add the current event, skip it
continue;
}
}
if(this.config.hideOngoing) {
if(event.startDate < now) {
if (this.config.hideOngoing) {
if (event.startDate < now) {
continue;
}
}
if(this.listContainsEvent(events,event)){
if (this.listContainsEvent(events, event)) {
continue;
}
event.url = c;
event.today = event.startDate >= today && event.startDate < (today + 24 * 60 * 60 * 1000);
event.today = event.startDate >= today && event.startDate < today + 24 * 60 * 60 * 1000;
/* if sliceMultiDayEvents is set to true, multiday events (events exceeding at least one midnight) are sliced into days,
* otherwise, esp. in dateheaders mode it is not clear how long these events are.
*/
var maxCount = Math.ceil(((event.endDate - 1) - moment(event.startDate, "x").endOf("day").format("x"))/(1000*60*60*24)) + 1;
* otherwise, esp. in dateheaders mode it is not clear how long these events are.
*/
var maxCount = Math.ceil((event.endDate - 1 - moment(event.startDate, "x").endOf("day").format("x")) / (1000 * 60 * 60 * 24)) + 1;
if (this.config.sliceMultiDayEvents && maxCount > 1) {
var splitEvents = [];
var midnight = moment(event.startDate, "x").clone().startOf("day").add(1, "day").format("x");
var count = 1;
while (event.endDate > midnight) {
var thisEvent = JSON.parse(JSON.stringify(event)); // clone object
thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < (today + 24 * 60 * 60 * 1000);
thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < today + 24 * 60 * 60 * 1000;
thisEvent.endDate = midnight;
thisEvent.title += " (" + count + "/" + maxCount + ")";
splitEvents.push(thisEvent);
@@ -504,11 +499,11 @@ Module.register("calendar", {
midnight = moment(midnight, "x").add(1, "day").format("x"); // next day
}
// Last day
event.title += " ("+count+"/"+maxCount+")";
event.title += " (" + count + "/" + maxCount + ")";
splitEvents.push(event);
for (event of splitEvents) {
if ((event.endDate > now) && (event.endDate <= future)) {
if (event.endDate > now && event.endDate <= future) {
events.push(event);
}
}
@@ -524,9 +519,9 @@ Module.register("calendar", {
return events.slice(0, this.config.maximumEntries);
},
listContainsEvent: function(eventList, event){
for(var evt of eventList){
if(evt.title === event.title && parseInt(evt.startDate) === parseInt(event.startDate)){
listContainsEvent: function (eventList, event) {
for (var evt of eventList) {
if (evt.title === event.title && parseInt(evt.startDate) === parseInt(event.startDate)) {
return true;
}
}
@@ -549,7 +544,7 @@ Module.register("calendar", {
titleClass: calendarConfig.titleClass,
timeClass: calendarConfig.timeClass,
auth: auth,
broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents,
broadcastPastEvents: calendarConfig.broadcastPastEvents || this.config.broadcastPastEvents
});
},
@@ -676,8 +671,9 @@ Module.register("calendar", {
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 + " ");
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) {
@@ -688,9 +684,9 @@ Module.register("calendar", {
}
if (currentLine.length > 0) {
temp += (currentLine + "<br>" + word + " ");
temp += currentLine + "<br>" + word + " ";
} else {
temp += (word + "<br>");
temp += word + "<br>";
}
currentLine = "";
}
@@ -758,11 +754,10 @@ Module.register("calendar", {
}
}
eventList.sort(function(a,b) {
eventList.sort(function (a, b) {
return a.startDate - b.startDate;
});
this.sendNotification("CALENDAR_EVENTS", eventList);
}
});

View File

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

View File

@@ -24,12 +24,12 @@ console.log("Create fetcher ...");
var fetcher = new CalendarFetcher(url, fetchInterval, [], maximumEntries, maximumNumberOfDays, auth);
fetcher.onReceive(function(fetcher) {
fetcher.onReceive(function (fetcher) {
console.log(fetcher.events());
console.log("------------------------------------------------------------");
});
fetcher.onError(function(fetcher, error) {
fetcher.onError(function (fetcher, error) {
console.log("Fetcher error:");
console.log(error);
});

View File

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

View File

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

View File

@@ -6,7 +6,7 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
Module.register("clock",{
Module.register("clock", {
// Module config defaults.
defaults: {
displayType: "digital", // options: digital, analog, both
@@ -31,18 +31,18 @@ Module.register("clock",{
showSunTimes: false,
showMoonTimes: false,
lat: 47.630539,
lon: -122.344147,
lon: -122.344147
},
// Define required scripts.
getScripts: function() {
getScripts: function () {
return ["moment.js", "moment-timezone.js", "suncalc.js"];
},
// Define styles.
getStyles: function() {
getStyles: function () {
return ["clock_styles.css"];
},
// Define start sequence.
start: function() {
start: function () {
Log.info("Starting module: " + this.name);
// Schedule update interval.
@@ -51,16 +51,16 @@ Module.register("clock",{
self.minute = moment().minute();
//Calculate how many ms should pass until next update depending on if seconds is displayed or not
var delayCalculator = function(reducedSeconds) {
var delayCalculator = function (reducedSeconds) {
if (self.config.displaySeconds) {
return 1000 - moment().milliseconds();
} else {
return ((60 - reducedSeconds) * 1000) - moment().milliseconds();
return (60 - reducedSeconds) * 1000 - moment().milliseconds();
}
};
//A recursive timeout function instead of interval to avoid drifting
var notificationTimer = function() {
var notificationTimer = function () {
self.updateDom();
//If seconds is displayed CLOCK_SECOND-notification should be sent (but not when CLOCK_MINUTE-notification is sent)
@@ -84,11 +84,9 @@ Module.register("clock",{
// Set locale.
moment.locale(config.language);
},
// Override dom generator.
getDom: function() {
getDom: function () {
var wrapper = document.createElement("div");
/************************************
@@ -127,12 +125,12 @@ Module.register("clock",{
}
if (this.config.clockBold === true) {
timeString = now.format(hourSymbol + "[<span class=\"bold\">]mm[</span>]");
timeString = now.format(hourSymbol + '[<span class="bold">]mm[</span>]');
} else {
timeString = now.format(hourSymbol + ":mm");
}
if(this.config.showDate){
if (this.config.showDate) {
dateWrapper.innerHTML = now.format(this.config.dateFormat);
}
if (this.config.showWeek) {
@@ -173,9 +171,18 @@ Module.register("clock",{
}
const untilNextEvent = moment.duration(moment(nextEvent).diff(now));
const untilNextEventString = untilNextEvent.hours() + "h " + untilNextEvent.minutes() + "m";
sunWrapper.innerHTML = "<span class=\"" + (isVisible ? "bright" : "") + "\"><i class=\"fa fa-sun-o\" aria-hidden=\"true\"></i> " + untilNextEventString + "</span>" +
"<span><i class=\"fa fa-arrow-up\" aria-hidden=\"true\"></i>" + formatTime(this.config, sunTimes.sunrise) + "</span>" +
"<span><i class=\"fa fa-arrow-down\" aria-hidden=\"true\"></i>" + formatTime(this.config, sunTimes.sunset) + "</span>";
sunWrapper.innerHTML =
'<span class="' +
(isVisible ? "bright" : "") +
'"><i class="fa fa-sun-o" aria-hidden="true"></i> ' +
untilNextEventString +
"</span>" +
'<span><i class="fa fa-arrow-up" aria-hidden="true"></i>' +
formatTime(this.config, sunTimes.sunrise) +
"</span>" +
'<span><i class="fa fa-arrow-down" aria-hidden="true"></i>' +
formatTime(this.config, sunTimes.sunset) +
"</span>";
}
if (this.config.showMoonTimes) {
const moonIllumination = SunCalc.getMoonIllumination(now.toDate());
@@ -190,9 +197,18 @@ Module.register("clock",{
}
const isVisible = now.isBetween(moonRise, moonSet) || moonTimes.alwaysUp === true;
const illuminatedFractionString = Math.round(moonIllumination.fraction * 100) + "%";
moonWrapper.innerHTML = "<span class=\"" + (isVisible ? "bright" : "") + "\"><i class=\"fa fa-moon-o\" aria-hidden=\"true\"></i> " + illuminatedFractionString + "</span>" +
"<span><i class=\"fa fa-arrow-up\" aria-hidden=\"true\"></i> " + (moonRise ? formatTime(this.config, moonRise) : "...") + "</span>"+
"<span><i class=\"fa fa-arrow-down\" aria-hidden=\"true\"></i> " + (moonSet ? formatTime(this.config, moonSet) : "...") + "</span>";
moonWrapper.innerHTML =
'<span class="' +
(isVisible ? "bright" : "") +
'"><i class="fa fa-moon-o" aria-hidden="true"></i> ' +
illuminatedFractionString +
"</span>" +
'<span><i class="fa fa-arrow-up" aria-hidden="true"></i> ' +
(moonRise ? formatTime(this.config, moonRise) : "...") +
"</span>" +
'<span><i class="fa fa-arrow-down" aria-hidden="true"></i> ' +
(moonSet ? formatTime(this.config, moonSet) : "...") +
"</span>";
}
/****************************************************************
@@ -206,7 +222,7 @@ Module.register("clock",{
if (this.config.timezone) {
now.tz(this.config.timezone);
}
var second = now.seconds() * 6,
var second = now.seconds() * 6,
minute = now.minute() * 6 + second / 60,
hour = ((now.hours() % 12) / 12) * 360 + 90 + minute / 12;
@@ -217,13 +233,12 @@ Module.register("clock",{
clockCircle.style.height = this.config.analogSize;
if (this.config.analogFace !== "" && this.config.analogFace !== "simple" && this.config.analogFace !== "none") {
clockCircle.style.background = "url("+ this.data.path + "faces/" + this.config.analogFace + ".svg)";
clockCircle.style.background = "url(" + this.data.path + "faces/" + this.config.analogFace + ".svg)";
clockCircle.style.backgroundSize = "100%";
// The following line solves issue: https://github.com/MichMich/MagicMirror/issues/611
// 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";
}
@@ -303,9 +318,9 @@ Module.register("clock",{
digitalWrapper.appendChild(moonWrapper);
digitalWrapper.appendChild(weekWrapper);
var appendClocks = function(condition, pos1, pos2) {
var padding = [0,0,0,0];
padding[(placement === condition) ? pos1 : pos2] = "20px";
var appendClocks = function (condition, pos1, pos2) {
var padding = [0, 0, 0, 0];
padding[placement === condition ? pos1 : pos2] = "20px";
analogWrapper.style.padding = padding.join(" ");
if (placement === condition) {
wrapper.appendChild(analogWrapper);

View File

@@ -1,72 +1,72 @@
.clockCircle {
margin: 0 auto;
position: relative;
border-radius: 50%;
background-size: 100%;
}
.clockFace {
width: 100%;
height: 100%;
}
.clockFace::after {
position: absolute;
top: 50%;
left: 50%;
width: 6px;
height: 6px;
margin: -3px 0 0 -3px;
background: white;
border-radius: 3px;
content: "";
display: block;
}
.clockHour {
width: 0;
height: 0;
position: absolute;
top: 50%;
left: 50%;
margin: -2px 0 -2px -25%; /* numbers much match negative length & thickness */
padding: 2px 0 2px 25%; /* indicator length & thickness */
background: white;
transform-origin: 100% 50%;
border-radius: 3px 0 0 3px;
}
.clockMinute {
width: 0;
height: 0;
position: absolute;
top: 50%;
left: 50%;
margin: -35% -2px 0; /* numbers must match negative length & thickness */
padding: 35% 2px 0; /* indicator length & thickness */
background: white;
transform-origin: 50% 100%;
border-radius: 3px 0 0 3px;
}
.clockSecond {
width: 0;
height: 0;
position: absolute;
top: 50%;
left: 50%;
margin: -38% -1px 0 0; /* numbers must match negative length & thickness */
padding: 38% 1px 0 0; /* indicator length & thickness */
background: #888;
transform-origin: 50% 100%;
}
.module.clock .sun,
.module.clock .moon {
display: flex;
}
.module.clock .sun > *,
.module.clock .moon > * {
flex: 1;
}
.clockCircle {
margin: 0 auto;
position: relative;
border-radius: 50%;
background-size: 100%;
}
.clockFace {
width: 100%;
height: 100%;
}
.clockFace::after {
position: absolute;
top: 50%;
left: 50%;
width: 6px;
height: 6px;
margin: -3px 0 0 -3px;
background: white;
border-radius: 3px;
content: "";
display: block;
}
.clockHour {
width: 0;
height: 0;
position: absolute;
top: 50%;
left: 50%;
margin: -2px 0 -2px -25%; /* numbers much match negative length & thickness */
padding: 2px 0 2px 25%; /* indicator length & thickness */
background: white;
transform-origin: 100% 50%;
border-radius: 3px 0 0 3px;
}
.clockMinute {
width: 0;
height: 0;
position: absolute;
top: 50%;
left: 50%;
margin: -35% -2px 0; /* numbers must match negative length & thickness */
padding: 35% 2px 0; /* indicator length & thickness */
background: white;
transform-origin: 50% 100%;
border-radius: 3px 0 0 3px;
}
.clockSecond {
width: 0;
height: 0;
position: absolute;
top: 50%;
left: 50%;
margin: -38% -1px 0 0; /* numbers must match negative length & thickness */
padding: 38% 1px 0 0; /* indicator length & thickness */
background: #888;
transform-origin: 50% 100%;
}
.module.clock .sun,
.module.clock .moon {
display: flex;
}
.module.clock .sun > *,
.module.clock .moon > * {
flex: 1;
}

View File

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

View File

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

View File

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

View File

@@ -4,8 +4,7 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
Module.register("currentweather",{
Module.register("currentweather", {
// Default module config.
defaults: {
location: false,
@@ -63,7 +62,7 @@ Module.register("currentweather",{
"11n": "wi-night-thunderstorm",
"13n": "wi-night-snow",
"50n": "wi-night-alt-cloudy-windy"
},
}
},
// create a variable for the first upcoming calendar event. Used if no location is specified.
@@ -73,17 +72,17 @@ Module.register("currentweather",{
fetchedLocationName: "",
// Define required scripts.
getScripts: function() {
getScripts: function () {
return ["moment.js"];
},
// Define required scripts.
getStyles: function() {
getStyles: function () {
return ["weather-icons.css", "currentweather.css"];
},
// Define required translations.
getTranslations: function() {
getTranslations: function () {
// The translations for the default modules are defined in the core translation files.
// Therefor we can just return false. Otherwise we should have returned a dictionary.
// If you're trying to build your own module including translations, check out the documentation.
@@ -91,7 +90,7 @@ Module.register("currentweather",{
},
// Define start sequence.
start: function() {
start: function () {
Log.info("Starting module: " + this.name);
// Set locale.
@@ -109,13 +108,11 @@ Module.register("currentweather",{
this.feelsLike = null;
this.loaded = false;
this.scheduleUpdate(this.config.initialLoadDelay);
},
// add extra information of current weather
// windDirection, humidity, sunrise and sunset
addExtraInfoWeather: function(wrapper) {
addExtraInfoWeather: function (wrapper) {
var small = document.createElement("div");
small.className = "normal medium";
@@ -130,8 +127,8 @@ Module.register("currentweather",{
if (this.config.showWindDirection) {
var windDirection = document.createElement("sup");
if (this.config.showWindDirectionAsArrow) {
if(this.windDeg !== null) {
windDirection.innerHTML = " &nbsp;<i class=\"fa fa-long-arrow-down\" style=\"transform:rotate("+this.windDeg+"deg);\"></i>&nbsp;";
if (this.windDeg !== null) {
windDirection.innerHTML = ' &nbsp;<i class="fa fa-long-arrow-down" style="transform:rotate(' + this.windDeg + 'deg);"></i>&nbsp;';
}
} else {
windDirection.innerHTML = " " + this.translate(this.windDirection);
@@ -170,7 +167,7 @@ Module.register("currentweather",{
},
// Override dom generator.
getDom: function() {
getDom: function () {
var wrapper = document.createElement("div");
wrapper.className = this.config.tableClass;
@@ -197,17 +194,17 @@ Module.register("currentweather",{
if (this.config.units === "metric" || this.config.units === "imperial") {
degreeLabel += "°";
}
if(this.config.degreeLabel) {
switch(this.config.units) {
case "metric":
degreeLabel += "C";
break;
case "imperial":
degreeLabel += "F";
break;
case "default":
degreeLabel += "K";
break;
if (this.config.degreeLabel) {
switch (this.config.units) {
case "metric":
degreeLabel += "C";
break;
case "imperial":
degreeLabel += "F";
break;
case "default":
degreeLabel += "K";
break;
}
}
@@ -250,7 +247,7 @@ Module.register("currentweather",{
wrapper.appendChild(large);
if (this.config.showFeelsLike && this.config.onlyTemp === false){
if (this.config.showFeelsLike && this.config.onlyTemp === false) {
var small = document.createElement("div");
small.className = "normal medium";
@@ -266,7 +263,7 @@ Module.register("currentweather",{
},
// Override getHeader method.
getHeader: function() {
getHeader: function () {
if (this.config.appendLocationNameToHeader && this.data.header !== undefined) {
return this.data.header + " " + this.fetchedLocationName;
}
@@ -279,10 +276,10 @@ Module.register("currentweather",{
},
// Override notification handler.
notificationReceived: function(notification, payload, sender) {
notificationReceived: function (notification, payload, sender) {
if (notification === "DOM_OBJECTS_CREATED") {
if (this.config.appendLocationNameToHeader) {
this.hide(0, {lockString: this.identifier});
this.hide(0, { lockString: this.identifier });
}
}
if (notification === "CALENDAR_EVENTS") {
@@ -314,7 +311,7 @@ Module.register("currentweather",{
* Requests new data from openweather.org.
* Calls processWeather on succesfull response.
*/
updateWeather: function() {
updateWeather: function () {
if (this.config.appid === "") {
Log.error("CurrentWeather: APPID not set!");
return;
@@ -326,7 +323,7 @@ Module.register("currentweather",{
var weatherRequest = new XMLHttpRequest();
weatherRequest.open("GET", url, true);
weatherRequest.onreadystatechange = function() {
weatherRequest.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status === 200) {
self.processWeather(JSON.parse(this.response));
@@ -340,7 +337,7 @@ Module.register("currentweather",{
}
if (retry) {
self.scheduleUpdate((self.loaded) ? -1 : self.config.retryDelay);
self.scheduleUpdate(self.loaded ? -1 : self.config.retryDelay);
}
}
};
@@ -352,18 +349,18 @@ Module.register("currentweather",{
*
* return String - URL params.
*/
getParams: function() {
getParams: function () {
var params = "?";
if(this.config.locationID) {
if (this.config.locationID) {
params += "id=" + this.config.locationID;
} else if(this.config.location) {
} else if (this.config.location) {
params += "q=" + this.config.location;
} else if (this.firstEvent && this.firstEvent.geo) {
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
} else if (this.firstEvent && this.firstEvent.location) {
params += "q=" + this.firstEvent.location;
} else {
this.hide(this.config.animationSpeed, {lockString:this.identifier});
this.hide(this.config.animationSpeed, { lockString: this.identifier });
return;
}
@@ -379,8 +376,7 @@ Module.register("currentweather",{
*
* argument data object - Weather information received form openweather.org.
*/
processWeather: function(data) {
processWeather: function (data) {
if (!data || !data.main || typeof data.main.temp === "undefined") {
// Did not receive usable new data.
// Maybe this needs a better check?
@@ -392,7 +388,7 @@ Module.register("currentweather",{
this.fetchedLocationName = data.name;
this.feelsLike = 0;
if (this.config.useBeaufort){
if (this.config.useBeaufort) {
this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed));
} else if (this.config.useKMPHwind) {
this.windSpeed = parseFloat((data.wind.speed * 60 * 60) / 1000).toFixed(0);
@@ -404,50 +400,59 @@ Module.register("currentweather",{
var windInMph = parseFloat(data.wind.speed * 2.23694);
var tempInF = 0;
switch (this.config.units){
case "metric": tempInF = 1.8 * this.temperature + 32;
break;
case "imperial": tempInF = this.temperature;
break;
case "default":
tempInF = 1.8 * (this.temperature - 273.15) + 32;
break;
switch (this.config.units) {
case "metric":
tempInF = 1.8 * this.temperature + 32;
break;
case "imperial":
tempInF = this.temperature;
break;
case "default":
tempInF = 1.8 * (this.temperature - 273.15) + 32;
break;
}
if (windInMph > 3 && tempInF < 50){
if (windInMph > 3 && tempInF < 50) {
// windchill
var windChillInF = Math.round(35.74+0.6215*tempInF-35.75*Math.pow(windInMph,0.16)+0.4275*tempInF*Math.pow(windInMph,0.16));
var windChillInC = (windChillInF - 32) * (5/9);
var windChillInF = Math.round(35.74 + 0.6215 * tempInF - 35.75 * Math.pow(windInMph, 0.16) + 0.4275 * tempInF * Math.pow(windInMph, 0.16));
var windChillInC = (windChillInF - 32) * (5 / 9);
// this.feelsLike = windChillInC.toFixed(0);
switch (this.config.units){
case "metric": this.feelsLike = windChillInC.toFixed(0);
break;
case "imperial": this.feelsLike = windChillInF.toFixed(0);
break;
case "default":
this.feelsLike = (windChillInC + 273.15).toFixed(0);
break;
switch (this.config.units) {
case "metric":
this.feelsLike = windChillInC.toFixed(0);
break;
case "imperial":
this.feelsLike = windChillInF.toFixed(0);
break;
case "default":
this.feelsLike = (windChillInC + 273.15).toFixed(0);
break;
}
} else if (tempInF > 80 && this.humidity > 40){
} else if (tempInF > 80 && this.humidity > 40) {
// heat index
var Hindex = -42.379 + 2.04901523*tempInF + 10.14333127*this.humidity
- 0.22475541*tempInF*this.humidity - 6.83783*Math.pow(10,-3)*tempInF*tempInF
- 5.481717*Math.pow(10,-2)*this.humidity*this.humidity
+ 1.22874*Math.pow(10,-3)*tempInF*tempInF*this.humidity
+ 8.5282*Math.pow(10,-4)*tempInF*this.humidity*this.humidity
- 1.99*Math.pow(10,-6)*tempInF*tempInF*this.humidity*this.humidity;
var Hindex =
-42.379 +
2.04901523 * tempInF +
10.14333127 * this.humidity -
0.22475541 * tempInF * this.humidity -
6.83783 * Math.pow(10, -3) * tempInF * tempInF -
5.481717 * Math.pow(10, -2) * this.humidity * this.humidity +
1.22874 * Math.pow(10, -3) * tempInF * tempInF * this.humidity +
8.5282 * Math.pow(10, -4) * tempInF * this.humidity * this.humidity -
1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
switch (this.config.units){
case "metric": this.feelsLike = parseFloat((Hindex - 32) / 1.8).toFixed(0);
break;
case "imperial": this.feelsLike = Hindex.toFixed(0);
break;
case "default":
var tc = parseFloat((Hindex - 32) / 1.8) + 273.15;
this.feelsLike = tc.toFixed(0);
break;
switch (this.config.units) {
case "metric":
this.feelsLike = parseFloat((Hindex - 32) / 1.8).toFixed(0);
break;
case "imperial":
this.feelsLike = Hindex.toFixed(0);
break;
case "default":
var tc = parseFloat((Hindex - 32) / 1.8) + 273.15;
this.feelsLike = tc.toFixed(0);
break;
}
} else {
this.feelsLike = parseFloat(this.temperature).toFixed(0);
@@ -464,7 +469,7 @@ Module.register("currentweather",{
// 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 sunriseSunsetDateObject = (sunrise < now && sunset > now) ? sunset : sunrise;
var sunriseSunsetDateObject = sunrise < now && sunset > now ? sunset : sunrise;
var timeString = moment(sunriseSunsetDateObject).format("HH:mm");
if (this.config.timeFormat !== 24) {
//var hours = sunriseSunsetDateObject.getHours() % 12 || 12;
@@ -483,12 +488,12 @@ Module.register("currentweather",{
}
this.sunriseSunsetTime = timeString;
this.sunriseSunsetIcon = (sunrise < now && sunset > now) ? "wi-sunset" : "wi-sunrise";
this.sunriseSunsetIcon = sunrise < now && sunset > now ? "wi-sunset" : "wi-sunrise";
this.show(this.config.animationSpeed, {lockString:this.identifier});
this.show(this.config.animationSpeed, { lockString: this.identifier });
this.loaded = true;
this.updateDom(this.config.animationSpeed);
this.sendNotification("CURRENTWEATHER_DATA", {data: data});
this.sendNotification("CURRENTWEATHER_DATA", { data: data });
},
/* scheduleUpdate()
@@ -496,14 +501,14 @@ Module.register("currentweather",{
*
* argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
*/
scheduleUpdate: function(delay) {
scheduleUpdate: function (delay) {
var nextLoad = this.config.updateInterval;
if (typeof delay !== "undefined" && delay >= 0) {
nextLoad = delay;
}
var self = this;
setTimeout(function() {
setTimeout(function () {
self.updateWeather();
}, nextLoad);
},
@@ -519,8 +524,8 @@ Module.register("currentweather",{
*
* return number - Windspeed in beaufort.
*/
ms2Beaufort: function(ms) {
var kmh = ms * 60 * 60 / 1000;
ms2Beaufort: function (ms) {
var kmh = (ms * 60 * 60) / 1000;
var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
for (var beaufort in speeds) {
var speed = speeds[beaufort];
@@ -531,8 +536,8 @@ Module.register("currentweather",{
return 12;
},
deg2Cardinal: function(deg) {
if (deg>11.25 && deg<=33.75){
deg2Cardinal: function (deg) {
if (deg > 11.25 && deg <= 33.75) {
return "NNE";
} else if (deg > 33.75 && deg <= 56.25) {
return "NE";
@@ -574,9 +579,8 @@ Module.register("currentweather",{
*
* return string - Rounded Temperature.
*/
roundValue: function(temperature) {
roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals);
}
});

View File

@@ -7,18 +7,9 @@
// Modules listed below can be loaded without the 'default/' prefix. Omitting the default folder name.
var defaultModules = [
"alert",
"calendar",
"clock",
"compliments",
"currentweather",
"helloworld",
"newsfeed",
"weatherforecast",
"updatenotification",
"weather"
];
var defaultModules = ["alert", "calendar", "clock", "compliments", "currentweather", "helloworld", "newsfeed", "weatherforecast", "updatenotification", "weather"];
/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {module.exports = defaultModules;}
if (typeof module !== "undefined") {
module.exports = defaultModules;
}

View File

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

View File

@@ -4,8 +4,7 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
Module.register("helloworld",{
Module.register("helloworld", {
// Default module config.
defaults: {
text: "Hello World!"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,6 @@
* MIT Licensed.
*/
Module.register("updatenotification", {
defaults: {
updateInterval: 10 * 60 * 1000, // every 10 minutes
refreshInterval: 24 * 60 * 60 * 1000, // one day
@@ -18,7 +17,10 @@ Module.register("updatenotification", {
start: function () {
var self = this;
Log.log("Start updatenotification");
setInterval( () => { self.moduleList = {};self.updateDom(2); } , self.config.refreshInterval);
setInterval(() => {
self.moduleList = {};
self.updateDom(2);
}, self.config.refreshInterval);
},
notificationReceived: function (notification, payload, sender) {
@@ -39,16 +41,15 @@ Module.register("updatenotification", {
var self = this;
if (payload && payload.behind > 0) {
// if we haven't seen info for this module
if(this.moduleList[payload.module] === undefined){
if (this.moduleList[payload.module] === undefined) {
// save it
this.moduleList[payload.module]=payload;
this.moduleList[payload.module] = payload;
self.updateDom(2);
}
//self.show(1000, { lockString: self.identifier });
} else if (payload && payload.behind === 0){
} else if (payload && payload.behind === 0) {
// if the module WAS in the list, but shouldn't be
if(this.moduleList[payload.module] !== undefined){
if (this.moduleList[payload.module] !== undefined) {
// remove it
delete this.moduleList[payload.module];
self.updateDom(2);
@@ -56,23 +57,18 @@ Module.register("updatenotification", {
}
},
diffLink: function(module, text) {
diffLink: function (module, text) {
var localRef = module.hash;
var remoteRef = module.tracking.replace(/.*\//, "");
return "<a href=\"https://github.com/MichMich/MagicMirror/compare/"+localRef+"..."+remoteRef+"\" "+
"class=\"xsmall dimmed\" "+
"style=\"text-decoration: none;\" "+
"target=\"_blank\" >" +
text +
"</a>";
return '<a href="https://github.com/MichMich/MagicMirror/compare/' + localRef + "..." + remoteRef + '" ' + 'class="xsmall dimmed" ' + 'style="text-decoration: none;" ' + 'target="_blank" >' + text + "</a>";
},
// Override dom generator.
getDom: function () {
var wrapper = document.createElement("div");
if(this.suspended === false){
if (this.suspended === false) {
// process the hash of module info found
for(var key of Object.keys(this.moduleList)){
for (var key of Object.keys(this.moduleList)) {
let m = this.moduleList[key];
var message = document.createElement("div");
@@ -93,7 +89,7 @@ Module.register("updatenotification", {
var text = document.createElement("span");
if (m.module === "default") {
text.innerHTML = this.translate("UPDATE_NOTIFICATION");
subtextHtml = this.diffLink(m,subtextHtml);
subtextHtml = this.diffLink(m, subtextHtml);
} else {
text.innerHTML = this.translate("UPDATE_NOTIFICATION_MODULE", {
MODULE_NAME: m.module
@@ -112,11 +108,11 @@ Module.register("updatenotification", {
return wrapper;
},
suspend: function() {
this.suspended=true;
suspend: function () {
this.suspended = true;
},
resume: function() {
this.suspended=false;
resume: function () {
this.suspended = false;
this.updateDom(2);
}
});

View File

@@ -15,15 +15,15 @@ Table of Contents:
This is the script in which the weather provider will be defined. In its most simple form, the weather provider must implement the following:
````javascript
```javascript
WeatherProvider.register("yourprovider", {
providerName: "YourProvider",
providerName: "YourProvider",
fetchCurrentWeather() {},
fetchCurrentWeather() {},
fetchWeatherForecast() {}
fetchWeatherForecast() {}
});
````
```
### Weather provider methods to implement
@@ -89,24 +89,24 @@ A convenience function to make requests. It returns a promise.
### WeatherObject
| Property | Type | Value/Unit |
| --- | --- | --- |
| units | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| tempUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| windUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| date | `object` | [Moment.js](https://momentjs.com/) object of the time/date. |
| windSpeed |`number` | Metric: `meter/second` <br> Imperial: `miles/hour` |
| windDirection |`number` | Direction of the wind in degrees. |
| sunrise |`object` | [Moment.js](https://momentjs.com/) object of sunrise. |
| sunset |`object` | [Moment.js](https://momentjs.com/) object of sunset. |
| temperature | `number` | Current temperature |
| minTemperature | `number` | Lowest temperature of the day. |
| maxTemperature | `number` | Highest temperature of the day. |
| weatherType | `string` | Icon name of the weather type. <br> Possible values: [WeatherIcons](https://www.npmjs.com/package/weathericons) |
| 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` <br> UK Met Office provider: `percent` |
| Property | Type | Value/Unit |
| -------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
| units | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| tempUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| windUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| date | `object` | [Moment.js](https://momentjs.com/) object of the time/date. |
| windSpeed | `number` | Metric: `meter/second` <br> Imperial: `miles/hour` |
| windDirection | `number` | Direction of the wind in degrees. |
| sunrise | `object` | [Moment.js](https://momentjs.com/) object of sunrise. |
| sunset | `object` | [Moment.js](https://momentjs.com/) object of sunset. |
| temperature | `number` | Current temperature |
| minTemperature | `number` | Lowest temperature of the day. |
| maxTemperature | `number` | Highest temperature of the day. |
| weatherType | `string` | Icon name of the weather type. <br> Possible values: [WeatherIcons](https://www.npmjs.com/package/weathericons) |
| 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` <br> UK Met Office provider: `percent` |
#### Current weather

View File

@@ -22,15 +22,16 @@ WeatherProvider.register("darksky", {
fetchCurrentWeather() {
this.fetchData(this.getUrl())
.then(data => {
if(!data || !data.currently || typeof data.currently.temperature === "undefined") {
.then((data) => {
if (!data || !data.currently || typeof data.currently.temperature === "undefined") {
// No usable data?
return;
}
const currentWeather = this.generateWeatherDayFromCurrentWeather(data);
this.setCurrentWeather(currentWeather);
}).catch(function(request) {
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable());
@@ -38,15 +39,16 @@ WeatherProvider.register("darksky", {
fetchWeatherForecast() {
this.fetchData(this.getUrl())
.then(data => {
if(!data || !data.daily || !data.daily.data.length) {
.then((data) => {
if (!data || !data.daily || !data.daily.data.length) {
// No usable data?
return;
}
const forecast = this.generateWeatherObjectsFromForecast(data.daily.data);
this.setWeatherForecast(forecast);
}).catch(function(request) {
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable());
@@ -109,12 +111,12 @@ WeatherProvider.register("darksky", {
const weatherTypes = {
"clear-day": "day-sunny",
"clear-night": "night-clear",
"rain": "rain",
"snow": "snow",
"sleet": "snow",
"wind": "wind",
"fog": "fog",
"cloudy": "cloudy",
rain: "rain",
snow: "snow",
sleet: "snow",
wind: "wind",
fog: "fog",
cloudy: "cloudy",
"partly-cloudy-day": "day-cloudy",
"partly-cloudy-night": "night-cloudy"
};

View File

@@ -9,7 +9,6 @@
* This class is the blueprint for a weather provider.
*/
WeatherProvider.register("openweathermap", {
// 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.
@@ -18,7 +17,7 @@ WeatherProvider.register("openweathermap", {
// Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() {
this.fetchData(this.getUrl())
.then(data => {
.then((data) => {
if (!data || !data.main || typeof data.main.temp === "undefined") {
// Did not receive usable new data.
// Maybe this needs a better check?
@@ -30,7 +29,7 @@ WeatherProvider.register("openweathermap", {
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
this.setCurrentWeather(currentWeather);
})
.catch(function(request) {
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable());
@@ -39,7 +38,7 @@ WeatherProvider.register("openweathermap", {
// Overwrite the fetchCurrentWeather method.
fetchWeatherForecast() {
this.fetchData(this.getUrl())
.then(data => {
.then((data) => {
if (!data || !data.list || !data.list.length) {
// Did not receive usable new data.
// Maybe this needs a better check?
@@ -51,7 +50,7 @@ WeatherProvider.register("openweathermap", {
const forecast = this.generateWeatherObjectsFromForecast(data.list);
this.setWeatherForecast(forecast);
})
.catch(function(request) {
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable());
@@ -86,7 +85,6 @@ 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") {
@@ -113,7 +111,6 @@ WeatherProvider.register("openweathermap", {
let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
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);
@@ -139,7 +136,6 @@ WeatherProvider.register("openweathermap", {
// 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) {
@@ -260,16 +256,16 @@ WeatherProvider.register("openweathermap", {
*/
getParams() {
let params = "?";
if(this.config.locationID) {
if (this.config.locationID) {
params += "id=" + this.config.locationID;
} else if(this.config.location) {
} else if (this.config.location) {
params += "q=" + this.config.location;
} else if (this.firstEvent && this.firstEvent.geo) {
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
} else if (this.firstEvent && this.firstEvent.location) {
params += "q=" + this.firstEvent.location;
} else {
this.hide(this.config.animationSpeed, {lockString:this.identifier});
this.hide(this.config.animationSpeed, { lockString: this.identifier });
return;
}

View File

@@ -9,7 +9,6 @@
* This class is a provider for UK Met Office Datapoint.
*/
WeatherProvider.register("ukmetoffice", {
// 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.
@@ -23,7 +22,7 @@ WeatherProvider.register("ukmetoffice", {
// Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() {
this.fetchData(this.getUrl("3hourly"))
.then(data => {
.then((data) => {
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location || !data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length === 0) {
// Did not receive usable new data.
// Maybe this needs a better check?
@@ -35,7 +34,7 @@ WeatherProvider.register("ukmetoffice", {
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
this.setCurrentWeather(currentWeather);
})
.catch(function(request) {
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable());
@@ -44,7 +43,7 @@ WeatherProvider.register("ukmetoffice", {
// Overwrite the fetchCurrentWeather method.
fetchWeatherForecast() {
this.fetchData(this.getUrl("daily"))
.then(data => {
.then((data) => {
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location || !data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length === 0) {
// Did not receive usable new data.
// Maybe this needs a better check?
@@ -56,7 +55,7 @@ WeatherProvider.register("ukmetoffice", {
const forecast = this.generateWeatherObjectsFromForecast(data);
this.setWeatherForecast(forecast);
})
.catch(function(request) {
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable());
@@ -83,18 +82,17 @@ WeatherProvider.register("ukmetoffice", {
// loop round each of the (5) periods, look for today (the first period may be yesterday)
for (var i in currentWeatherData.SiteRep.DV.Location.Period) {
let periodDate = moment.utc(currentWeatherData.SiteRep.DV.Location.Period[i].value.substr(0,10), "YYYY-MM-DD");
let periodDate = moment.utc(currentWeatherData.SiteRep.DV.Location.Period[i].value.substr(0, 10), "YYYY-MM-DD");
// ignore if period is before today
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
// check this is the period we want, after today the diff will be -ve
if (moment().diff(periodDate, "minutes") > 0) {
// loop round the reports looking for the one we are in
// $ value specifies the time in minutes-of-the-day: 0, 180, 360,...1260
for (var j in currentWeatherData.SiteRep.DV.Location.Period[i].Rep){
for (var j in currentWeatherData.SiteRep.DV.Location.Period[i].Rep) {
let p = currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].$;
if (timeInMins >= p && timeInMins-180 < p) {
if (timeInMins >= p && timeInMins - 180 < p) {
// finally got the one we want, so populate weather object
currentWeather.humidity = currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].H;
currentWeather.temperature = this.convertTemp(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].T);
@@ -121,7 +119,6 @@ WeatherProvider.register("ukmetoffice", {
* Generate WeatherObjects based on forecast information
*/
generateWeatherObjectsFromForecast(forecasts) {
const days = [];
// loop round the (5) periods getting the data
@@ -131,12 +128,12 @@ WeatherProvider.register("ukmetoffice", {
// data times are always UTC
const dateStr = forecasts.SiteRep.DV.Location.Period[j].value;
let periodDate = moment.utc(dateStr.substr(0,10), "YYYY-MM-DD");
let periodDate = moment.utc(dateStr.substr(0, 10), "YYYY-MM-DD");
// ignore if period is before today
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
// populate the weather object
weather.date = moment.utc(dateStr.substr(0,10), "YYYY-MM-DD");
weather.date = moment.utc(dateStr.substr(0, 10), "YYYY-MM-DD");
weather.minTemperature = this.convertTemp(forecasts.SiteRep.DV.Location.Period[j].Rep[1].Nm);
weather.maxTemperature = this.convertTemp(forecasts.SiteRep.DV.Location.Period[j].Rep[0].Dm);
weather.weatherType = this.convertWeatherType(forecasts.SiteRep.DV.Location.Period[j].Rep[0].W);
@@ -207,7 +204,7 @@ WeatherProvider.register("ukmetoffice", {
* Convert temp (from degrees C) if required
*/
convertTemp(tempInC) {
return this.tempUnits === "imperial" ? tempInC * 9 / 5 + 32 : tempInC;
return this.tempUnits === "imperial" ? (tempInC * 9) / 5 + 32 : tempInC;
},
/*
@@ -222,22 +219,22 @@ WeatherProvider.register("ukmetoffice", {
*/
convertWindDirection(windDirection) {
const windCardinals = {
"N": 0,
"NNE": 22,
"NE": 45,
"ENE": 67,
"E": 90,
"ESE": 112,
"SE": 135,
"SSE": 157,
"S": 180,
"SSW": 202,
"SW": 225,
"WSW": 247,
"W": 270,
"WNW": 292,
"NW": 315,
"NNW": 337
N: 0,
NNE: 22,
NE: 45,
ENE: 67,
E: 90,
ESE: 112,
SE: 135,
SSE: 157,
S: 180,
SSW: 202,
SW: 225,
WSW: 247,
W: 270,
WNW: 292,
NW: 315,
NNW: 337
};
return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null;

View File

@@ -12,7 +12,6 @@
* 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.
@@ -21,7 +20,7 @@ WeatherProvider.register("weathergov", {
// Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() {
this.fetchData(this.getUrl())
.then(data => {
.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?
@@ -31,7 +30,7 @@ WeatherProvider.register("weathergov", {
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data.properties.periods[0]);
this.setCurrentWeather(currentWeather);
})
.catch(function(request) {
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable());
@@ -40,7 +39,7 @@ WeatherProvider.register("weathergov", {
// Overwrite the fetchCurrentWeather method.
fetchWeatherForecast() {
this.fetchData(this.getUrl())
.then(data => {
.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?
@@ -50,7 +49,7 @@ WeatherProvider.register("weathergov", {
const forecast = this.generateWeatherObjectsFromForecast(data.properties.periods);
this.setWeatherForecast(forecast);
})
.catch(function(request) {
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable());
@@ -105,9 +104,7 @@ WeatherProvider.register("weathergov", {
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);
@@ -240,22 +237,22 @@ WeatherProvider.register("weathergov", {
*/
convertWindDirection(windDirection) {
const windCardinals = {
"N": 0,
"NNE": 22,
"NE": 45,
"ENE": 67,
"E": 90,
"ESE": 112,
"SE": 135,
"SSE": 157,
"S": 180,
"SSW": 202,
"SW": 225,
"WSW": 247,
"W": 270,
"WNW": 292,
"NW": 315,
"NNW": 337
N: 0,
NNE: 22,
NE: 45,
ENE: 67,
E: 90,
ESE: 112,
SE: 135,
SSE: 157,
S: 180,
SSW: 202,
SW: 225,
WSW: 247,
W: 270,
WNW: 292,
NW: 315,
NNW: 337
};
return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null;

View File

@@ -6,7 +6,7 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
Module.register("weather",{
Module.register("weather", {
// Default module config.
defaults: {
weatherProvider: "openweathermap",
@@ -60,23 +60,17 @@ Module.register("weather",{
weatherProvider: null,
// Define required scripts.
getStyles: function() {
getStyles: function () {
return ["font-awesome.css", "weather-icons.css", "weather.css"];
},
// Return the scripts that are necessary for the weather module.
getScripts: function () {
return [
"moment.js",
"weatherprovider.js",
"weatherobject.js",
"suncalc.js",
this.file("providers/" + this.config.weatherProvider.toLowerCase() + ".js")
];
return ["moment.js", "weatherprovider.js", "weatherobject.js", "suncalc.js", this.file("providers/" + this.config.weatherProvider.toLowerCase() + ".js")];
},
// Override getHeader method.
getHeader: function() {
getHeader: function () {
if (this.config.appendLocationNameToHeader && this.data.header !== undefined && this.weatherProvider) {
return this.data.header + " " + this.weatherProvider.fetchedLocation();
}
@@ -102,7 +96,7 @@ Module.register("weather",{
},
// Override notification handler.
notificationReceived: function(notification, payload, sender) {
notificationReceived: function (notification, payload, sender) {
if (notification === "CALENDAR_EVENTS") {
var senderClasses = sender.data.classes.toLowerCase().split(" ");
if (senderClasses.indexOf(this.config.calendarClass.toLowerCase()) !== -1) {
@@ -145,13 +139,13 @@ Module.register("weather",{
},
// What to do when the weather provider has new information available?
updateAvailable: function() {
updateAvailable: function () {
Log.log("New weather information available.");
this.updateDom(0);
this.scheduleUpdate();
},
scheduleUpdate: function(delay = null) {
scheduleUpdate: function (delay = null) {
var nextLoad = this.config.updateInterval;
if (delay !== null && delay >= 0) {
nextLoad = delay;
@@ -166,88 +160,106 @@ Module.register("weather",{
}, nextLoad);
},
roundValue: function(temperature) {
roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals);
},
addFilters() {
this.nunjucksEnvironment().addFilter("formatTime", function(date) {
date = moment(date);
this.nunjucksEnvironment().addFilter(
"formatTime",
function (date) {
date = moment(date);
if (this.config.timeFormat !== 24) {
if (this.config.showPeriod) {
if (this.config.showPeriodUpper) {
return date.format("h:mm A");
if (this.config.timeFormat !== 24) {
if (this.config.showPeriod) {
if (this.config.showPeriodUpper) {
return date.format("h:mm A");
} else {
return date.format("h:mm a");
}
} else {
return date.format("h:mm a");
}
} else {
return date.format("h:mm");
}
}
return date.format("HH:mm");
}.bind(this));
this.nunjucksEnvironment().addFilter("unit", function (value, type) {
if (type === "temperature") {
if (this.config.tempUnits === "metric" || this.config.tempUnits === "imperial") {
value += "°";
}
if (this.config.degreeLabel) {
if (this.config.tempUnits === "metric") {
value += "C";
} else if (this.config.tempUnits === "imperial") {
value += "F";
} else {
value += "K";
return date.format("h:mm");
}
}
} else if (type === "precip") {
if (isNaN(value) || value === 0 || value.toFixed(2) === "0.00") {
value = "";
} else {
if (this.config.weatherProvider === "ukmetoffice") {
value += "%";
} else {
value = `${value.toFixed(2)} ${this.config.units === "imperial" ? "in" : "mm"}`;
return date.format("HH:mm");
}.bind(this)
);
this.nunjucksEnvironment().addFilter(
"unit",
function (value, type) {
if (type === "temperature") {
if (this.config.tempUnits === "metric" || this.config.tempUnits === "imperial") {
value += "°";
}
if (this.config.degreeLabel) {
if (this.config.tempUnits === "metric") {
value += "C";
} else if (this.config.tempUnits === "imperial") {
value += "F";
} else {
value += "K";
}
}
} else if (type === "precip") {
if (isNaN(value) || value === 0 || value.toFixed(2) === "0.00") {
value = "";
} else {
if (this.config.weatherProvider === "ukmetoffice") {
value += "%";
} else {
value = `${value.toFixed(2)} ${this.config.units === "imperial" ? "in" : "mm"}`;
}
}
} else if (type === "humidity") {
value += "%";
}
} else if (type === "humidity") {
value += "%";
}
return value;
}.bind(this));
return value;
}.bind(this)
);
this.nunjucksEnvironment().addFilter("roundValue", function(value) {
return this.roundValue(value);
}.bind(this));
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(
"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(
"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;
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;
}
} else {
return 1;
}
}.bind(this));
}.bind(this)
);
}
});

View File

@@ -11,7 +11,6 @@
*/
class WeatherObject {
constructor(units, tempUnits, windUnits) {
this.units = units;
this.tempUnits = tempUnits;
this.windUnits = windUnits;
@@ -29,11 +28,10 @@ class WeatherObject {
this.snow = null;
this.precipitation = null;
this.feelsLikeTemp = null;
}
cardinalWindDirection() {
if (this.windDirection > 11.25 && this.windDirection <= 33.75){
if (this.windDirection > 11.25 && this.windDirection <= 33.75) {
return "NNE";
} else if (this.windDirection > 33.75 && this.windDirection <= 56.25) {
return "NE";
@@ -69,7 +67,7 @@ class WeatherObject {
}
beaufortWindSpeed() {
const windInKmh = (this.windUnits === "imperial") ? this.windSpeed * 1.609344 : this.windSpeed * 60 * 60 / 1000;
const windInKmh = this.windUnits === "imperial" ? this.windSpeed * 1.609344 : (this.windSpeed * 60 * 60) / 1000;
const speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
for (const [index, speed] of speeds.entries()) {
if (speed > windInKmh) {
@@ -87,21 +85,25 @@ class WeatherObject {
if (this.feelsLikeTemp) {
return this.feelsLikeTemp;
}
const windInMph = (this.windUnits === "imperial") ? this.windSpeed : this.windSpeed * 2.23694;
const tempInF = this.tempUnits === "imperial" ? this.temperature : this.temperature * 9 / 5 + 32;
const windInMph = this.windUnits === "imperial" ? this.windSpeed : this.windSpeed * 2.23694;
const tempInF = this.tempUnits === "imperial" ? this.temperature : (this.temperature * 9) / 5 + 32;
let feelsLike = tempInF;
if (windInMph > 3 && tempInF < 50) {
feelsLike = Math.round(35.74 + 0.6215 * tempInF - 35.75 * Math.pow(windInMph, 0.16) + 0.4275 * tempInF * Math.pow(windInMph, 0.16));
} else if (tempInF > 80 && this.humidity > 40) {
feelsLike = -42.379 + 2.04901523 * tempInF + 10.14333127 * this.humidity
- 0.22475541 * tempInF * this.humidity - 6.83783 * Math.pow(10, -3) * tempInF * tempInF
- 5.481717 * Math.pow(10, -2) * this.humidity * this.humidity
+ 1.22874 * Math.pow(10, -3) * tempInF * tempInF * this.humidity
+ 8.5282 * Math.pow(10, -4) * tempInF * this.humidity * this.humidity
- 1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
feelsLike =
-42.379 +
2.04901523 * tempInF +
10.14333127 * this.humidity -
0.22475541 * tempInF * this.humidity -
6.83783 * Math.pow(10, -3) * tempInF * tempInF -
5.481717 * Math.pow(10, -2) * this.humidity * this.humidity +
1.22874 * Math.pow(10, -3) * tempInF * tempInF * this.humidity +
8.5282 * Math.pow(10, -4) * tempInF * this.humidity * this.humidity -
1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
}
return this.tempUnits === "imperial" ? feelsLike : (feelsLike - 32) * 5 / 9;
return this.tempUnits === "imperial" ? feelsLike : ((feelsLike - 32) * 5) / 9;
}
}

View File

@@ -28,77 +28,77 @@ var WeatherProvider = Class.extend({
// All the following methods can be overwritten, although most are good as they are.
// Called when a weather provider is initialized.
init: function(config) {
init: function (config) {
this.config = config;
Log.info(`Weather provider: ${this.providerName} initialized.`);
},
// Called to set the config, this config is the same as the weather module's config.
setConfig: function(config) {
setConfig: function (config) {
this.config = config;
Log.info(`Weather provider: ${this.providerName} config set.`, this.config);
},
// Called when the weather provider is about to start.
start: function() {
start: function () {
Log.info(`Weather provider: ${this.providerName} started.`);
},
// This method should start the API request to fetch the current weather.
// This method should definitely be overwritten in the provider.
fetchCurrentWeather: function() {
fetchCurrentWeather: function () {
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchCurrentWeather method.`);
},
// This method should start the API request to fetch the weather forecast.
// This method should definitely be overwritten in the provider.
fetchWeatherForecast: function() {
fetchWeatherForecast: function () {
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherForecast method.`);
},
// This returns a WeatherDay object for the current weather.
currentWeather: function() {
currentWeather: function () {
return this.currentWeatherObject;
},
// This returns an array of WeatherDay objects for the weather forecast.
weatherForecast: function() {
weatherForecast: function () {
return this.weatherForecastArray;
},
// This returns the name of the fetched location or an empty string.
fetchedLocation: function() {
fetchedLocation: function () {
return this.fetchedLocationName || "";
},
// Set the currentWeather and notify the delegate that new information is available.
setCurrentWeather: function(currentWeatherObject) {
setCurrentWeather: function (currentWeatherObject) {
// We should check here if we are passing a WeatherDay
this.currentWeatherObject = currentWeatherObject;
},
// Set the weatherForecastArray and notify the delegate that new information is available.
setWeatherForecast: function(weatherForecastArray) {
setWeatherForecast: function (weatherForecastArray) {
// We should check here if we are passing a WeatherDay
this.weatherForecastArray = weatherForecastArray;
},
// Set the fetched location name.
setFetchedLocation: function(name) {
setFetchedLocation: function (name) {
this.fetchedLocationName = name;
},
// Notify the delegate that new weather is available.
updateAvailable: function() {
updateAvailable: function () {
this.delegate.updateAvailable(this);
},
// A convenience function to make requests. It returns a promise.
fetchData: function(url, method = "GET", data = null) {
return new Promise(function(resolve, reject) {
fetchData: function (url, method = "GET", data = null) {
return new Promise(function (resolve, reject) {
var request = new XMLHttpRequest();
request.open(method, url, true);
request.onreadystatechange = function() {
request.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status === 200) {
resolve(JSON.parse(this.response));
@@ -120,14 +120,14 @@ WeatherProvider.providers = [];
/**
* Static method to register a new weather provider.
*/
WeatherProvider.register = function(providerIdentifier, providerDetails) {
WeatherProvider.register = function (providerIdentifier, providerDetails) {
WeatherProvider.providers[providerIdentifier.toLowerCase()] = WeatherProvider.extend(providerDetails);
};
/**
* Static method to initialize a new weather provider.
*/
WeatherProvider.initialize = function(providerIdentifier, delegate) {
WeatherProvider.initialize = function (providerIdentifier, delegate) {
providerIdentifier = providerIdentifier.toLowerCase();
var provider = new WeatherProvider.providers[providerIdentifier]();

View File

@@ -1,4 +1,5 @@
# Module: Weather Forecast
The `weatherforecast` module is one of the default modules of the MagicMirror.
This module displays the weather forecast for the coming week, including an an icon to display the current conditions, the minimum temperature and the maximum temperature.

View File

@@ -4,8 +4,7 @@
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
Module.register("weatherforecast",{
Module.register("weatherforecast", {
// Default module config.
defaults: {
location: false,
@@ -56,7 +55,7 @@ Module.register("weatherforecast",{
"11n": "wi-night-thunderstorm",
"13n": "wi-night-snow",
"50n": "wi-night-alt-cloudy-windy"
},
}
},
// create a variable for the first upcoming calendar event. Used if no location is specified.
@@ -66,17 +65,17 @@ Module.register("weatherforecast",{
fetchedLocationName: "",
// Define required scripts.
getScripts: function() {
getScripts: function () {
return ["moment.js"];
},
// Define required scripts.
getStyles: function() {
getStyles: function () {
return ["weather-icons.css", "weatherforecast.css"];
},
// Define required translations.
getTranslations: function() {
getTranslations: function () {
// The translations for the default modules are defined in the core translation files.
// Therefor we can just return false. Otherwise we should have returned a dictionary.
// If you're trying to build your own module including translations, check out the documentation.
@@ -84,7 +83,7 @@ Module.register("weatherforecast",{
},
// Define start sequence.
start: function() {
start: function () {
Log.info("Starting module: " + this.name);
// Set locale.
@@ -95,11 +94,10 @@ Module.register("weatherforecast",{
this.scheduleUpdate(this.config.initialLoadDelay);
this.updateTimer = null;
},
// Override dom generator.
getDom: function() {
getDom: function () {
var wrapper = document.createElement("div");
if (this.config.appid === "") {
@@ -143,17 +141,17 @@ Module.register("weatherforecast",{
if (this.config.units === "metric" || this.config.units === "imperial") {
degreeLabel += "°";
}
if(this.config.scale) {
switch(this.config.units) {
case "metric":
degreeLabel += "C";
break;
case "imperial":
degreeLabel += "F";
break;
case "default":
degreeLabel = "K";
break;
if (this.config.scale) {
switch (this.config.units) {
case "metric":
degreeLabel += "C";
break;
case "imperial":
degreeLabel += "F";
break;
case "default":
degreeLabel = "K";
break;
}
}
@@ -176,7 +174,7 @@ Module.register("weatherforecast",{
if (isNaN(forecast.rain)) {
rainCell.innerHTML = "";
} else {
if(config.units !== "imperial") {
if (config.units !== "imperial") {
rainCell.innerHTML = parseFloat(forecast.rain).toFixed(1).replace(".", this.config.decimalSymbol) + " mm";
} else {
rainCell.innerHTML = (parseFloat(forecast.rain) / 25.4).toFixed(2).replace(".", this.config.decimalSymbol) + " in";
@@ -194,7 +192,7 @@ Module.register("weatherforecast",{
var steps = this.forecast.length - startingPoint;
if (f >= startingPoint) {
var currentStep = f - startingPoint;
row.style.opacity = 1 - (1 / steps * currentStep);
row.style.opacity = 1 - (1 / steps) * currentStep;
}
}
}
@@ -203,7 +201,7 @@ Module.register("weatherforecast",{
},
// Override getHeader method.
getHeader: function() {
getHeader: function () {
if (this.config.appendLocationNameToHeader) {
return this.data.header + " " + this.fetchedLocationName;
}
@@ -212,10 +210,10 @@ Module.register("weatherforecast",{
},
// Override notification handler.
notificationReceived: function(notification, payload, sender) {
notificationReceived: function (notification, payload, sender) {
if (notification === "DOM_OBJECTS_CREATED") {
if (this.config.appendLocationNameToHeader) {
this.hide(0, {lockString: this.identifier});
this.hide(0, { lockString: this.identifier });
}
}
if (notification === "CALENDAR_EVENTS") {
@@ -239,7 +237,7 @@ Module.register("weatherforecast",{
* Requests new data from openweather.org.
* Calls processWeather on successful response.
*/
updateWeather: function() {
updateWeather: function () {
if (this.config.appid === "") {
Log.error("WeatherForecast: APPID not set!");
return;
@@ -251,7 +249,7 @@ Module.register("weatherforecast",{
var weatherRequest = new XMLHttpRequest();
weatherRequest.open("GET", url, true);
weatherRequest.onreadystatechange = function() {
weatherRequest.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status === 200) {
self.processWeather(JSON.parse(this.response));
@@ -269,7 +267,7 @@ Module.register("weatherforecast",{
}
if (retry) {
self.scheduleUpdate((self.loaded) ? -1 : self.config.retryDelay);
self.scheduleUpdate(self.loaded ? -1 : self.config.retryDelay);
}
}
};
@@ -281,18 +279,18 @@ Module.register("weatherforecast",{
*
* return String - URL params.
*/
getParams: function() {
getParams: function () {
var params = "?";
if(this.config.locationID) {
if (this.config.locationID) {
params += "id=" + this.config.locationID;
} else if(this.config.location) {
} else if (this.config.location) {
params += "q=" + this.config.location;
} else if (this.firstEvent && this.firstEvent.geo) {
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
} else if (this.firstEvent && this.firstEvent.location) {
params += "q=" + this.firstEvent.location;
} else {
this.hide(this.config.animationSpeed, {lockString:this.identifier});
this.hide(this.config.animationSpeed, { lockString: this.identifier });
return;
}
@@ -310,9 +308,9 @@ Module.register("weatherforecast",{
* from openweather.org
*
*/
parserDataWeather: function(data) {
parserDataWeather: function (data) {
if (data.hasOwnProperty("main")) {
data["temp"] = {"min": data.main.temp_min, "max": data.main.temp_max};
data["temp"] = { min: data.main.temp_min, max: data.main.temp_max };
}
return data;
},
@@ -322,7 +320,7 @@ Module.register("weatherforecast",{
*
* argument data object - Weather information received form openweather.org.
*/
processWeather: function(data) {
processWeather: function (data) {
this.fetchedLocationName = data.city.name + ", " + data.city.country;
this.forecast = [];
@@ -330,13 +328,12 @@ Module.register("weatherforecast",{
var forecastData = {};
for (var i = 0, count = data.list.length; i < count; i++) {
var forecast = data.list[i];
this.parserDataWeather(forecast); // hack issue #1017
var day;
var hour;
if(forecast.dt_txt) {
if (forecast.dt_txt) {
day = moment(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss").format("ddd");
hour = moment(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss").format("H");
} else {
@@ -375,7 +372,7 @@ Module.register("weatherforecast",{
}
//Log.log(this.forecast);
this.show(this.config.animationSpeed, {lockString:this.identifier});
this.show(this.config.animationSpeed, { lockString: this.identifier });
this.loaded = true;
this.updateDom(this.config.animationSpeed);
},
@@ -385,7 +382,7 @@ Module.register("weatherforecast",{
*
* argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
*/
scheduleUpdate: function(delay) {
scheduleUpdate: function (delay) {
var nextLoad = this.config.updateInterval;
if (typeof delay !== "undefined" && delay >= 0) {
nextLoad = delay;
@@ -393,7 +390,7 @@ Module.register("weatherforecast",{
var self = this;
clearTimeout(this.updateTimer);
this.updateTimer = setTimeout(function() {
this.updateTimer = setTimeout(function () {
self.updateWeather();
}, nextLoad);
},
@@ -409,8 +406,8 @@ Module.register("weatherforecast",{
*
* return number - Windspeed in beaufort.
*/
ms2Beaufort: function(ms) {
var kmh = ms * 60 * 60 / 1000;
ms2Beaufort: function (ms) {
var kmh = (ms * 60 * 60) / 1000;
var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
for (var beaufort in speeds) {
var speed = speeds[beaufort];
@@ -428,7 +425,7 @@ Module.register("weatherforecast",{
*
* return string - Rounded Temperature.
*/
roundValue: function(temperature) {
roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals);
},
@@ -440,16 +437,16 @@ Module.register("weatherforecast",{
* That object has a property "3h" which contains the amount of rain since the previous forecast in the list.
* This code finds all forecasts that is for the same day and sums the amount of rain and returns that.
*/
processRain: function(forecast, allForecasts) {
processRain: function (forecast, allForecasts) {
//If the amount of rain actually is a number, return it
if (!isNaN(forecast.rain)) {
return forecast.rain;
}
//Find all forecasts that is for the same day
var checkDateTime = (forecast.dt_txt) ? moment(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss") : moment(forecast.dt, "X");
var daysForecasts = allForecasts.filter(function(item) {
var itemDateTime = (item.dt_txt) ? moment(item.dt_txt, "YYYY-MM-DD hh:mm:ss") : moment(item.dt, "X");
var checkDateTime = forecast.dt_txt ? moment(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss") : moment(forecast.dt, "X");
var daysForecasts = allForecasts.filter(function (item) {
var itemDateTime = item.dt_txt ? moment(item.dt_txt, "YYYY-MM-DD hh:mm:ss") : moment(item.dt, "X");
return itemDateTime.isSame(checkDateTime, "day") && item.rain instanceof Object;
});
@@ -459,10 +456,12 @@ Module.register("weatherforecast",{
}
//Summarize all the rain from the matching days
return daysForecasts.map(function(item) {
return Object.values(item.rain)[0];
}).reduce(function(a, b) {
return a + b;
}, 0);
return daysForecasts
.map(function (item) {
return Object.values(item.rain)[0];
})
.reduce(function (a, b) {
return a + b;
}, 0);
}
});