123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615 |
- /* global Loader, defaults, Translator */
-
- /* Magic Mirror
- * Main System
- *
- * By Michael Teeuw https://michaelteeuw.nl
- * MIT Licensed.
- */
- const MM = (function () {
- let modules = [];
-
- /* Private Methods */
-
- /**
- * Create dom objects for all modules that are configured for a specific position.
- */
- const createDomObjects = function () {
- const domCreationPromises = [];
-
- modules.forEach(function (module) {
- if (typeof module.data.position !== "string") {
- return;
- }
-
- const wrapper = selectWrapper(module.data.position);
-
- const dom = document.createElement("div");
- dom.id = module.identifier;
- dom.className = module.name;
-
- if (typeof module.data.classes === "string") {
- dom.className = "module " + dom.className + " " + module.data.classes;
- }
-
- dom.opacity = 0;
- wrapper.appendChild(dom);
-
- const moduleHeader = document.createElement("header");
- moduleHeader.innerHTML = module.getHeader();
- moduleHeader.className = "module-header";
- dom.appendChild(moduleHeader);
-
- if (typeof module.getHeader() === "undefined" || module.getHeader() !== "") {
- moduleHeader.style.display = "none;";
- } else {
- moduleHeader.style.display = "block;";
- }
-
- const moduleContent = document.createElement("div");
- moduleContent.className = "module-content";
- dom.appendChild(moduleContent);
-
- const domCreationPromise = updateDom(module, 0);
- domCreationPromises.push(domCreationPromise);
- domCreationPromise
- .then(function () {
- sendNotification("MODULE_DOM_CREATED", null, null, module);
- })
- .catch(Log.error);
- });
-
- updateWrapperStates();
-
- Promise.all(domCreationPromises).then(function () {
- sendNotification("DOM_OBJECTS_CREATED");
- });
- };
-
- /**
- * Select the wrapper dom object for a specific position.
- *
- * @param {string} position The name of the position.
- * @returns {HTMLElement} the wrapper element
- */
- const selectWrapper = function (position) {
- const classes = position.replace("_", " ");
- const parentWrapper = document.getElementsByClassName(classes);
- if (parentWrapper.length > 0) {
- const wrapper = parentWrapper[0].getElementsByClassName("container");
- if (wrapper.length > 0) {
- return wrapper[0];
- }
- }
- };
-
- /**
- * Send a notification to all modules.
- *
- * @param {string} notification The identifier of the notification.
- * @param {*} payload The payload of the notification.
- * @param {Module} sender The module that sent the notification.
- * @param {Module} [sendTo] The (optional) module to send the notification to.
- */
- const sendNotification = function (notification, payload, sender, sendTo) {
- for (const m in modules) {
- const module = modules[m];
- if (module !== sender && (!sendTo || module === sendTo)) {
- module.notificationReceived(notification, payload, sender);
- }
- }
- };
-
- /**
- * 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.
- * @returns {Promise} Resolved when the dom is fully updated.
- */
- const updateDom = function (module, speed) {
- return new Promise(function (resolve) {
- const newHeader = module.getHeader();
- let newContentPromise = module.getDom();
-
- if (!(newContentPromise instanceof Promise)) {
- // convert to a promise if not already one to avoid if/else's everywhere
- newContentPromise = Promise.resolve(newContentPromise);
- }
-
- newContentPromise
- .then(function (newContent) {
- const updatePromise = updateDomWithContent(module, speed, newHeader, newContent);
-
- updatePromise.then(resolve).catch(Log.error);
- })
- .catch(Log.error);
- });
- };
-
- /**
- * Update the dom with the specified content
- *
- * @param {Module} module The module that needs an update.
- * @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.
- * @returns {Promise} Resolved when the module dom has been updated.
- */
- const updateDomWithContent = function (module, speed, newHeader, newContent) {
- return new Promise(function (resolve) {
- if (module.hidden || !speed) {
- updateModuleContent(module, newHeader, newContent);
- resolve();
- return;
- }
-
- if (!moduleNeedsUpdate(module, newHeader, newContent)) {
- resolve();
- return;
- }
-
- if (!speed) {
- updateModuleContent(module, newHeader, newContent);
- resolve();
- return;
- }
-
- hideModule(module, speed / 2, function () {
- updateModuleContent(module, newHeader, newContent);
- if (!module.hidden) {
- showModule(module, speed / 2);
- }
- resolve();
- });
- });
- };
-
- /**
- * Check if the content has changed.
- *
- * @param {Module} module The module to check.
- * @param {string} newHeader The new header that is generated.
- * @param {HTMLElement} newContent The new content that is generated.
- * @returns {boolean} True if the module need an update, false otherwise
- */
- const moduleNeedsUpdate = function (module, newHeader, newContent) {
- const moduleWrapper = document.getElementById(module.identifier);
- if (moduleWrapper === null) {
- return false;
- }
-
- const contentWrapper = moduleWrapper.getElementsByClassName("module-content");
- const headerWrapper = moduleWrapper.getElementsByClassName("module-header");
-
- let headerNeedsUpdate = false;
- let contentNeedsUpdate;
-
- if (headerWrapper.length > 0) {
- headerNeedsUpdate = newHeader !== headerWrapper[0].innerHTML;
- }
-
- const tempContentWrapper = document.createElement("div");
- tempContentWrapper.appendChild(newContent);
- contentNeedsUpdate = tempContentWrapper.innerHTML !== contentWrapper[0].innerHTML;
-
- return headerNeedsUpdate || contentNeedsUpdate;
- };
-
- /**
- * Update the content of a module on screen.
- *
- * @param {Module} module The module to check.
- * @param {string} newHeader The new header that is generated.
- * @param {HTMLElement} newContent The new content that is generated.
- */
- const updateModuleContent = function (module, newHeader, newContent) {
- const moduleWrapper = document.getElementById(module.identifier);
- if (moduleWrapper === null) {
- return;
- }
- const headerWrapper = moduleWrapper.getElementsByClassName("module-header");
- const contentWrapper = moduleWrapper.getElementsByClassName("module-content");
-
- contentWrapper[0].innerHTML = "";
- contentWrapper[0].appendChild(newContent);
-
- headerWrapper[0].innerHTML = newHeader;
- if (headerWrapper.length > 0 && newHeader) {
- headerWrapper[0].style.display = "block";
- } else {
- headerWrapper[0].style.display = "none";
- }
- };
-
- /**
- * Hide the module.
- *
- * @param {Module} module The module to hide.
- * @param {number} speed The speed of the hide animation.
- * @param {Function} callback Called when the animation is done.
- * @param {object} [options] Optional settings for the hide method.
- */
- const hideModule = function (module, speed, callback, options) {
- options = options || {};
-
- // set lockString if set in options.
- if (options.lockString) {
- // Log.log("Has lockstring: " + options.lockString);
- if (module.lockStrings.indexOf(options.lockString) === -1) {
- module.lockStrings.push(options.lockString);
- }
- }
-
- const moduleWrapper = document.getElementById(module.identifier);
- if (moduleWrapper !== null) {
- moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
- moduleWrapper.style.opacity = 0;
-
- 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";
-
- updateWrapperStates();
-
- if (typeof callback === "function") {
- callback();
- }
- }, speed);
- } else {
- // invoke callback even if no content, issue 1308
- if (typeof callback === "function") {
- callback();
- }
- }
- };
-
- /**
- * Show the module.
- *
- * @param {Module} module The module to show.
- * @param {number} speed The speed of the show animation.
- * @param {Function} callback Called when the animation is done.
- * @param {object} [options] Optional settings for the show method.
- */
- const showModule = function (module, speed, callback, options) {
- options = options || {};
-
- // remove lockString if set in options.
- if (options.lockString) {
- const index = module.lockStrings.indexOf(options.lockString);
- if (index !== -1) {
- module.lockStrings.splice(index, 1);
- }
- }
-
- // Check if there are no more lockstrings set, or the force option is set.
- // Otherwise cancel show action.
- if (module.lockStrings.length !== 0 && options.force !== true) {
- Log.log("Will not show " + module.name + ". LockStrings active: " + module.lockStrings.join(","));
- if (typeof options.onError === "function") {
- options.onError(new Error("LOCK_STRING_ACTIVE"));
- }
- return;
- }
-
- module.hidden = false;
-
- // If forced show, clean current lockstrings.
- if (module.lockStrings.length !== 0 && options.force === true) {
- Log.log("Force show of module: " + module.name);
- module.lockStrings = [];
- }
-
- const moduleWrapper = document.getElementById(module.identifier);
- if (moduleWrapper !== null) {
- moduleWrapper.style.transition = "opacity " + speed / 1000 + "s";
- // Restore the position. See hideModule() for more info.
- moduleWrapper.style.position = "static";
-
- updateWrapperStates();
-
- // Waiting for DOM-changes done in updateWrapperStates before we can start the animation.
- const dummy = moduleWrapper.parentElement.parentElement.offsetHeight;
- moduleWrapper.style.opacity = 1;
-
- clearTimeout(module.showHideTimer);
- module.showHideTimer = setTimeout(function () {
- if (typeof callback === "function") {
- callback();
- }
- }, speed);
- } else {
- // invoke callback
- if (typeof callback === "function") {
- callback();
- }
- }
- };
-
- /**
- * Checks for all positions if it has visible content.
- * If not, if will hide the position to prevent unwanted margins.
- * This method should be called by the show and hide methods.
- *
- * Example:
- * If the top_bar only contains the update notification. And no update is available,
- * the update notification is hidden. The top bar still occupies space making for
- * an ugly top margin. By using this function, the top bar will be hidden if the
- * update notification is not visible.
- */
- const updateWrapperStates = function () {
- const positions = ["top_bar", "top_left", "top_center", "top_right", "upper_third", "middle_center", "lower_third", "bottom_left", "bottom_center", "bottom_right", "bottom_bar", "fullscreen_above", "fullscreen_below"];
-
- positions.forEach(function (position) {
- const wrapper = selectWrapper(position);
- const moduleWrappers = wrapper.getElementsByClassName("module");
-
- let showWrapper = false;
- Array.prototype.forEach.call(moduleWrappers, function (moduleWrapper) {
- if (moduleWrapper.style.position === "" || moduleWrapper.style.position === "static") {
- showWrapper = true;
- }
- });
-
- wrapper.style.display = showWrapper ? "block" : "none";
- });
- };
-
- /**
- * Loads the core config and combines it with the system defaults.
- */
- const loadConfig = function () {
- // FIXME: Think about how to pass config around without breaking tests
- /* eslint-disable */
- if (typeof config === "undefined") {
- config = defaults;
- Log.error("Config file is missing! Please create a config file.");
- return;
- }
-
- config = Object.assign({}, defaults, config);
- /* eslint-enable */
- };
-
- /**
- * Adds special selectors on a collection of modules.
- *
- * @param {Module[]} modules Array of modules.
- */
- const setSelectionMethodsForModules = function (modules) {
- /**
- * Filter modules with the specified classes.
- *
- * @param {string|string[]} className one or multiple classnames (array or space divided).
- * @returns {Module[]} Filtered collection of modules.
- */
- const withClass = function (className) {
- return modulesByClass(className, true);
- };
-
- /**
- * Filter modules without the specified classes.
- *
- * @param {string|string[]} className one or multiple classnames (array or space divided).
- * @returns {Module[]} Filtered collection of modules.
- */
- const exceptWithClass = function (className) {
- return modulesByClass(className, false);
- };
-
- /**
- * Filters a collection of modules based on classname(s).
- *
- * @param {string|string[]} className one or multiple classnames (array or space divided).
- * @param {boolean} include if the filter should include or exclude the modules with the specific classes.
- * @returns {Module[]} Filtered collection of modules.
- */
- const modulesByClass = function (className, include) {
- let searchClasses = className;
- if (typeof className === "string") {
- searchClasses = className.split(" ");
- }
-
- const newModules = modules.filter(function (module) {
- const classes = module.data.classes.toLowerCase().split(" ");
-
- for (const searchClass of searchClasses) {
- if (classes.indexOf(searchClass.toLowerCase()) !== -1) {
- return include;
- }
- }
-
- return !include;
- });
-
- setSelectionMethodsForModules(newModules);
- return newModules;
- };
-
- /**
- * Removes a module instance from the collection.
- *
- * @param {object} module The module instance to remove from the collection.
- * @returns {Module[]} Filtered collection of modules.
- */
- const exceptModule = function (module) {
- const newModules = modules.filter(function (mod) {
- return mod.identifier !== module.identifier;
- });
-
- setSelectionMethodsForModules(newModules);
- return newModules;
- };
-
- /**
- * Walks thru a collection of modules and executes the callback with the module as an argument.
- *
- * @param {Function} callback The function to execute with the module as an argument.
- */
- const enumerate = function (callback) {
- modules.map(function (module) {
- callback(module);
- });
- };
-
- if (typeof modules.withClass === "undefined") {
- Object.defineProperty(modules, "withClass", { value: withClass, enumerable: false });
- }
- if (typeof modules.exceptWithClass === "undefined") {
- Object.defineProperty(modules, "exceptWithClass", { value: exceptWithClass, enumerable: false });
- }
- if (typeof modules.exceptModule === "undefined") {
- Object.defineProperty(modules, "exceptModule", { value: exceptModule, enumerable: false });
- }
- if (typeof modules.enumerate === "undefined") {
- Object.defineProperty(modules, "enumerate", { value: enumerate, enumerable: false });
- }
- };
-
- return {
- /* Public Methods */
-
- /**
- * Main init method.
- */
- init: function () {
- Log.info("Initializing MagicMirror.");
- loadConfig();
-
- Log.setLogLevel(config.logLevel);
-
- Translator.loadCoreTranslations(config.language);
- Loader.loadModules();
- },
-
- /**
- * Gets called when all modules are started.
- *
- * @param {Module[]} moduleObjects All module instances.
- */
- modulesStarted: function (moduleObjects) {
- modules = [];
- moduleObjects.forEach((module) => modules.push(module));
-
- Log.info("All modules started!");
- sendNotification("ALL_MODULES_STARTED");
-
- createDomObjects();
- },
-
- /**
- * Send a notification to all modules.
- *
- * @param {string} notification The identifier of the notification.
- * @param {*} payload The payload of the notification.
- * @param {Module} sender The module that sent the notification.
- */
- sendNotification: function (notification, payload, sender) {
- if (arguments.length < 3) {
- Log.error("sendNotification: Missing arguments.");
- return;
- }
-
- if (typeof notification !== "string") {
- Log.error("sendNotification: Notification should be a string.");
- return;
- }
-
- if (!(sender instanceof Module)) {
- Log.error("sendNotification: Sender should be a module.");
- return;
- }
-
- // Further implementation is done in the private method.
- sendNotification(notification, payload, sender);
- },
-
- /**
- * 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.
- */
- updateDom: function (module, speed) {
- if (!(module instanceof Module)) {
- Log.error("updateDom: Sender should be a module.");
- return;
- }
-
- if (!module.data.position) {
- Log.warn("module tries to update the DOM without being displayed.");
- return;
- }
-
- // Further implementation is done in the private method.
- updateDom(module, speed);
- },
-
- /**
- * Returns a collection of all modules currently active.
- *
- * @returns {Module[]} A collection of all modules currently active.
- */
- getModules: function () {
- setSelectionMethodsForModules(modules);
- return modules;
- },
-
- /**
- * Hide the module.
- *
- * @param {Module} module The module to hide.
- * @param {number} speed The speed of the hide animation.
- * @param {Function} callback Called when the animation is done.
- * @param {object} [options] Optional settings for the hide method.
- */
- hideModule: function (module, speed, callback, options) {
- module.hidden = true;
- hideModule(module, speed, callback, options);
- },
-
- /**
- * Show the module.
- *
- * @param {Module} module The module to show.
- * @param {number} speed The speed of the show animation.
- * @param {Function} callback Called when the animation is done.
- * @param {object} [options] Optional settings for the show method.
- */
- showModule: function (module, speed, callback, options) {
- // do not change module.hidden yet, only if we really show it later
- showModule(module, speed, callback, options);
- }
- };
- })();
-
- // Add polyfill for Object.assign.
- if (typeof Object.assign !== "function") {
- (function () {
- Object.assign = function (target) {
- "use strict";
- if (target === undefined || target === null) {
- throw new TypeError("Cannot convert undefined or null to object");
- }
- const output = Object(target);
- for (let index = 1; index < arguments.length; index++) {
- const source = arguments[index];
- if (source !== undefined && source !== null) {
- for (const nextKey in source) {
- if (source.hasOwnProperty(nextKey)) {
- output[nextKey] = source[nextKey];
- }
- }
- }
- }
- return output;
- };
- })();
- }
-
- MM.init();
|