Merge branch 'develop' into features/weather_forecast_and_forecast_daily_support

This commit is contained in:
Michael Teeuw
2019-02-14 17:34:08 +01:00
committed by GitHub
23 changed files with 323 additions and 87 deletions

View File

@@ -32,6 +32,7 @@ The following properties can be configured:
| `defaultSymbol` | The default symbol. <br><br> **Possible values:** See [Font Awsome](http://fontawesome.io/icons/) website. <br> **Default value:** `calendar`
| `maxTitleLength` | The maximum title length. <br><br> **Possible values:** `10` - `50` <br> **Default value:** `25`
| `wrapEvents` | Wrap event titles to multiple lines. Breaks lines at the length defined by `maxTitleLength`. <br><br> **Possible values:** `true` or `false` <br> **Default value:** `false`
| `maxTitleLines` | The maximum number of lines a title will wrap vertically before being cut (Only enabled if `wrapEvents` is also enabled). <br><br> **Possible values:** `0` - `10` <br> **Default value:** `3`
| `fetchInterval` | How often does the content needs to be fetched? (Milliseconds) <br><br> **Possible values:** `1000` - `86400000` <br> **Default value:** `300000` (5 minutes)
| `animationSpeed` | Speed of the update animation. (Milliseconds) <br><br> **Possible values:** `0` - `5000` <br> **Default value:** `2000` (2 seconds)
| `fade` | Fade the future events to black. (Gradient) <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`

View File

@@ -19,6 +19,7 @@ Module.register("calendar", {
defaultRepeatingCountTitle: "",
maxTitleLength: 25,
wrapEvents: false, // wrap events to multiple lines breaking at maxTitleLength
maxTitleLines: 3,
fetchInterval: 5 * 60 * 1000, // Update every 5 minutes.
animationSpeed: 2000,
fade: true,
@@ -51,7 +52,7 @@ Module.register("calendar", {
// Define required scripts.
getStyles: function () {
return ["calendar.css", "font-awesome5.css", "font-awesome5.v4shims.css"];
return ["calendar.css", "font-awesome.css"];
},
// Define required scripts.
@@ -220,7 +221,7 @@ Module.register("calendar", {
var titleWrapper = document.createElement("td"),
repeatingCountTitle = "";
if (this.config.displayRepeatingCountTitle) {
if (this.config.displayRepeatingCountTitle && event.firstYear !== undefined) {
repeatingCountTitle = this.countTitleForUrl(event.url);
@@ -584,9 +585,10 @@ Module.register("calendar", {
* @param {string} string Text string to shorten
* @param {number} maxLength The max length of the string
* @param {boolean} wrapEvents Wrap the text after the line has reached maxLength
* @param {number} maxTitleLines The max number of vertical lines before cutting event title
* @returns {string} The shortened string
*/
shorten: function (string, maxLength, wrapEvents) {
shorten: function (string, maxLength, wrapEvents, maxTitleLines) {
if (typeof string !== "string") {
return "";
}
@@ -595,12 +597,21 @@ Module.register("calendar", {
var temp = "";
var currentLine = "";
var words = string.split(" ");
var line = 0;
for (var i = 0; i < words.length; i++) {
var word = words[i];
if (currentLine.length + word.length < (typeof maxLength === "number" ? maxLength : 25) - 1) { // max - 1 to account for a space
currentLine += (word + " ");
} else {
line++;
if (line > maxTitleLines - 1) {
if (i < words.length) {
currentLine += "&hellip;";
}
break;
}
if (currentLine.length > 0) {
temp += (currentLine + "<br>" + word + " ");
} else {
@@ -651,7 +662,7 @@ Module.register("calendar", {
title = title.replace(needle, replacement);
}
title = this.shorten(title, this.config.maxTitleLength, this.config.wrapEvents);
title = this.shorten(title, this.config.maxTitleLength, this.config.wrapEvents, this.config.maxTitleLines);
return title;
},

View File

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

View File

@@ -26,6 +26,7 @@ Module.register("clock",{
analogShowDate: "top", // options: false, 'top', or 'bottom'
secondsColor: "#888888",
timezone: null,
autoTimezone: false
},
// Define required scripts.
getScripts: function() {
@@ -39,16 +40,31 @@ Module.register("clock",{
start: function() {
Log.info("Starting module: " + this.name);
// Schedule update interval.
var self = this;
setInterval(function() {
self.updateDom();
}, 1000);
if (this.config.autoTimezone) {
this.sendSocketNotification("AUTO_TIMEZONE");
} else {
// Schedule update interval.
var self = this;
setInterval(function() {
self.updateDom();
}, 1000);
}
// Set locale.
moment.locale(config.language);
},
socketNotificationReceived: function (notification, payload) {
if (notification === "UPDATE_TIMEZONE") {
var self = this;
self.config.timezone = payload.timezone;
setInterval(function() {
self.updateDom();
}, 1000);
}
},
// Override dom generator.
getDom: function() {
@@ -137,7 +153,8 @@ Module.register("clock",{
clockCircle.style.backgroundSize = "100%";
// The following line solves issue: https://github.com/MichMich/MagicMirror/issues/611
clockCircle.style.border = "1px solid black";
// clockCircle.style.border = "1px solid black";
clockCircle.style.border = "rgba(0, 0, 0, 0.1)"; //Updated fix for Issue 611 where non-black backgrounds are used
} else if (this.config.analogFace != "none") {
clockCircle.style.border = "2px solid white";

View File

@@ -0,0 +1,29 @@
var http = require("http");
var NodeHelper = require("node_helper");
module.exports = NodeHelper.create({
start: function () {
},
socketNotificationReceived: function (notification, payload) {
var self = this;
if (notification === "AUTO_TIMEZONE") {
console.log("Loading timezone...");
http.get("http://ip-api.com/json", function (req) {
var data = "";
req.on("data", function (d) {
data += d;
});
req.on("end", function () {
var body = JSON.parse(data);
payload.timezone = body.timezone;
self.sendSocketNotification("UPDATE_TIMEZONE", payload);
});
}).on("error", function () {
payload.error = "Could not figure out the timezone.";
self.sendSocketNotification("UPDATE_TIMEZONE", payload);
});
}
}
});

View File

@@ -11,6 +11,7 @@ Module.register("currentweather",{
// Default module config.
defaults: {
autoLocation: false,
location: false,
locationID: false,
appid: "",
@@ -109,8 +110,19 @@ Module.register("currentweather",{
this.weatherType = null;
this.feelsLike = null;
this.loaded = false;
this.scheduleUpdate(this.config.initialLoadDelay);
if (this.config.autoLocation) {
this.sendSocketNotification("AUTO_LOCATION");
} else {
this.scheduleUpdate(this.config.initialLoadDelay);
}
},
socketNotificationReceived: function (notification, payload) {
if (notification === "UPDATE_LOCATION") {
this.config.location = payload.location;
this.scheduleUpdate(this.config.initialLoadDelay);
}
},
// add extra information of current weather
@@ -198,16 +210,19 @@ Module.register("currentweather",{
large.appendChild(weatherIcon);
var degreeLabel = "";
if (this.config.degreeLabel) {
switch (this.config.units ) {
if (this.config.units === "metric" || this.config.units === "imperial") {
degreeLabel += "°";
}
if(this.config.degreeLabel) {
switch(this.config.units) {
case "metric":
degreeLabel = " &deg;C";
degreeLabel += "C";
break;
case "imperial":
degreeLabel = " &deg;F";
degreeLabel += "F";
break;
case "default":
degreeLabel = " K";
degreeLabel += "K";
break;
}
}

View File

@@ -0,0 +1,29 @@
var http = require("http");
var NodeHelper = require("node_helper");
module.exports = NodeHelper.create({
start: function () {
},
socketNotificationReceived: function (notification, payload) {
var self = this;
if (notification === "AUTO_LOCATION") {
console.log("Loading timezone...");
http.get("http://ip-api.com/json", function (req) {
var data = "";
req.on("data", function (d) {
data += d;
});
req.on("end", function () {
var body = JSON.parse(data);
payload.location = body.city + ", " + body.regionName;
self.sendSocketNotification("UPDATE_LOCATION", payload);
});
}).on("error", function () {
payload.error = "Could not figure out the timezone.";
self.sendSocketNotification("UPDATE_LOCATION", payload);
});
}
}
});

View File

@@ -2,7 +2,7 @@
This module is aimed to be the replacement for the current `currentweather` and `weatherforcast` modules. The module will be configurable to be used as a current weather view, or to show the forecast. This way the module can be used twice to fullfil both purposes.
The biggest cange is the use of weather providers. This way we are not bound to one API source. And users can choose which API they want to use as their source.
The biggest change is the use of weather providers. This way we are not bound to one API source. And users can choose which API they want to use as their source.
The module is in a very early stage, and needs a lot of work. It's API isn't set in stone, so keep that in mind when you want to contribute.
@@ -71,6 +71,9 @@ The following properties can be configured:
| `tableClass` | The class for the forecast table. <br><br> **Default value:** `'small'`
| `colored` | If set to `true`, the min and max temperature are color coded. <br><br> **Default value:** `false`
| `showRainAmount` | Show the amount of rain in the forecast <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `fade` | Fade the future events to black. (Gradient) <br><br> **Possible values:** `true` or `false` <br> **Default value:** `true`
| `fadePoint` | Where to start fade? <br><br> **Possible values:** `0` (top of the list) - `1` (bottom of list) <br> **Default value:** `0.25`
| `maxNumberOfDays` | How many days of forecast to return. Specified by config.js <br><br> **Possible values:** `1` - `16` <br> **Default value:** `5` (5 days) <br> This value is optional. By default the weatherforecast module will return 5 days.
### Openweathermap options

View File

@@ -65,9 +65,9 @@
{% endif %}
{% else %}
<div class="dimmed light small">
{{ "LOADING" | translate }}
{{ "LOADING" | translate | safe }}
</div>
{% endif %}
<!-- Unclomment the line below to see the contents of the `current` object. -->
<!-- Uncomment the line below to see the contents of the `current` object. -->
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{current | dump}}</div> -->

View File

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

View File

@@ -33,6 +33,9 @@ Module.register("weather",{
decimalSymbol: ".",
showIndoorTemperature: false,
showIndoorHumidity: false,
maxNumberOfDays: 5,
fade: true,
fadePoint: 0.25, // Start on 1/4th of the list.
initialLoadDelay: 0, // 0 seconds delay
retryDelay: 2500,
@@ -59,7 +62,7 @@ Module.register("weather",{
return ["font-awesome.css", "weather-icons.css", "weather.css"];
},
// Return the scripts that are nessecery for the weather module.
// Return the scripts that are necessary for the weather module.
getScripts: function () {
return [
"moment.js",
@@ -215,7 +218,28 @@ Module.register("weather",{
}.bind(this));
this.nunjucksEnvironment().addFilter("decimalSymbol", function(value) {
return value.replace(/\./g, this.config.decimalSymbol);
return value.toString().replace(/\./g, this.config.decimalSymbol);
}.bind(this));
this.nunjucksEnvironment().addFilter("calcNumSteps", function(forecast) {
return Math.min(forecast.length, this.config.maxNumberOfDays);
}.bind(this));
this.nunjucksEnvironment().addFilter("opacity", function(currentStep, numSteps) {
if (this.config.fade && this.config.fadePoint < 1) {
if (this.config.fadePoint < 0) {
this.config.fadePoint = 0;
}
var startingPoint = numSteps * this.config.fadePoint;
var numFadesteps = numSteps - startingPoint;
if (currentStep >= startingPoint) {
return 1 - (currentStep - startingPoint) / numFadesteps;
} else {
return 1;
}
} else {
return 1;
}
}.bind(this));
}
});

View File

@@ -0,0 +1,29 @@
var http = require("http");
var NodeHelper = require("node_helper");
module.exports = NodeHelper.create({
start: function () {
},
socketNotificationReceived: function (notification, payload) {
var self = this;
if (notification === "AUTO_LOCATION") {
console.log("Loading timezone...");
http.get("http://ip-api.com/json", function (req) {
var data = "";
req.on("data", function (d) {
data += d;
});
req.on("end", function () {
var body = JSON.parse(data);
payload.location = body.city + ", " + body.regionName;
self.sendSocketNotification("UPDATE_LOCATION", payload);
});
}).on("error", function () {
payload.error = "Could not figure out the timezone.";
self.sendSocketNotification("UPDATE_LOCATION", payload);
});
}
}
});

View File

@@ -11,6 +11,7 @@ Module.register("weatherforecast",{
// Default module config.
defaults: {
autoLocation: false,
location: false,
locationID: false,
appid: "",
@@ -95,10 +96,20 @@ Module.register("weatherforecast",{
this.forecast = [];
this.loaded = false;
this.scheduleUpdate(this.config.initialLoadDelay);
this.updateTimer = null;
if (this.config.autoLocation) {
this.sendSocketNotification("AUTO_LOCATION");
} else {
this.scheduleUpdate(this.config.initialLoadDelay);
}
},
socketNotificationReceived: function (notification, payload) {
if (notification === "UPDATE_LOCATION") {
this.config.location = payload.location;
this.scheduleUpdate(this.config.initialLoadDelay);
}
},
// Override dom generator.
@@ -143,16 +154,19 @@ Module.register("weatherforecast",{
iconCell.appendChild(icon);
var degreeLabel = "";
if (this.config.units === "metric" || this.config.units === "imperial") {
degreeLabel += "°";
}
if(this.config.scale) {
switch(this.config.units) {
case "metric":
degreeLabel = " &deg;C";
degreeLabel += "C";
break;
case "imperial":
degreeLabel = " &deg;F";
degreeLabel += "F";
break;
case "default":
degreeLabel = " K";
degreeLabel = "K";
break;
}
}