From bea948dd65b02765e4118adeae88976eaad73e4f Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Fri, 2 Jan 2026 00:29:53 +0100 Subject: [PATCH 1/3] chore: add demo configs --- README.md | 2 + cspell.config.json | 3 + demo.config.positional.js | 139 ++++++++++++++++++++++++++++++++++++++ demo.config.slides.js | 139 ++++++++++++++++++++++++++++++++++++++ eslint.config.mjs | 1 + package.json | 2 + 6 files changed, 286 insertions(+) create mode 100644 demo.config.positional.js create mode 100644 demo.config.slides.js diff --git a/README.md b/README.md index fb506e0..eff54bf 100644 --- a/README.md +++ b/README.md @@ -262,6 +262,8 @@ Please note that this project is released with a [Contributor Code of Conduct](C ### Developer commands - `npm install` - Install dependencies like ESLint and prettier. +- `node --run demo:slides` - Start MagicMirror with the slides demo configuration. +- `node --run demo:positional` - Start MagicMirror with the positional demo configuration - `node --run lint` - Run linting and formatter checks. - `node --run lint:fix` - Fix linting and formatter issues. - `node --run test` - Run linting and formatter checks + Run spelling check. diff --git a/cspell.config.json b/cspell.config.json index 92b8c51..2a6ebd2 100644 --- a/cspell.config.json +++ b/cspell.config.json @@ -12,6 +12,9 @@ "Kristjan", "mmmc", "newsfeed", + "updatenotification", + "openmeteo", + "evdev", "Pagger", "planetrise", "PLAYPAUSE", diff --git a/demo.config.positional.js b/demo.config.positional.js new file mode 100644 index 0000000..09a25fa --- /dev/null +++ b/demo.config.positional.js @@ -0,0 +1,139 @@ +const config = { + address: "0.0.0.0", + logLevel: [ + "INFO", + "LOG", + "WARN", + "ERROR", + "DEBUG" + ], + modules: [ + { + module: "alert" + }, + { + module: "updatenotification", + position: "top_bar" + }, + { + module: "clock", + position: "top_left" + }, + { + module: "calendar", + header: "US Holidays", + position: "top_left", + config: { + calendars: [ + { + fetchInterval: 7 * 24 * 60 * 60 * 1000, + symbol: "calendar-check", + url: "https://ics.calendarlabs.com/76/mm3137/US_Holidays.ics" + } + ] + } + }, + { + module: "compliments", + position: "lower_third" + }, + { + module: "weather", + position: "top_right", + config: { + weatherProvider: "openmeteo", + type: "current", + lat: 40.776676, + lon: -73.971321 + } + }, + { + module: "weather", + position: "top_right", + header: "Weather Forecast", + config: { + weatherProvider: "openmeteo", + type: "forecast", + lat: 40.776676, + lon: -73.971321 + } + }, + { + module: "newsfeed", + position: "bottom_bar", + config: { + feeds: [ + { + title: "New York Times", + url: "https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml" + } + ], + showSourceTitle: true, + showPublishDate: true, + broadcastNewsFeeds: true, + broadcastNewsUpdates: true + } + }, + { + module: "MMM-Remote-Control", + disabled: true, + config: { + secureEndpoints: false + } + }, + { + module: "MMM-SendNotificationButton", + position: "top_right" + + }, + + + { + module: "MMM-Carousel", + position: "bottom_bar", + config: { + transitionInterval: 10000, + showPageIndicators: false, + showPageControls: false, + ignoreModules: [ + "MMM-Remote-Control", + "MMM-SendNotificationButton" + ], + mode: "positional", + top_left: { + enabled: true, + ignoreModules: [], + overrideTransitionInterval: 8000 + }, + top_right: { + enabled: true, + ignoreModules: [], + overrideTransitionInterval: 12000 + }, + bottom_bar: { + enabled: true, + ignoreModules: [], + overrideTransitionInterval: 15000 + }, + keyBindings: { + enabled: true, + mode: "DEFAULT" + } + } + }, + { + module: "MMM-KeyBindings", + config: { + enableKeyboard: true, + evdev: { + enabled: false + } + } + } + ] +}; + +/** ************* DO NOT EDIT THE LINE BELOW */ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/demo.config.slides.js b/demo.config.slides.js new file mode 100644 index 0000000..09a25fa --- /dev/null +++ b/demo.config.slides.js @@ -0,0 +1,139 @@ +const config = { + address: "0.0.0.0", + logLevel: [ + "INFO", + "LOG", + "WARN", + "ERROR", + "DEBUG" + ], + modules: [ + { + module: "alert" + }, + { + module: "updatenotification", + position: "top_bar" + }, + { + module: "clock", + position: "top_left" + }, + { + module: "calendar", + header: "US Holidays", + position: "top_left", + config: { + calendars: [ + { + fetchInterval: 7 * 24 * 60 * 60 * 1000, + symbol: "calendar-check", + url: "https://ics.calendarlabs.com/76/mm3137/US_Holidays.ics" + } + ] + } + }, + { + module: "compliments", + position: "lower_third" + }, + { + module: "weather", + position: "top_right", + config: { + weatherProvider: "openmeteo", + type: "current", + lat: 40.776676, + lon: -73.971321 + } + }, + { + module: "weather", + position: "top_right", + header: "Weather Forecast", + config: { + weatherProvider: "openmeteo", + type: "forecast", + lat: 40.776676, + lon: -73.971321 + } + }, + { + module: "newsfeed", + position: "bottom_bar", + config: { + feeds: [ + { + title: "New York Times", + url: "https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml" + } + ], + showSourceTitle: true, + showPublishDate: true, + broadcastNewsFeeds: true, + broadcastNewsUpdates: true + } + }, + { + module: "MMM-Remote-Control", + disabled: true, + config: { + secureEndpoints: false + } + }, + { + module: "MMM-SendNotificationButton", + position: "top_right" + + }, + + + { + module: "MMM-Carousel", + position: "bottom_bar", + config: { + transitionInterval: 10000, + showPageIndicators: false, + showPageControls: false, + ignoreModules: [ + "MMM-Remote-Control", + "MMM-SendNotificationButton" + ], + mode: "positional", + top_left: { + enabled: true, + ignoreModules: [], + overrideTransitionInterval: 8000 + }, + top_right: { + enabled: true, + ignoreModules: [], + overrideTransitionInterval: 12000 + }, + bottom_bar: { + enabled: true, + ignoreModules: [], + overrideTransitionInterval: 15000 + }, + keyBindings: { + enabled: true, + mode: "DEFAULT" + } + } + }, + { + module: "MMM-KeyBindings", + config: { + enableKeyboard: true, + evdev: { + enabled: false + } + } + } + ] +}; + +/** ************* DO NOT EDIT THE LINE BELOW */ +if (typeof module !== "undefined") { + module.exports = config; +} diff --git a/eslint.config.mjs b/eslint.config.mjs index beebbcd..0ade6f4 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -39,6 +39,7 @@ export default defineConfig([ "sort-keys": "off" } }, + {"files": ["demo.config.js"], "rules": {"prefer-const": "off"}}, { "files": ["**/*.mjs"], "languageOptions": { diff --git a/package.json b/package.json index 04b596f..d5b7d48 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,8 @@ "main": "MMM-Carousel.js", "type": "module", "scripts": { + "demo:slides": "cd ../../ && MM_CONFIG_FILE=modules/MMM-Carousel/demo.config.slides.js node --run start:wayland:dev", + "demo:positional": "cd ../../ && MM_CONFIG_FILE=modules/MMM-Carousel/demo.config.positional.js node --run start:wayland:dev", "lint": "eslint && prettier --check .", "lint:fix": "eslint --fix && prettier --write .", "prepare": "simple-git-hooks", From 276c7770fb0fcc7a1180a50248e2cf0db9333d60 Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Fri, 2 Jan 2026 00:30:29 +0100 Subject: [PATCH 2/3] fix: resolve positional mode only rotating last position In positional mode, setUpTransitionTimers was called in a loop for each enabled position, but the context was stored in a single shared variable, causing each iteration to overwrite the previous position's context. Fixed by using closure-captured context in setInterval for each position, ensuring all enabled positions rotate independently. --- MMM-Carousel.js | 62 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/MMM-Carousel.js b/MMM-Carousel.js index 1faba14..df10115 100644 --- a/MMM-Carousel.js +++ b/MMM-Carousel.js @@ -251,6 +251,7 @@ Module.register("MMM-Carousel", { Log.info("[MMM-Carousel] Switched to manual mode - stopping automatic rotation"); this.updatePause(true); if (this.transitionTimer) { + clearInterval(this.transitionTimer); clearTimeout(this.transitionTimer); this.transitionTimer = null; } @@ -357,15 +358,49 @@ Module.register("MMM-Carousel", { */ setUpTransitionTimers (positionIndex) { const modules = this.getFilteredModules(positionIndex); - this.modulesContext = this.buildModulesContext(modules); + const ctx = this.buildModulesContext(modules); + ctx.positionIndex = positionIndex; + ctx.transitionInterval = this.getTransitionTimer(positionIndex); - // Initial transition - this.moduleTransition(); + if (positionIndex === null) { + // Global/slides mode: single context + this.modulesContext = ctx; - // Create bound function for manual/timed transitions - this.manualTransition = (goToIndex, goDirection, goToSlide) => { - this.moduleTransition(goToIndex, goDirection, goToSlide); - }; + // Initial transition + this.moduleTransition(); + + // Create bound function for manual/timed transitions + this.manualTransition = (goToIndex, goDirection, goToSlide) => { + this.moduleTransition(goToIndex, goDirection, goToSlide); + }; + } else { + /* + * Positional mode: use closure to capture ctx for this position + * Each position gets its own timer with its own context + */ + const transitionFn = () => { + const moduleCount = ctx.modules.length; + ctx.currentIndex = (ctx.currentIndex + 1) % moduleCount; + + Log.debug(`[MMM-Carousel] Position ${positionIndex}: transitioning to index ${ctx.currentIndex}`); + + // Hide all, then show current + for (const mod of ctx.modules) { + mod.hide(ctx.slideFadeOutSpeed, false, {lockString: "mmmc"}); + } + setTimeout(() => { + ctx.modules[ctx.currentIndex].show(ctx.slideFadeInSpeed, false, {lockString: "mmmc"}); + }, ctx.slideFadeOutSpeed); + }; + + // Initial transition + transitionFn(); + + // Start interval timer (captured in closure) + if (ctx.transitionInterval > 0) { + setInterval(transitionFn, ctx.transitionInterval); + } + } }, /** @@ -680,6 +715,14 @@ Module.register("MMM-Carousel", { this.updatePause(false); + /* + * Positional mode uses setInterval which auto-restarts + * Only global/slides mode needs manual restart + */ + if (this.config.mode === "positional") { + return; + } + // Get current index from context const currentIndex = this.modulesContext?.currentIndex || 0; this.scheduleNextTransition(currentIndex); @@ -691,6 +734,11 @@ Module.register("MMM-Carousel", { return; } + // Positional mode uses setInterval - pause/play not supported + if (this.config.mode === "positional") { + return; + } + // Check if a timer exists and toggle it if (this.transitionTimer) { // Timer is running - pause it From a0474132d2c9d3278a39442ea598f7b8b562f1ad Mon Sep 17 00:00:00 2001 From: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com> Date: Fri, 2 Jan 2026 00:30:58 +0100 Subject: [PATCH 3/3] chore(release): 0.7.2 --- .prettierignore | 3 ++- CHANGELOG.md | 11 +++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.prettierignore b/.prettierignore index 1b19cda..1d006b4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ *.js -*.mjs \ No newline at end of file +*.mjs +CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e273c1..d069053 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to this project will be documented in this file. +## [0.7.2](https://github.com/shbatm/MMM-Carousel/compare/v0.7.1...v0.7.2) (2026-01-01) + +### Bug Fixes + +- resolve positional mode only rotating last position ([effa34d](https://github.com/shbatm/MMM-Carousel/commit/effa34d17d1ccea774a2eebaa7ecdf58f83a6e82)) + +### Chores + +- add demo configs ([347779a](https://github.com/shbatm/MMM-Carousel/commit/347779a0af6a7ede25627eabcc852b1d0005b009)) +- **deps:** bump actions/checkout from 5 to 6 ([50bc8bf](https://github.com/shbatm/MMM-Carousel/commit/50bc8bf7649e8f495b3800983a0ef6fe553a17f1)) + ## [0.7.1](https://github.com/shbatm/MMM-Carousel/compare/v0.7.0...v0.7.1) (2026-01-01) ### Documentation diff --git a/package-lock.json b/package-lock.json index d67b747..75333ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mmm-carousel", - "version": "0.7.1", + "version": "0.7.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mmm-carousel", - "version": "0.7.1", + "version": "0.7.2", "license": "MIT", "devDependencies": { "@eslint/css": "^0.14.1", diff --git a/package.json b/package.json index d5b7d48..3b63abe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mmm-carousel", - "version": "0.7.1", + "version": "0.7.2", "description": "Displays a single MagicMirror² module at a time or in groups of slides, rotating through the list of configured modules in a carousel-like fashion.", "keywords": [ "magic",