2026-05-18 19:58:44 +02:00
/* global nunjucks */
// eslint-disable-next-line import-x/extensions
import { Loader } from "./loader.js" ;
// eslint-disable-next-line import-x/extensions
import { MMSocket } from "./socketclient.js" ;
2016-03-24 17:19:32 +01:00
2024-08-12 22:52:43 +02:00
/*
* Module Blueprint.
2020-08-01 16:31:42 +02:00
* @typedef {Object} Module
2016-03-24 17:19:32 +01:00
*/
2026-05-18 19:58:44 +02:00
export class Module {
2026-05-12 20:01:12 +02:00
/**
* Initializes per-instance mutable state.
*/
constructor () {
// Timer reference used for showHide animation callbacks.
this . showHideTimer = null ;
/*
* Array to store lockStrings. These strings are used to lock
* visibility when hiding and showing module.
*/
this . lockStrings = [];
/*
* Storage of the nunjucks Environment.
* This should not be referenced directly.
* Use the nunjucksEnvironment() method to get it.
*/
this . _nunjucksEnvironment = null ;
}
2024-01-01 15:38:08 +01:00
2024-08-12 22:52:43 +02:00
/**
2025-11-13 15:08:47 -06:00
*********************************************************
* All methods (and properties) below can be overridden. *
*********************************************************
2024-08-12 22:52:43 +02:00
*/
2016-03-24 17:19:32 +01:00
2020-07-28 16:48:03 +02:00
/**
* Called when the module is instantiated.
2016-03-24 17:19:32 +01:00
*/
2024-01-01 15:38:08 +01:00
init () {
2026-05-12 20:01:12 +02:00
}
2016-03-24 17:19:32 +01:00
2020-07-28 16:48:03 +02:00
/**
* Called when the module is started.
2016-03-24 17:19:32 +01:00
*/
2026-02-25 10:55:56 +01:00
start () {
2023-04-04 20:44:32 +02:00
Log . info ( `Starting module: ${ this . name } ` );
2026-05-12 20:01:12 +02:00
}
2016-03-24 17:19:32 +01:00
2020-07-28 16:48:03 +02:00
/**
2016-03-24 17:19:32 +01:00
* Returns a list of scripts the module requires to be loaded.
2020-08-01 16:31:42 +02:00
* @returns {string[]} An array with filenames.
2016-03-24 17:19:32 +01:00
*/
2024-01-01 15:38:08 +01:00
getScripts () {
2016-03-24 17:19:32 +01:00
return [];
2026-05-12 20:01:12 +02:00
}
2016-03-24 17:19:32 +01:00
2020-07-28 16:48:03 +02:00
/**
2016-03-24 17:19:32 +01:00
* Returns a list of stylesheets the module requires to be loaded.
2020-08-01 16:31:42 +02:00
* @returns {string[]} An array with filenames.
2016-03-24 17:19:32 +01:00
*/
2024-01-01 15:38:08 +01:00
getStyles () {
2016-03-24 17:19:32 +01:00
return [];
2026-05-12 20:01:12 +02:00
}
2016-03-24 17:19:32 +01:00
2020-07-28 16:48:03 +02:00
/**
2016-04-21 01:03:26 +02:00
* Returns a map of translation files the module requires to be loaded.
*
2020-07-28 16:48:03 +02:00
* return Map<String, String> -
2025-09-23 06:27:29 +02:00
* @returns {Map} A map with langKeys and filenames.
2016-04-21 01:03:26 +02:00
*/
2024-01-01 15:38:08 +01:00
getTranslations () {
2016-05-11 12:38:41 +02:00
return false ;
2026-05-12 20:01:12 +02:00
}
2016-04-21 01:03:26 +02:00
2020-07-28 16:48:03 +02:00
/**
2022-01-26 23:09:26 +01:00
* Generates the dom which needs to be displayed. This method is called by the MagicMirror² core.
2025-11-13 15:08:47 -06:00
* This method can to be overridden if the module wants to display info on the mirror.
* Alternatively, the getTemplate method could be overridden.
2020-07-28 16:48:03 +02:00
* @returns {HTMLElement|Promise} The dom or a promise with the dom to display.
2016-03-24 17:19:32 +01:00
*/
2024-01-01 15:38:08 +01:00
getDom () {
2021-03-23 22:40:24 +01:00
return new Promise (( resolve ) => {
const div = document . createElement ( "div" );
const template = this . getTemplate ();
const templateData = this . getTemplateData ();
2018-01-01 10:55:39 -06:00
2018-01-01 09:42:34 -06:00
// Check to see if we need to render a template string or a file.
2024-01-01 15:38:08 +01:00
if (( /^.*((\.html)|(\.njk))$/ ). test ( template )) {
2018-01-01 09:42:34 -06:00
// the template is a filename
2021-03-23 22:40:24 +01:00
this . nunjucksEnvironment (). render ( template , templateData , function ( err , res ) {
2018-01-01 09:42:34 -06:00
if ( err ) {
2019-06-05 10:23:58 +02:00
Log . error ( err );
2018-01-01 09:42:34 -06:00
}
div . innerHTML = res ;
resolve ( div );
});
} else {
// the template is a template string.
2021-03-23 22:40:24 +01:00
div . innerHTML = this . nunjucksEnvironment (). renderString ( template , templateData );
2018-01-01 09:42:34 -06:00
resolve ( div );
}
});
2026-05-12 20:01:12 +02:00
}
2016-03-24 17:19:32 +01:00
2020-07-28 16:48:03 +02:00
/**
* Generates the header string which needs to be displayed if a user has a header configured for this module.
2022-01-26 23:09:26 +01:00
* This method is called by the MagicMirror² core, but only if the user has configured a default header for the module.
2025-11-13 15:08:47 -06:00
* This method needs to be overridden if the module wants to display modified headers on the mirror.
2020-07-28 16:48:03 +02:00
* @returns {string} The header to display above the header.
2016-09-20 17:22:24 +02:00
*/
2024-01-01 15:38:08 +01:00
getHeader () {
2016-09-20 17:22:24 +02:00
return this . data . header ;
2026-05-12 20:01:12 +02:00
}
2016-09-20 17:22:24 +02:00
2020-07-28 16:48:03 +02:00
/**
* Returns the template for the module which is used by the default getDom implementation.
2025-11-13 15:08:47 -06:00
* This method needs to be overridden if the module wants to use a template.
* It can either return a template string, or a template filename.
2017-09-28 16:11:25 +02:00
* If the string ends with '.html' it's considered a file from within the module's folder.
2020-07-28 16:48:03 +02:00
* @returns {string} The template string of filename.
2017-09-28 16:11:25 +02:00
*/
2024-01-01 15:38:08 +01:00
getTemplate () {
2023-04-04 20:44:32 +02:00
return `<div class="normal"> ${ this . name } </div><div class="small dimmed"> ${ this . identifier } </div>` ;
2026-05-12 20:01:12 +02:00
}
2017-09-28 16:11:25 +02:00
2020-07-28 16:48:03 +02:00
/**
* Returns the data to be used in the template.
2025-11-13 15:08:47 -06:00
* This method needs to be overridden if the module wants to use a custom data.
2020-07-28 16:48:03 +02:00
* @returns {object} The data for the template
2017-09-28 16:11:25 +02:00
*/
2024-01-01 15:38:08 +01:00
getTemplateData () {
2019-06-05 10:23:58 +02:00
return {};
2026-05-12 20:01:12 +02:00
}
2017-09-28 16:11:25 +02:00
2020-07-28 16:48:03 +02:00
/**
2022-01-26 23:09:26 +01:00
* Called by the MagicMirror² core when a notification arrives.
2020-07-28 16:48:03 +02:00
* @param {string} notification The identifier of the notification.
2025-09-23 06:27:29 +02:00
* @param {object} payload The payload of the notification.
2020-08-01 16:31:42 +02:00
* @param {Module} sender The module that sent the notification.
2016-03-24 17:19:32 +01:00
*/
2024-01-01 15:38:08 +01:00
notificationReceived ( notification , payload , sender ) {
2016-03-24 17:19:32 +01:00
if ( sender ) {
2025-10-21 20:32:48 +02:00
Log . debug ( ` ${ this . name } received a module notification: ${ notification } from sender: ${ sender . name } ` );
2016-03-24 17:19:32 +01:00
} else {
2025-10-21 20:32:48 +02:00
Log . debug ( ` ${ this . name } received a system notification: ${ notification } ` );
2016-03-24 17:19:32 +01:00
}
2026-05-12 20:01:12 +02:00
}
2016-03-24 17:19:32 +01:00
2020-07-28 16:48:03 +02:00
/**
2017-09-29 11:05:59 +02:00
* Returns the nunjucks environment for the current module.
2017-09-28 16:11:25 +02:00
* The environment is checked in the _nunjucksEnvironment instance variable.
2020-07-28 16:48:03 +02:00
* @returns {object} The Nunjucks Environment
2017-09-28 16:11:25 +02:00
*/
2024-01-01 15:38:08 +01:00
nunjucksEnvironment () {
2019-06-05 09:32:10 +02:00
if ( this . _nunjucksEnvironment !== null ) {
2017-09-28 16:11:25 +02:00
return this . _nunjucksEnvironment ;
}
2020-05-11 22:22:32 +02:00
this . _nunjucksEnvironment = new nunjucks . Environment ( new nunjucks . WebLoader ( this . file ( "" ), { async : true }), {
2017-10-18 13:49:03 +02:00
trimBlocks : true ,
lstripBlocks : true
});
2020-07-28 16:48:03 +02:00
2021-03-23 22:40:24 +01:00
this . _nunjucksEnvironment . addFilter ( "translate" , ( str , variables ) => {
return nunjucks . runtime . markSafe ( this . translate ( str , variables ));
2017-09-28 16:11:25 +02:00
});
return this . _nunjucksEnvironment ;
2026-05-12 20:01:12 +02:00
}
2017-09-28 16:11:25 +02:00
2020-07-28 16:48:03 +02:00
/**
* Called when a socket notification arrives.
* @param {string} notification The identifier of the notification.
2025-09-23 06:27:29 +02:00
* @param {object} payload The payload of the notification.
2016-03-30 12:20:46 +02:00
*/
2024-01-01 15:38:08 +01:00
socketNotificationReceived ( notification , payload ) {
2023-04-04 20:44:32 +02:00
Log . log ( ` ${ this . name } received a socket notification: ${ notification } - Payload: ${ payload } ` );
2026-05-12 20:01:12 +02:00
}
2016-03-24 17:19:32 +01:00
2021-03-23 22:40:24 +01:00
/**
2020-07-28 16:48:03 +02:00
* Called when the module is hidden.
2016-05-11 13:49:40 +02:00
*/
2024-01-01 15:38:08 +01:00
suspend () {
2023-04-04 20:44:32 +02:00
Log . log ( ` ${ this . name } is suspended.` );
2026-05-12 20:01:12 +02:00
}
2016-05-11 13:49:40 +02:00
2021-03-23 22:40:24 +01:00
/**
2020-07-28 16:48:03 +02:00
* Called when the module is shown.
2016-05-11 13:49:40 +02:00
*/
2024-01-01 15:38:08 +01:00
resume () {
2023-04-04 20:44:32 +02:00
Log . log ( ` ${ this . name } is resumed.` );
2026-05-12 20:01:12 +02:00
}
2016-05-11 13:49:40 +02:00
2024-08-12 22:52:43 +02:00
/**
2025-11-13 15:08:47 -06:00
***********************************************
* The methods below should not be overridden. *
***********************************************
2024-08-12 22:52:43 +02:00
*/
2016-03-24 17:19:32 +01:00
2020-07-28 16:48:03 +02:00
/**
2016-03-24 17:19:32 +01:00
* Set the module data.
2021-03-23 22:40:24 +01:00
* @param {object} data The module data
2016-03-24 17:19:32 +01:00
*/
2024-01-01 15:38:08 +01:00
setData ( data ) {
2016-03-24 17:19:32 +01:00
this . data = data ;
this . name = data . name ;
this . identifier = data . identifier ;
2016-03-31 19:15:58 +02:00
this . hidden = false ;
2023-10-01 20:13:41 +02:00
this . hasAnimateIn = false ;
this . hasAnimateOut = false ;
2016-03-24 17:19:32 +01:00
2020-09-22 00:26:24 +02:00
this . setConfig ( data . config , data . configDeepMerge );
2026-05-12 20:01:12 +02:00
}
2016-03-24 17:19:32 +01:00
2020-07-28 16:48:03 +02:00
/**
2016-03-24 17:19:32 +01:00
* Set the module config and combine it with the module defaults.
2020-07-28 16:48:03 +02:00
* @param {object} config The combined module config.
2020-12-29 18:48:45 +01:00
* @param {boolean} deep Merge module config in deep.
2016-03-24 17:19:32 +01:00
*/
2024-01-01 15:38:08 +01:00
setConfig ( config , deep ) {
2020-09-22 00:26:24 +02:00
this . config = deep ? configMerge ({}, this . defaults , config ) : Object . assign ({}, this . defaults , config );
2026-05-12 20:01:12 +02:00
}
2016-03-24 17:19:32 +01:00
2020-07-28 16:48:03 +02:00
/**
* Returns a socket object. If it doesn't exist, it's created.
2016-03-30 12:20:46 +02:00
* It also registers the notification callback.
2020-07-28 16:48:03 +02:00
* @returns {MMSocket} a socket object
2016-03-30 12:20:46 +02:00
*/
2024-01-01 15:38:08 +01:00
socket () {
2016-04-05 14:35:11 -04:00
if ( typeof this . _socket === "undefined" ) {
2020-05-02 10:39:09 +02:00
this . _socket = new MMSocket ( this . name );
2016-03-30 12:20:46 +02:00
}
2021-03-23 22:40:24 +01:00
this . _socket . setNotificationCallback (( notification , payload ) => {
this . socketNotificationReceived ( notification , payload );
2016-03-30 12:20:46 +02:00
});
return this . _socket ;
2026-05-12 20:01:12 +02:00
}
2016-03-30 12:20:46 +02:00
2020-07-28 16:48:03 +02:00
/**
2016-04-20 23:13:31 +02:00
* Retrieve the path to a module file.
2020-07-28 16:48:03 +02:00
* @param {string} file Filename
* @returns {string} the file path
2016-03-24 17:19:32 +01:00
*/
2024-01-01 15:38:08 +01:00
file ( file ) {
2023-04-04 20:44:32 +02:00
return ` ${ this . data . path } / ${ file } ` . replace ( "//" , "/" );
2026-05-12 20:01:12 +02:00
}
2016-03-24 17:19:32 +01:00
2020-07-28 16:48:03 +02:00
/**
2016-03-24 17:19:32 +01:00
* Load all required stylesheets by requesting the MM object to load the files.
2023-04-04 20:44:32 +02:00
* @returns {Promise<void>}
2016-03-24 17:19:32 +01:00
*/
2024-01-01 15:38:08 +01:00
loadStyles () {
2023-04-04 20:44:32 +02:00
return this . loadDependencies ( "getStyles" );
2026-05-12 20:01:12 +02:00
}
2016-03-24 17:19:32 +01:00
2020-07-28 16:48:03 +02:00
/**
2016-03-24 17:19:32 +01:00
* Load all required scripts by requesting the MM object to load the files.
2023-04-04 20:44:32 +02:00
* @returns {Promise<void>}
2016-03-24 17:19:32 +01:00
*/
2024-01-01 15:38:08 +01:00
loadScripts () {
2023-04-04 20:44:32 +02:00
return this . loadDependencies ( "getScripts" );
2026-05-12 20:01:12 +02:00
}
2016-03-24 17:19:32 +01:00
2020-07-28 16:48:03 +02:00
/**
2017-02-07 23:51:13 +01:00
* Helper method to load all dependencies.
2020-07-28 16:48:03 +02:00
* @param {string} funcName Function name to call to get scripts or styles.
2023-04-04 20:44:32 +02:00
* @returns {Promise<void>}
2017-02-07 23:51:13 +01:00
*/
2024-01-01 15:38:08 +01:00
async loadDependencies ( funcName ) {
2021-03-23 22:40:24 +01:00
let dependencies = this [ funcName ]();
2017-02-08 00:05:28 +01:00
2023-04-04 20:44:32 +02:00
const loadNextDependency = async () => {
2017-02-08 00:05:28 +01:00
if ( dependencies . length > 0 ) {
2021-03-23 22:40:24 +01:00
const nextDependency = dependencies [ 0 ];
2023-04-04 20:44:32 +02:00
await Loader . loadFileForModule ( nextDependency , this );
dependencies = dependencies . slice ( 1 );
await loadNextDependency ();
2017-02-08 00:05:28 +01:00
} else {
2023-04-04 20:44:32 +02:00
return Promise . resolve ();
2017-02-08 00:05:28 +01:00
}
};
2023-04-04 20:44:32 +02:00
await loadNextDependency ();
2026-05-12 20:01:12 +02:00
}
2017-02-07 23:51:13 +01:00
2020-07-28 16:48:03 +02:00
/**
* Load all translations.
2023-07-01 21:17:31 +02:00
* @returns {Promise<void>}
2016-04-21 01:03:26 +02:00
*/
2024-01-01 15:38:08 +01:00
async loadTranslations () {
2021-01-29 22:25:49 +01:00
const translations = this . getTranslations () || {};
const language = config . language . toLowerCase ();
const languages = Object . keys ( translations );
const fallbackLanguage = languages [ 0 ];
2016-05-11 12:38:41 +02:00
2021-01-29 22:34:12 +01:00
if ( languages . length === 0 ) {
2021-02-06 22:55:59 +01:00
return ;
2020-05-11 22:22:32 +02:00
}
2016-05-11 12:38:41 +02:00
2021-01-29 22:34:12 +01:00
const translationFile = translations [ language ];
const translationsFallbackFile = translations [ fallbackLanguage ];
2016-05-11 12:38:41 +02:00
2021-01-29 22:34:12 +01:00
if ( ! translationFile ) {
2023-04-04 20:44:32 +02:00
return Translator . load ( this , translationsFallbackFile , true );
2021-01-29 22:34:12 +01:00
}
2023-04-04 20:44:32 +02:00
await Translator . load ( this , translationFile , false );
if ( translationFile !== translationsFallbackFile ) {
return Translator . load ( this , translationsFallbackFile , true );
}
2026-05-12 20:01:12 +02:00
}
2016-04-21 01:03:26 +02:00
2020-07-28 16:48:03 +02:00
/**
2017-04-25 23:15:34 +03:00
* Request the translation for a given key with optional variables and default value.
2020-07-28 16:48:03 +02:00
* @param {string} key The key of the string to translate
* @param {string|object} [defaultValueOrVariables] The default value or variables for translating.
* @param {string} [defaultValue] The default value with variables.
* @returns {string} the translated key
2016-10-13 16:42:15 +02:00
*/
2024-01-01 15:38:08 +01:00
translate ( key , defaultValueOrVariables , defaultValue ) {
2020-05-11 22:22:32 +02:00
if ( typeof defaultValueOrVariables === "object" ) {
2017-04-25 23:15:34 +03:00
return Translator . translate ( this , key , defaultValueOrVariables ) || defaultValue || "" ;
}
return Translator . translate ( this , key ) || defaultValueOrVariables || "" ;
2026-05-12 20:01:12 +02:00
}
2016-04-21 01:03:26 +02:00
2020-07-28 16:48:03 +02:00
/**
2016-03-24 17:19:32 +01:00
* Request an (animated) update of the module.
2023-10-01 20:13:41 +02:00
* @param {number|object} [updateOptions] The speed of the animation or object with for updateOptions (speed/animates)
2016-03-24 17:19:32 +01:00
*/
2024-01-01 15:38:08 +01:00
updateDom ( updateOptions ) {
2023-10-01 20:13:41 +02:00
MM . updateDom ( this , updateOptions );
2026-05-12 20:01:12 +02:00
}
2016-03-24 17:19:32 +01:00
2020-07-28 16:48:03 +02:00
/**
2016-03-24 17:19:32 +01:00
* Send a notification to all modules.
2020-07-28 16:48:03 +02:00
* @param {string} notification The identifier of the notification.
2025-09-23 06:27:29 +02:00
* @param {object} payload The payload of the notification.
2016-03-24 17:19:32 +01:00
*/
2024-01-01 15:38:08 +01:00
sendNotification ( notification , payload ) {
2016-03-24 17:19:32 +01:00
MM . sendNotification ( notification , payload , this );
2026-05-12 20:01:12 +02:00
}
2016-03-30 12:20:46 +02:00
2020-07-28 16:48:03 +02:00
/**
2016-03-30 12:20:46 +02:00
* Send a socket notification to the node helper.
2020-07-28 16:48:03 +02:00
* @param {string} notification The identifier of the notification.
2025-09-23 06:27:29 +02:00
* @param {object} payload The payload of the notification.
2016-03-30 12:20:46 +02:00
*/
2024-01-01 15:38:08 +01:00
sendSocketNotification ( notification , payload ) {
2016-03-30 12:20:46 +02:00
this . socket (). sendNotification ( notification , payload );
2026-05-12 20:01:12 +02:00
}
2016-03-31 19:15:58 +02:00
2020-07-28 16:48:03 +02:00
/**
2016-03-31 19:15:58 +02:00
* Hide this module.
2020-07-28 16:48:03 +02:00
* @param {number} speed The speed of the hide animation.
2025-09-23 06:27:29 +02:00
* @param {Promise} callback Called when the animation is done.
2020-08-01 16:31:42 +02:00
* @param {object} [options] Optional settings for the hide method.
2016-03-31 19:15:58 +02:00
*/
2024-01-01 15:38:08 +01:00
hide ( speed , callback , options = {}) {
2023-07-01 21:17:31 +02:00
let usedCallback = callback || function () {};
let usedOptions = options ;
2016-10-13 15:00:59 +02:00
if ( typeof callback === "object" ) {
2023-07-01 21:17:31 +02:00
Log . error ( "Parameter mismatch in module.hide: callback is not an optional parameter!" );
usedOptions = callback ;
usedCallback = function () {};
2016-10-13 15:00:59 +02:00
}
2020-05-11 22:22:32 +02:00
MM . hideModule (
2021-03-23 22:40:24 +01:00
this ,
2020-05-11 22:22:32 +02:00
speed ,
2021-03-23 22:40:24 +01:00
() => {
this . suspend ();
2023-07-01 21:17:31 +02:00
usedCallback ();
2020-05-11 22:22:32 +02:00
},
2023-07-01 21:17:31 +02:00
usedOptions
2020-05-11 22:22:32 +02:00
);
2026-05-12 20:01:12 +02:00
}
2016-03-31 19:15:58 +02:00
2020-07-28 16:48:03 +02:00
/**
2016-03-31 19:15:58 +02:00
* Show this module.
2020-07-28 16:48:03 +02:00
* @param {number} speed The speed of the show animation.
2025-09-23 06:27:29 +02:00
* @param {Promise} callback Called when the animation is done.
2020-08-01 16:31:42 +02:00
* @param {object} [options] Optional settings for the show method.
2016-03-31 19:15:58 +02:00
*/
2024-01-01 15:38:08 +01:00
show ( speed , callback , options ) {
2023-07-01 21:17:31 +02:00
let usedCallback = callback || function () {};
let usedOptions = options ;
2016-10-13 15:00:59 +02:00
if ( typeof callback === "object" ) {
2023-07-01 21:17:31 +02:00
Log . error ( "Parameter mismatch in module.show: callback is not an optional parameter!" );
usedOptions = callback ;
usedCallback = function () {};
2016-10-13 15:00:59 +02:00
}
2020-12-29 18:48:45 +01:00
MM . showModule (
this ,
speed ,
2021-02-06 21:22:13 +01:00
() => {
this . resume ();
2023-07-01 21:17:31 +02:00
usedCallback ();
2020-12-29 18:48:45 +01:00
},
2023-07-01 21:17:31 +02:00
usedOptions
2020-12-29 18:48:45 +01:00
);
2016-03-24 17:19:32 +01:00
}
2026-05-12 20:01:12 +02:00
}
2016-03-24 17:19:32 +01:00
2026-05-18 19:58:44 +02:00
globalThis . Module = Module ;
2020-12-29 18:48:45 +01:00
/**
2025-10-14 22:44:37 +02:00
* Merging MagicMirror² (or other) default/config script by `@bugsounet`
2020-12-29 18:48:45 +01:00
* Merge 2 objects or/with array
*
* Usage:
2020-09-21 23:17:41 +02:00
* -------
* this.config = configMerge({}, this.defaults, this.config)
* -------
2020-12-29 18:48:45 +01:00
* arg1: initial object
2020-09-21 23:17:41 +02:00
* arg2: config model
* arg3: config to merge
* -------
* why using it ?
* Object.assign() function don't to all job
* it don't merge all thing in deep
* -> object in object and array is not merging
* -------
2020-12-29 18:48:45 +01:00
*
* Todo: idea of Mich determinate what do you want to merge or not
* @param {object} result the initial object
* @returns {object} the merged config
2020-09-21 23:17:41 +02:00
*/
2024-01-01 15:38:08 +01:00
function configMerge ( result ) {
2021-03-23 22:40:24 +01:00
const stack = Array . prototype . slice . call ( arguments , 1 );
let item , key ;
2020-09-21 23:17:41 +02:00
while ( stack . length ) {
item = stack . shift ();
for ( key in item ) {
if ( item . hasOwnProperty ( key )) {
if ( typeof result [ key ] === "object" && result [ key ] && Object . prototype . toString . call ( result [ key ]) !== "[object Array]" ) {
if ( typeof item [ key ] === "object" && item [ key ] !== null ) {
result [ key ] = configMerge ({}, result [ key ], item [ key ]);
} else {
result [ key ] = item [ key ];
}
} else {
result [ key ] = item [ key ];
}
}
}
}
return result ;
2020-09-22 00:26:24 +02:00
}
2020-09-21 23:17:41 +02:00
2016-03-31 13:05:23 +02:00
Module . definitions = {};
2016-10-13 16:42:15 +02:00
Module . create = function ( name ) {
// Make sure module definition is available.
if ( ! Module . definitions [ name ]) {
return ;
}
2016-03-31 13:05:23 +02:00
2021-03-23 22:40:24 +01:00
const moduleDefinition = Module . definitions [ name ];
const clonedDefinition = cloneObject ( moduleDefinition );
2026-05-12 20:01:12 +02:00
const className = typeof name === "string" && name . trim () ? name : "AnonymousModule" ;
2016-03-31 13:05:23 +02:00
// Note that we clone the definition. Otherwise the objects are shared, which gives problems.
2026-05-12 20:01:12 +02:00
const SubClass = {
[ className ] : class extends Module {
constructor () {
super ();
Object . assign ( this , clonedDefinition );
if ( typeof this . init === "function" ) {
this . init ();
}
}
}
}[ className ];
2016-03-31 13:05:23 +02:00
2026-05-12 20:01:12 +02:00
return new SubClass ();
2016-03-31 13:05:23 +02:00
};
2020-07-28 16:48:03 +02:00
Module . register = function ( name , moduleDefinition ) {
if ( moduleDefinition . requiresVersion ) {
2023-04-04 20:44:32 +02:00
Log . log ( `Check MagicMirror² version for module ' ${ name } ' - Minimum version: ${ moduleDefinition . requiresVersion } - Current version: ${ window . mmVersion } ` );
2021-04-25 22:11:51 +02:00
if ( cmpVersions ( window . mmVersion , moduleDefinition . requiresVersion ) >= 0 ) {
2020-07-28 16:48:03 +02:00
Log . log ( "Version is ok!" );
} else {
2023-04-04 20:44:32 +02:00
Log . warn ( `Version is incorrect. Skip module: ' ${ name } '` );
2020-07-28 16:48:03 +02:00
return ;
}
}
2023-04-04 20:44:32 +02:00
Log . log ( `Module registered: ${ name } ` );
2020-07-28 16:48:03 +02:00
Module . definitions [ name ] = moduleDefinition ;
};
2020-07-27 14:24:30 +02:00
/**
2020-05-11 22:22:32 +02:00
* Compare two semantic version numbers and return the difference.
2020-07-27 14:24:30 +02:00
* @param {string} a Version number a.
* @param {string} b Version number b.
2020-07-28 16:48:03 +02:00
* @returns {number} A positive number if a is larger than b, a negative
* number if a is smaller and 0 if they are the same
2020-05-11 22:22:32 +02:00
*/
2026-05-18 19:58:44 +02:00
export function cmpVersions ( a , b ) {
2021-03-23 22:40:24 +01:00
const regExStrip0 = /(\.0+)+$/ ;
const segmentsA = a . replace ( regExStrip0 , "" ). split ( "." );
const segmentsB = b . replace ( regExStrip0 , "" ). split ( "." );
const l = Math . min ( segmentsA . length , segmentsB . length );
for ( let i = 0 ; i < l ; i ++ ) {
let diff = parseInt ( segmentsA [ i ], 10 ) - parseInt ( segmentsB [ i ], 10 );
2016-10-13 16:42:15 +02:00
if ( diff ) {
return diff ;
}
}
return segmentsA . length - segmentsB . length ;
}
2026-05-12 20:01:12 +02:00
/**
* Define the clone method for later use. Helper Method.
* @param {object} obj Object to be cloned
* @returns {object} the cloned object
*/
2026-05-18 19:58:44 +02:00
export function cloneObject ( obj ) {
2026-05-12 20:01:12 +02:00
if ( obj === null || typeof obj !== "object" ) {
return obj ;
}
if ( Array . isArray ( obj )) {
return obj . map (( item ) => cloneObject ( item ));
}
const tag = Object . prototype . toString . call ( obj );
if ( tag === "[object RegExp]" ) {
return new RegExp ( obj );
}
if ( tag === "[object Date]" ) {
return new Date ( obj . getTime ());
}
const proto = Object . getPrototypeOf ( obj );
const isPlainObject = proto === null || Object . getPrototypeOf ( proto ) === null ;
// Avoid calling class constructors without "new". Preserve unknown objects by reference.
if ( ! isPlainObject ) {
return obj ;
}
const temp = {};
for ( const key of Object . keys ( obj )) {
temp [ key ] = cloneObject ( obj [ key ]);
}
return temp ;
}