mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-06-27 03:39:55 +00:00
## [2.26.0] - 01-01-2024 Thanks to: @bnitkin, @bugsounet, @dependabot, @jkriegshauser, @kaennchenstruggle, @KristjanESPERANTO 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 also marks the latest release by Michael Teeuw. For more info, please read the following post: [A New Chapter for MagicMirror: The Community Takes the Lead](https://forum.magicmirror.builders/topic/18329/a-new-chapter-for-magicmirror-the-community-takes-the-lead). ### Added - Added update notification updater (for 3rd party modules) - Added node 21 to the test matrix - Added transform object to calendar:customEvents - Added ESLint rules for jest (including jest/expect-expect and jest/no-done-callback) ### Removed - Removed Codecov workflow (not working anymore, other workflow required) (#3107) - Removed titleReplace from calendar, replaced + extended by customEvents (backward compatibility included) (#3249) - Removed failing unit test (#3254) - Removed some unused variables ### Updated - Update electron to v27 and update other dependencies as well as github actions - Update newsfeed: Use `html-to-text` instead of regex for transform description - Review ESLint config (#3269) - Updated dependencies - Clock module: optionally display current moon phase in addition to rise/set times - electron is now per default started without gpu, if needed it must be enabled with new env var `ELECTRON_ENABLE_GPU=1` on startup (#3226) - Replace prettier by stylistic in ESLint config to lint JavaScript (and disable some rules for `config/config.js*` files) - Update node-ical to v0.17.1 and fix tests ### Fixed - Avoid fade out/in on updateDom when many calendars are used - Fix the option eventClass on customEvents. - Fix yr API version in locationforecast and sunrise call (#3227) - Fix cloneObject() function to respect RegExp (#3237) - Fix newsfeed module for feeds using "a10:updated" tag (#3238) - Fix issue template (#3167) - Fix #3256 filter out bad results from rrule.between - Fix calendar events sometimes not respecting deleted events (#3250) - Fix electron loadurl locally on Windows when address "0.0.0.0" (#2550) - Fix updatanotification (update_helper.js): catch error if reponse is not an JSON format (check PM2) - Fix missing typeof in calendar module - Fix style issues after prettier update - Fix calendar test (#3291) by moving "Exdate check" from e2e to electron to run on a Thursday - Fix calendar config params `fetchInterval` and `excludedEvents` were never used from single calendar config (#3297) - Fix MM_PORT variable not used in electron and allow full path for MM_CONFIG_FILE variable (#3302) --------- 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> Co-authored-by: kaennchenstruggle <54073894+kaennchenstruggle@users.noreply.github.com> Co-authored-by: jkriegshauser <jkriegshauser@gmail.com> Co-authored-by: Ben Nitkin <ben@nitkin.net>
262 lines
7.2 KiB
JavaScript
262 lines
7.2 KiB
JavaScript
/* global defaultModules, vendor */
|
|
|
|
/* MagicMirror²
|
|
* Module and File loaders.
|
|
*
|
|
* By Michael Teeuw https://michaelteeuw.nl
|
|
* MIT Licensed.
|
|
*/
|
|
const Loader = (function () {
|
|
|
|
/* Create helper variables */
|
|
|
|
const loadedModuleFiles = [];
|
|
const loadedFiles = [];
|
|
const moduleObjects = [];
|
|
|
|
/* Private Methods */
|
|
|
|
/**
|
|
* Loops through all modules and requests start for every module.
|
|
*/
|
|
const startModules = async function () {
|
|
const modulePromises = [];
|
|
for (const module of moduleObjects) {
|
|
try {
|
|
modulePromises.push(module.start());
|
|
} catch (error) {
|
|
Log.error(`Error when starting node_helper for module ${module.name}:`);
|
|
Log.error(error);
|
|
}
|
|
}
|
|
|
|
const results = await Promise.allSettled(modulePromises);
|
|
|
|
// Log errors that happened during async node_helper startup
|
|
results.forEach((result) => {
|
|
if (result.status === "rejected") {
|
|
Log.error(result.reason);
|
|
}
|
|
});
|
|
|
|
// Notify core of loaded modules.
|
|
MM.modulesStarted(moduleObjects);
|
|
|
|
// Starting modules also hides any modules that have requested to be initially hidden
|
|
for (const thisModule of moduleObjects) {
|
|
if (thisModule.data.hiddenOnStartup) {
|
|
Log.info(`Initially hiding ${thisModule.name}`);
|
|
thisModule.hide();
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Retrieve list of all modules.
|
|
* @returns {object[]} module data as configured in config
|
|
*/
|
|
const getAllModules = function () {
|
|
return config.modules;
|
|
};
|
|
|
|
/**
|
|
* Generate array with module information including module paths.
|
|
* @returns {object[]} Module information.
|
|
*/
|
|
const getModuleData = function () {
|
|
const modules = getAllModules();
|
|
const moduleFiles = [];
|
|
|
|
modules.forEach(function (moduleData, index) {
|
|
const module = moduleData.module;
|
|
|
|
const elements = module.split("/");
|
|
const moduleName = elements[elements.length - 1];
|
|
let moduleFolder = `${config.paths.modules}/${module}`;
|
|
|
|
if (defaultModules.indexOf(moduleName) !== -1) {
|
|
moduleFolder = `${config.paths.modules}/default/${module}`;
|
|
}
|
|
|
|
if (moduleData.disabled === true) {
|
|
return;
|
|
}
|
|
|
|
moduleFiles.push({
|
|
index: index,
|
|
identifier: `module_${index}_${module}`,
|
|
name: moduleName,
|
|
path: `${moduleFolder}/`,
|
|
file: `${moduleName}.js`,
|
|
position: moduleData.position,
|
|
animateIn: moduleData.animateIn,
|
|
animateOut: moduleData.animateOut,
|
|
hiddenOnStartup: moduleData.hiddenOnStartup,
|
|
header: moduleData.header,
|
|
configDeepMerge: typeof moduleData.configDeepMerge === "boolean" ? moduleData.configDeepMerge : false,
|
|
config: moduleData.config,
|
|
classes: typeof moduleData.classes !== "undefined" ? `${moduleData.classes} ${module}` : module
|
|
});
|
|
});
|
|
|
|
return moduleFiles;
|
|
};
|
|
|
|
/**
|
|
* Load modules via ajax request and create module objects.
|
|
* @param {object} module Information about the module we want to load.
|
|
* @returns {Promise<void>} resolved when module is loaded
|
|
*/
|
|
const loadModule = async function (module) {
|
|
const url = module.path + module.file;
|
|
|
|
/**
|
|
* @returns {Promise<void>}
|
|
*/
|
|
const afterLoad = async function () {
|
|
const moduleObject = Module.create(module.name);
|
|
if (moduleObject) {
|
|
await bootstrapModule(module, moduleObject);
|
|
}
|
|
};
|
|
|
|
if (loadedModuleFiles.indexOf(url) !== -1) {
|
|
await afterLoad();
|
|
} else {
|
|
await loadFile(url);
|
|
loadedModuleFiles.push(url);
|
|
await afterLoad();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Bootstrap modules by setting the module data and loading the scripts & styles.
|
|
* @param {object} module Information about the module we want to load.
|
|
* @param {Module} mObj Modules instance.
|
|
*/
|
|
const bootstrapModule = async function (module, mObj) {
|
|
Log.info(`Bootstrapping module: ${module.name}`);
|
|
mObj.setData(module);
|
|
|
|
await mObj.loadScripts();
|
|
Log.log(`Scripts loaded for: ${module.name}`);
|
|
|
|
await mObj.loadStyles();
|
|
Log.log(`Styles loaded for: ${module.name}`);
|
|
|
|
await mObj.loadTranslations();
|
|
Log.log(`Translations loaded for: ${module.name}`);
|
|
|
|
moduleObjects.push(mObj);
|
|
};
|
|
|
|
/**
|
|
* Load a script or stylesheet by adding it to the dom.
|
|
* @param {string} fileName Path of the file we want to load.
|
|
* @returns {Promise} resolved when the file is loaded
|
|
*/
|
|
const loadFile = async function (fileName) {
|
|
const extension = fileName.slice((Math.max(0, fileName.lastIndexOf(".")) || Infinity) + 1);
|
|
let script, stylesheet;
|
|
|
|
switch (extension.toLowerCase()) {
|
|
case "js":
|
|
return new Promise((resolve) => {
|
|
Log.log(`Load script: ${fileName}`);
|
|
script = document.createElement("script");
|
|
script.type = "text/javascript";
|
|
script.src = fileName;
|
|
script.onload = function () {
|
|
resolve();
|
|
};
|
|
script.onerror = function () {
|
|
Log.error("Error on loading script:", fileName);
|
|
resolve();
|
|
};
|
|
document.getElementsByTagName("body")[0].appendChild(script);
|
|
});
|
|
case "css":
|
|
return new Promise((resolve) => {
|
|
Log.log(`Load stylesheet: ${fileName}`);
|
|
|
|
stylesheet = document.createElement("link");
|
|
stylesheet.rel = "stylesheet";
|
|
stylesheet.type = "text/css";
|
|
stylesheet.href = fileName;
|
|
stylesheet.onload = function () {
|
|
resolve();
|
|
};
|
|
stylesheet.onerror = function () {
|
|
Log.error("Error on loading stylesheet:", fileName);
|
|
resolve();
|
|
};
|
|
document.getElementsByTagName("head")[0].appendChild(stylesheet);
|
|
});
|
|
}
|
|
};
|
|
|
|
/* Public Methods */
|
|
return {
|
|
|
|
/**
|
|
* Load all modules as defined in the config.
|
|
*/
|
|
async loadModules () {
|
|
let moduleData = getModuleData();
|
|
|
|
/**
|
|
* @returns {Promise<void>} when all modules are loaded
|
|
*/
|
|
const loadNextModule = async function () {
|
|
if (moduleData.length > 0) {
|
|
const nextModule = moduleData[0];
|
|
await loadModule(nextModule);
|
|
moduleData = moduleData.slice(1);
|
|
await loadNextModule();
|
|
} else {
|
|
// All modules loaded. Load custom.css
|
|
// This is done after all the modules so we can
|
|
// overwrite all the defined styles.
|
|
await loadFile(config.customCss);
|
|
// custom.css loaded. Start all modules.
|
|
await startModules();
|
|
}
|
|
};
|
|
await loadNextModule();
|
|
},
|
|
|
|
/**
|
|
* Load a file (script or stylesheet).
|
|
* Prevent double loading and search for files in the vendor folder.
|
|
* @param {string} fileName Path of the file we want to load.
|
|
* @param {Module} module The module that calls the loadFile function.
|
|
* @returns {Promise} resolved when the file is loaded
|
|
*/
|
|
async loadFileForModule (fileName, module) {
|
|
if (loadedFiles.indexOf(fileName.toLowerCase()) !== -1) {
|
|
Log.log(`File already loaded: ${fileName}`);
|
|
return;
|
|
}
|
|
|
|
if (fileName.indexOf("http://") === 0 || fileName.indexOf("https://") === 0 || fileName.indexOf("/") !== -1) {
|
|
// This is an absolute or relative path.
|
|
// Load it and then return.
|
|
loadedFiles.push(fileName.toLowerCase());
|
|
return loadFile(fileName);
|
|
}
|
|
|
|
if (vendor[fileName] !== undefined) {
|
|
// This file is available in the vendor folder.
|
|
// Load it from this vendor folder.
|
|
loadedFiles.push(fileName.toLowerCase());
|
|
return loadFile(`${config.paths.vendor}/${vendor[fileName]}`);
|
|
}
|
|
|
|
// File not loaded yet.
|
|
// Load it based on the module path.
|
|
loadedFiles.push(fileName.toLowerCase());
|
|
return loadFile(module.file(fileName));
|
|
}
|
|
};
|
|
}());
|