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/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 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-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 04b596f..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", @@ -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",