Release v2.25.0 (#3214)

## [2.25.0] - 2023-10-01

Thanks to: @bugsounet, @dgoth, @dependabot, @kenzal, @Knapoc,
@KristjanESPERANTO, @martingron, @NolanKingdon, @Paranoid93,
@TeddyStarinvest and @Ybbet.

Special thanks to @khassel, @rejas and @sdetweil for taking over most
(if not all) of the work on this release as project collaborators. This
version would not be there without their effort. Thank you guys! You are
awesome!

> ⚠️ This release needs nodejs version >= `v18`, older releases have
reached end of life and will not work!

### Added

- Added UV Index support to OpenWeatherMap
- Added 'hideDuplicates' flag to the calendar module
- Added `allowOverrideNotification` to weather module to enable sending
current weather objects with the `CURRENT_WEATHER_OVERRIDE` notification
to supplement/replace the current weather displayed
- Added optional AnimateCSS animate for `hide()`, `show()`,
`updateDom()`
- Added AnimateIn and animateOut in module config definition
- Apply AnimateIn rules on the first start
- Added automatic client page reload when server was restarted by
setting `reloadAfterServerRestart: true` in `config.js`, per default
`false` (#3105)
- Added eventClass option for customEvents on the default calendar
- Added AnimateCSS integration in tests suite (#3206)
- Added npm dependabot [Reserved to developer] (#3210)
- Added improved logging for calendar (#3110)

### Removed

- **Breaking Change**: Removed `digest` authentication method from
calendar module (which was already broken since release `2.15.0`)

### Updated

- Update roboto fonts to version v5
- Update issue template
- Update dev/dependencies incl. electron to v26
- Replace pretty-quick by lint-staged
(<https://github.com/azz/pretty-quick/issues/164>)
- Update engine node >=18. v16 reached it's end of life. (#3170)
- Update typescript definition for modules
- Cleaned up nunjuck templates
- Replace `node-fetch` with internal fetch (#2649) and remove
`digest-fetch`
- Update the French translation according to the English file.
- Update dependabot incl. vendor/fonts (monthly check)
- Renew `package-lock.json` for release

### Fixed

- Fix engine check on npm install (#3135)
- Fix undefined formatTime method in clock module (#3143)
- Fix clientonly startup fails after async added (#3151)
- Fix electron width/heigth when using xrandr under bullseye
- Fix time issue with certain recurring events in calendar module
- Fix ipWhiteList test (#3179)
- Fix newsfeed: Convert HTML entities, codes and tag in description
(#3191)
- Respect width/height (no fullscreen) if set in electronOptions
(together with `fullscreen: false`) in `config.js` (#3174)
- Fix: AnimateCSS merge hide() and show() animated css class when we do
multiple call
- Fix `Uncaught SyntaxError: Identifier 'getCorsUrl' has already been
declared (at utils.js:1:1)` when using `clock` and `weather` module
(#3204)
- Fix overriding `config.js` when running tests (#3201)
- Fix issue in weathergov provider with probability of precipitation not
showing up on hourly or daily forecast

---------

Signed-off-by: naveen <172697+naveensrinivasan@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Karsten Hassel <hassel@gmx.de>
Co-authored-by: Malte Hallström <46646495+SkySails@users.noreply.github.com>
Co-authored-by: Veeck <github@veeck.de>
Co-authored-by: veeck <michael@veeck.de>
Co-authored-by: dWoolridge <dwoolridge@charter.net>
Co-authored-by: Johan <jojjepersson@yahoo.se>
Co-authored-by: Dario Mratovich <dario_mratovich@hotmail.com>
Co-authored-by: Dario Mratovich <dario.mratovich@outlook.com>
Co-authored-by: Magnus <34011212+MagMar94@users.noreply.github.com>
Co-authored-by: Naveen <172697+naveensrinivasan@users.noreply.github.com>
Co-authored-by: buxxi <buxxi@omfilm.net>
Co-authored-by: Thomas Hirschberger <47733292+Tom-Hirschberger@users.noreply.github.com>
Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com>
Co-authored-by: Andrés Vanegas Jiménez <142350+angeldeejay@users.noreply.github.com>
Co-authored-by: Dave Child <dave@addedbytes.com>
Co-authored-by: grenagit <46225780+grenagit@users.noreply.github.com>
Co-authored-by: Grena <grena@grenabox.fr>
Co-authored-by: Magnus Marthinsen <magmar@online.no>
Co-authored-by: Patrick <psieg@users.noreply.github.com>
Co-authored-by: Piotr Rajnisz <56397164+rajniszp@users.noreply.github.com>
Co-authored-by: Suthep Yonphimai <tomzt@users.noreply.github.com>
Co-authored-by: CarJem Generations (Carter Wallace) <cwallacecs@gmail.com>
Co-authored-by: Nicholas Fogal <nfogal.misc@gmail.com>
Co-authored-by: JakeBinney <126349119+JakeBinney@users.noreply.github.com>
Co-authored-by: OWL4C <124401812+OWL4C@users.noreply.github.com>
Co-authored-by: Oscar Björkman <17575446+oscarb@users.noreply.github.com>
Co-authored-by: Ismar Slomic <ismar@slomic.no>
Co-authored-by: Jørgen Veum-Wahlberg <jorgen.wahlberg@amedia.no>
Co-authored-by: Eddie Hung <6740044+eddiehung@users.noreply.github.com>
Co-authored-by: Bugsounet - Cédric <github@bugsounet.fr>
Co-authored-by: bugsounet <bugsounet@bugsounet.fr>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Knapoc <Knapoc@users.noreply.github.com>
Co-authored-by: sam detweiler <sdetweil@gmail.com>
Co-authored-by: veeck <michael.veeck@nebenan.de>
Co-authored-by: Paranoid93 <6515818+Paranoid93@users.noreply.github.com>
Co-authored-by: NolanKingdon <27908974+NolanKingdon@users.noreply.github.com>
Co-authored-by: J. Kenzal Hunter <kenzal.hunter@gmail.com>
Co-authored-by: Teddy <teddy.payet@gmail.com>
Co-authored-by: TeddyStarinvest <teddy.payet@starinvest.com>
Co-authored-by: martingron <61826403+martingron@users.noreply.github.com>
Co-authored-by: dgoth <132394363+dgoth@users.noreply.github.com>
This commit is contained in:
Michael Teeuw 2023-10-01 20:13:41 +02:00 committed by GitHub
parent e87f50e64a
commit 343e7de7bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
80 changed files with 4643 additions and 8570 deletions

View file

@ -1,4 +1,4 @@
/* global Loader, defaults, Translator */
/* global Loader, defaults, Translator, addAnimateCSS, removeAnimateCSS, AnimateCSSIn, AnimateCSSOut */
/* MagicMirror²
* Main System
@ -22,6 +22,10 @@ const MM = (function () {
return;
}
let haveAnimateIn = null;
// check if have valid animateIn in module definition (module.data.animateIn)
if (module.data.animateIn && AnimateCSSIn.indexOf(module.data.animateIn) !== -1) haveAnimateIn = module.data.animateIn;
const wrapper = selectWrapper(module.data.position);
const dom = document.createElement("div");
@ -50,7 +54,12 @@ const MM = (function () {
moduleContent.className = "module-content";
dom.appendChild(moduleContent);
const domCreationPromise = updateDom(module, 0);
// create the domCreationPromise with AnimateCSS (with animateIn of module definition)
// or just display it
var domCreationPromise;
if (haveAnimateIn) domCreationPromise = updateDom(module, { options: { speed: 1000, animate: { in: haveAnimateIn } } }, true);
else domCreationPromise = updateDom(module, 0);
domCreationPromises.push(domCreationPromise);
domCreationPromise
.then(function () {
@ -101,11 +110,30 @@ const MM = (function () {
/**
* Update the dom for a specific module.
* @param {Module} module The module that needs an update.
* @param {number} [speed] The (optional) number of microseconds for the animation.
* @param {object|number} [updateOptions] The (optional) number of microseconds for the animation or object with updateOptions (speed/animates)
* @param {boolean} [createAnimatedDom] for displaying only animateIn (used on first start of MagicMirror)
* @returns {Promise} Resolved when the dom is fully updated.
*/
const updateDom = function (module, speed) {
const updateDom = function (module, updateOptions, createAnimatedDom = false) {
return new Promise(function (resolve) {
let speed = updateOptions;
let animateOut = null;
let animateIn = null;
if (typeof updateOptions === "object") {
if (typeof updateOptions.options === "object" && updateOptions.options.speed !== undefined) {
speed = updateOptions.options.speed;
Log.debug(`updateDom: ${module.identifier} Has speed in object: ${speed}`);
if (typeof updateOptions.options.animate === "object") {
animateOut = updateOptions.options.animate.out;
animateIn = updateOptions.options.animate.in;
Log.debug(`updateDom: ${module.identifier} Has animate in object: out->${animateOut}, in->${animateIn}`);
}
} else {
Log.debug(`updateDom: ${module.identifier} Has no speed in object`);
speed = 0;
}
}
const newHeader = module.getHeader();
let newContentPromise = module.getDom();
@ -116,7 +144,7 @@ const MM = (function () {
newContentPromise
.then(function (newContent) {
const updatePromise = updateDomWithContent(module, speed, newHeader, newContent);
const updatePromise = updateDomWithContent(module, speed, newHeader, newContent, animateOut, animateIn, createAnimatedDom);
updatePromise.then(resolve).catch(Log.error);
})
@ -130,9 +158,12 @@ const MM = (function () {
* @param {number} [speed] The (optional) number of microseconds for the animation.
* @param {string} newHeader The new header that is generated.
* @param {HTMLElement} newContent The new content that is generated.
* @param {string} [animateOut] AnimateCss animation name before hidden
* @param {string} [animateIn] AnimateCss animation name on show
* @param {boolean} [createAnimatedDom] for displaying only animateIn (used on first start)
* @returns {Promise} Resolved when the module dom has been updated.
*/
const updateDomWithContent = function (module, speed, newHeader, newContent) {
const updateDomWithContent = function (module, speed, newHeader, newContent, animateOut, animateIn, createAnimatedDom = false) {
return new Promise(function (resolve) {
if (module.hidden || !speed) {
updateModuleContent(module, newHeader, newContent);
@ -151,13 +182,28 @@ const MM = (function () {
return;
}
hideModule(module, speed / 2, function () {
if (createAnimatedDom && animateIn !== null) {
Log.debug(`${module.identifier} createAnimatedDom (${animateIn})`);
updateModuleContent(module, newHeader, newContent);
if (!module.hidden) {
showModule(module, speed / 2);
showModule(module, speed, null, { animate: animateIn });
}
resolve();
});
return;
}
hideModule(
module,
speed / 2,
function () {
updateModuleContent(module, newHeader, newContent);
if (!module.hidden) {
showModule(module, speed / 2, null, { animate: animateIn });
}
resolve();
},
{ animate: animateOut }
);
});
};
@ -234,24 +280,65 @@ const MM = (function () {
const moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper !== null) {
moduleWrapper.style.transition = `opacity ${speed / 1000}s`;
moduleWrapper.style.opacity = 0;
moduleWrapper.classList.add("hidden");
clearTimeout(module.showHideTimer);
module.showHideTimer = setTimeout(function () {
// To not take up any space, we just make the position absolute.
// since it's fade out anyway, we can see it lay above or
// below other modules. This works way better than adjusting
// the .display property.
moduleWrapper.style.position = "fixed";
// reset all animations if needed
if (module.hasAnimateOut) {
removeAnimateCSS(module.identifier, module.hasAnimateOut);
Log.debug(`${module.identifier} Force remove animateOut (in hide): ${module.hasAnimateOut}`);
module.hasAnimateOut = false;
}
if (module.hasAnimateIn) {
removeAnimateCSS(module.identifier, module.hasAnimateIn);
Log.debug(`${module.identifier} Force remove animateIn (in hide): ${module.hasAnimateIn}`);
module.hasAnimateIn = false;
}
// haveAnimateName for verify if we are using AninateCSS library
// we check AnimateCSSOut Array for validate it
// and finaly return the animate name or `null` (for default MM² animation)
let haveAnimateName = null;
// check if have valid animateOut in module definition (module.data.animateOut)
if (module.data.animateOut && AnimateCSSOut.indexOf(module.data.animateOut) !== -1) haveAnimateName = module.data.animateOut;
// can't be override with options.animate
else if (options.animate && AnimateCSSOut.indexOf(options.animate) !== -1) haveAnimateName = options.animate;
updateWrapperStates();
if (haveAnimateName) {
// with AnimateCSS
Log.debug(`${module.identifier} Has animateOut: ${haveAnimateName}`);
module.hasAnimateOut = haveAnimateName;
addAnimateCSS(module.identifier, haveAnimateName, speed / 1000);
module.showHideTimer = setTimeout(function () {
removeAnimateCSS(module.identifier, haveAnimateName);
Log.debug(`${module.identifier} Remove animateOut: ${module.hasAnimateOut}`);
// AnimateCSS is now done
moduleWrapper.style.opacity = 0;
moduleWrapper.classList.add("hidden");
moduleWrapper.style.position = "fixed";
module.hasAnimateOut = false;
if (typeof callback === "function") {
callback();
}
}, speed);
updateWrapperStates();
if (typeof callback === "function") {
callback();
}
}, speed);
} else {
// default MM² Animate
moduleWrapper.style.transition = `opacity ${speed / 1000}s`;
moduleWrapper.style.opacity = 0;
moduleWrapper.classList.add("hidden");
module.showHideTimer = setTimeout(function () {
// To not take up any space, we just make the position absolute.
// since it's fade out anyway, we can see it lay above or
// below other modules. This works way better than adjusting
// the .display property.
moduleWrapper.style.position = "fixed";
updateWrapperStates();
if (typeof callback === "function") {
callback();
}
}, speed);
}
} else {
// invoke callback even if no content, issue 1308
if (typeof callback === "function") {
@ -285,6 +372,17 @@ const MM = (function () {
}
return;
}
// reset all animations if needed
if (module.hasAnimateOut) {
removeAnimateCSS(module.identifier, module.hasAnimateOut);
Log.debug(`${module.identifier} Force remove animateOut (in show): ${module.hasAnimateOut}`);
module.hasAnimateOut = false;
}
if (module.hasAnimateIn) {
removeAnimateCSS(module.identifier, module.hasAnimateIn);
Log.debug(`${module.identifier} Force remove animateIn (in show): ${module.hasAnimateIn}`);
module.hasAnimateIn = false;
}
module.hidden = false;
@ -296,7 +394,18 @@ const MM = (function () {
const moduleWrapper = document.getElementById(module.identifier);
if (moduleWrapper !== null) {
moduleWrapper.style.transition = `opacity ${speed / 1000}s`;
clearTimeout(module.showHideTimer);
// haveAnimateName for verify if we are using AninateCSS library
// we check AnimateCSSIn Array for validate it
// and finaly return the animate name or `null` (for default MM² animation)
let haveAnimateName = null;
// check if have valid animateOut in module definition (module.data.animateIn)
if (module.data.animateIn && AnimateCSSIn.indexOf(module.data.animateIn) !== -1) haveAnimateName = module.data.animateIn;
// can't be override with options.animate
else if (options.animate && AnimateCSSIn.indexOf(options.animate) !== -1) haveAnimateName = options.animate;
if (!haveAnimateName) moduleWrapper.style.transition = `opacity ${speed / 1000}s`;
// Restore the position. See hideModule() for more info.
moduleWrapper.style.position = "static";
moduleWrapper.classList.remove("hidden");
@ -307,12 +416,27 @@ const MM = (function () {
const dummy = moduleWrapper.parentElement.parentElement.offsetHeight;
moduleWrapper.style.opacity = 1;
clearTimeout(module.showHideTimer);
module.showHideTimer = setTimeout(function () {
if (typeof callback === "function") {
callback();
}
}, speed);
if (haveAnimateName) {
// with AnimateCSS
Log.debug(`${module.identifier} Has animateIn: ${haveAnimateName}`);
module.hasAnimateIn = haveAnimateName;
addAnimateCSS(module.identifier, haveAnimateName, speed / 1000);
module.showHideTimer = setTimeout(function () {
removeAnimateCSS(module.identifier, haveAnimateName);
Log.debug(`${module.identifier} Remove animateIn: ${haveAnimateName}`);
module.hasAnimateIn = false;
if (typeof callback === "function") {
callback();
}
}, speed);
} else {
// default MM² Animate
module.showHideTimer = setTimeout(function () {
if (typeof callback === "function") {
callback();
}
}, speed);
}
} else {
// invoke callback
if (typeof callback === "function") {
@ -477,12 +601,33 @@ const MM = (function () {
*/
modulesStarted: function (moduleObjects) {
modules = [];
let startUp = "";
moduleObjects.forEach((module) => modules.push(module));
Log.info("All modules started!");
sendNotification("ALL_MODULES_STARTED");
createDomObjects();
if (config.reloadAfterServerRestart) {
setInterval(async () => {
// if server startup time has changed (which means server was restarted)
// the client reloads the mm page
try {
const res = await fetch(`${location.protocol}//${location.host}/startup`);
const curr = await res.text();
if (startUp === "") startUp = curr;
if (startUp !== curr) {
startUp = "";
window.location.reload(true);
console.warn("Refreshing Website because server was restarted");
}
} catch (err) {
Log.error(`MagicMirror not reachable: ${err}`);
}
}, config.checkServerInterval);
}
},
/**
@ -514,9 +659,9 @@ const MM = (function () {
/**
* Update the dom for a specific module.
* @param {Module} module The module that needs an update.
* @param {number} [speed] The number of microseconds for the animation.
* @param {object|number} [updateOptions] The (optional) number of microseconds for the animation or object with updateOptions (speed/animates)
*/
updateDom: function (module, speed) {
updateDom: function (module, updateOptions) {
if (!(module instanceof Module)) {
Log.error("updateDom: Sender should be a module.");
return;
@ -528,7 +673,7 @@ const MM = (function () {
}
// Further implementation is done in the private method.
updateDom(module, speed);
updateDom(module, updateOptions);
},
/**