Skip to content

Commit

Permalink
add support for custom regions, by detecting what is used in index.ht…
Browse files Browse the repository at this point in the history
…ml (#3518)

read index.html to discover the regions used, make them the list checked
by app.js and check:config test

fixes #3504   supercedes #3506 

no config.js param required
  • Loading branch information
sdetweil authored Aug 27, 2024
1 parent 5673678 commit 2b97e0d
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 29 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ _This release is scheduled to be released on 2024-10-01._

- [weather] Updated `apiVersion` default from 2.5 to 3.0 (#3424)
- [core] Updated dependencies including stylistic-eslint
- [core] Allow custom module positions by setting `allowCustomModulePositions` in `config.js` (fixes #3504, related to https://github.com/MagicMirrorOrg/MagicMirror/pull/3445)
- [core] Updated SocketIO catch all to new API
- [core] Allow custom modules positions by scanning index.html for the defined regions, instead of hard coded(fixes #3504)

### Fixed

Expand Down
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<script type="text/javascript" src="js/loader.js"></script>
<script type="text/javascript" src="js/socketclient.js"></script>
<script type="text/javascript" src="js/animateCSS.js"></script>
<script type="text/javascript" src="js/positions.js"></script>
<script type="text/javascript" src="js/main.js"></script>
</body>
</html>
7 changes: 5 additions & 2 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,9 @@ function App () {

Log.setLogLevel(config.logLevel);

// get the used module positions
Utils.getModulePositions();

let modules = [];
for (const module of config.modules) {
if (module.disabled) continue;
Expand All @@ -266,10 +269,10 @@ function App () {
modules.push(module.module);
}
} else {
Log.warn("Invalid module position found for this configuration:", module);
Log.warn("Invalid module position found for this configuration:" + `\n${JSON.stringify(module, null, 2)}`);
}
} else {
Log.warn("No module name found for this configuration:", module);
Log.warn("No module name found for this configuration:" + `\n${JSON.stringify(module, null, 2)}`);
}
}

Expand Down
22 changes: 5 additions & 17 deletions js/check_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const ajv = new Ajv();

const rootPath = path.resolve(`${__dirname}/../`);
const Log = require(`${rootPath}/js/logger.js`);
const Utils = require(`${rootPath}/js/utils.js`);

/**
* Returns a string with path of configuration file.
Expand Down Expand Up @@ -68,6 +69,7 @@ function checkConfigFile () {

Log.info("Checking modules structure configuration... ");

const position_list = Utils.getModulePositions();
// Make Ajv schema confguration of modules config
// only scan "module" and "position"
const schema = {
Expand All @@ -83,21 +85,7 @@ function checkConfigFile () {
},
position: {
type: "string",
enum: [
"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"
]
enum: position_list
}
},
required: ["module"]
Expand All @@ -116,10 +104,10 @@ function checkConfigFile () {
let position = validate.errors[0].instancePath.split("/")[3];

Log.error(colors.red("This module configuration contains errors:"));
Log.error(data.modules[module]);
Log.error(`\n${JSON.stringify(data.modules[module], null, 2)}`);
if (position) {
Log.error(colors.red(`${position}: ${validate.errors[0].message}`));
Log.error(validate.errors[0].params.allowedValues);
Log.error(`\n${JSON.stringify(validate.errors[0].params.allowedValues, null, 2).slice(1, -1)}`);
} else {
Log.error(colors.red(validate.errors[0].message));
}
Expand Down
1 change: 0 additions & 1 deletion js/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ const defaults = {
// (interval 30 seconds). If startup-timestamp has changed the client reloads the magicmirror webpage.
checkServerInterval: 30 * 1000,
reloadAfterServerRestart: false,
allowCustomModulePositions: false,

modules: [
{
Expand Down
2 changes: 1 addition & 1 deletion js/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const Loader = (function () {
* @returns {object[]} module data as configured in config
*/
const getAllModules = function () {
const AllModules = config.modules.filter((module) => (module.module !== undefined) && (MM.getAvailableModulePositions.indexOf(module.position) > -1 || typeof (module.position) === "undefined" || config.allowCustomModulePositions));
const AllModules = config.modules.filter((module) => (module.module !== undefined) && (MM.getAvailableModulePositions.indexOf(module.position) > -1 || typeof (module.position) === "undefined"));
return AllModules;
};

Expand Down
3 changes: 1 addition & 2 deletions js/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global Loader, defaults, Translator, addAnimateCSS, removeAnimateCSS, AnimateCSSIn, AnimateCSSOut */
/* global Loader, defaults, Translator, addAnimateCSS, removeAnimateCSS, AnimateCSSIn, AnimateCSSOut, modulePositions */

const MM = (function () {
let modules = [];
Expand Down Expand Up @@ -450,7 +450,6 @@ const MM = (function () {
* an ugly top margin. By using this function, the top bar will be hidden if the
* update notification is not visible.
*/
const modulePositions = ["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"];

const updateWrapperStates = function () {
modulePositions.forEach(function (position) {
Expand Down
35 changes: 32 additions & 3 deletions js/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
const execSync = require("node:child_process").execSync;
const Log = require("logger");
const path = require("node:path");

const rootPath = path.resolve(`${__dirname}/../`);
const Log = require(`${rootPath}/js/logger.js`);
const os = require("node:os");
const fs = require("node:fs");
const si = require("systeminformation");

const modulePositions = []; // will get list from index.html
const regionRegEx = /"region ([^"]*)/i;
const indexFileName = "index.html";
const discoveredPositionsJSFilename = "js/positions.js";

module.exports = {

async logSystemInformation () {
Expand Down Expand Up @@ -29,13 +39,32 @@ module.exports = {

// return all available module positions
getAvailableModulePositions () {
return ["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"];
return modulePositions;
},

// return if postion is on modulePositions Array (true/false)
moduleHasValidPosition (position) {
if (config.allowCustomModulePositions) return true;
if (this.getAvailableModulePositions().indexOf(position) === -1) return false;
return true;
},

getModulePositions () {
// get the lines of the index.html
const lines = fs.readFileSync(indexFileName).toString().split(os.EOL);
// loop thru the lines
lines.forEach((line) => {
// run the regex on each line
const results = regionRegEx.exec(line);
// if the regex returned something
if (results && results.length > 0) {
// get the postition parts and replace space with underscore
const positionName = results[1].replace(" ", "_");
// add it to the list
modulePositions.push(positionName);
}
});
fs.writeFileSync(discoveredPositionsJSFilename, `const modulePositions=${JSON.stringify(modulePositions)}`);
// return the list to the caller
return modulePositions;
}
};
23 changes: 23 additions & 0 deletions tests/configs/customregions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
let config = {
modules:
// Using exotic content. This is why don't accept go to JSON configuration file
(() => {
let positions = ["row3_left", "top3_left1"];
let modules = Array();
for (let idx in positions) {
modules.push({
module: "helloworld",
position: positions[idx],
config: {
text: `Text in ${positions[idx]}`
}
});
}
return modules;
})()
};

/*************** DO NOT EDIT THE LINE BELOW ***************/
if (typeof module !== "undefined") {
module.exports = config;
}
30 changes: 30 additions & 0 deletions tests/e2e/custom_module_regions_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const helpers = require("./helpers/global-setup");

describe("Custom Position of modules", () => {
beforeAll(async () => {
await helpers.fixupIndex();
await helpers.startApplication("tests/configs/customregions.js");
await helpers.getDocument();
});
afterAll(async () => {
await helpers.stopApplication();
await helpers.restoreIndex();
});

const positions = ["row3_left", "top3_left1"];
let i = 0;
const className1 = positions[i].replace("_", ".");
let message1 = positions[i];
it(`should show text in ${message1}`, async () => {
const elem = await helpers.waitForElement(`.${className1}`);
expect(elem).not.toBeNull();
expect(elem.textContent).toContain(`Text in ${message1}`);
});
i = 1;
const className2 = positions[i].replace("_", ".");
let message2 = positions[i];
it(`should NOT show text in ${message2}`, async () => {
const elem = await helpers.waitForElement(`.${className2}`, "", 1500);
expect(elem).toBeNull();
}, 1510);
});
57 changes: 55 additions & 2 deletions tests/e2e/helpers/global-setup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
const os = require("node:os");
const fs = require("node:fs");
const jsdom = require("jsdom");

const indexFile = `${__dirname}/../../../index.html`;
const cssFile = `${__dirname}/../../../css/custom.css`;
const sampleCss = [
".region.row3 {",
" top: 0;",
"}",
".region.row3.left {",
" top: 100%;",
"}"
];
var indexData = [];
var cssData = [];

exports.startApplication = async (configFilename, exec) => {
jest.resetModules();
if (global.app) {
Expand Down Expand Up @@ -45,11 +60,12 @@ exports.getDocument = () => {
});
};

exports.waitForElement = (selector, ignoreValue = "") => {
exports.waitForElement = (selector, ignoreValue = "", timeout = 0) => {
return new Promise((resolve) => {
let oldVal = "dummy12345";
let element = null;
const interval = setInterval(() => {
const element = document.querySelector(selector);
element = document.querySelector(selector);
if (element) {
let newVal = element.textContent;
if (newVal === oldVal) {
Expand All @@ -64,6 +80,12 @@ exports.waitForElement = (selector, ignoreValue = "") => {
}
}
}, 100);
if (timeout !== 0) {
setTimeout(() => {
if (interval) clearInterval(interval);
resolve(null);
}, timeout);
}
});
};

Expand Down Expand Up @@ -91,3 +113,34 @@ exports.testMatch = async (element, regex) => {
expect(elem.textContent).toMatch(regex);
return true;
};

exports.fixupIndex = async () => {
// read and save the git level index file
indexData = (await fs.promises.readFile(indexFile)).toString();
// make lines of the content
let workIndexLines = indexData.split(os.EOL);
// loop thru the lines to find place to insert new region
for (let l in workIndexLines) {
if (workIndexLines[l].includes("region top right")) {
// insert a new line with new region definition
workIndexLines.splice(l, 0, " <div class=\"region row3 left\"><div class=\"container\"></div></div>");
break;
}
}
// write out the new index.html file, not append
await fs.promises.writeFile(indexFile, workIndexLines.join(os.EOL), { flush: true });
// read in the current custom.css
cssData = (await fs.promises.readFile(cssFile)).toString();
// write out the custom.css for this testcase, matching the new region name
await fs.promises.writeFile(cssFile, sampleCss.join(os.EOL), { flush: true });
};

exports.restoreIndex = async () => {
// if we read in data
if (indexData.length > 1) {
//write out saved index.html
await fs.promises.writeFile(indexFile, indexData, { flush: true });
// write out saved custom.css
await fs.promises.writeFile(cssFile, cssData, { flush: true });
}
};

0 comments on commit 2b97e0d

Please sign in to comment.