From 0e4b589a3550af1d05dfc497f10f5c1c60d89d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20K=C3=BCthe?= Date: Sun, 4 Oct 2020 00:11:47 +0200 Subject: [PATCH 01/21] updating to new url and options. added notification --- MMM-RandomPhoto.css | 5 ++ MMM-RandomPhoto.js | 128 ++++++++++++++++++++++++++++---------------- README.md | 37 +++++++------ package.json | 4 +- 4 files changed, 109 insertions(+), 65 deletions(-) create mode 100644 MMM-RandomPhoto.css diff --git a/MMM-RandomPhoto.css b/MMM-RandomPhoto.css new file mode 100644 index 0000000..221ed9c --- /dev/null +++ b/MMM-RandomPhoto.css @@ -0,0 +1,5 @@ +img { + opacity:0; + position:fixed; + top:0; +} \ No newline at end of file diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index 89e2cb8..a17e865 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -4,55 +4,89 @@ * Module: MMM-RandomPhoto * * By Diego Vieira + * and skuethe * ICS Licensed. */ Module.register("MMM-RandomPhoto",{ - defaults: { - opacity: 0.3, - animationSpeed: 500, - updateInterval: 60, - url: 'https://unsplash.it/1920/1080/?random' - }, - - start: function() { - this.load(); - }, - - load: function() { - var self = this; - - var url = self.config.url + (self.config.url.indexOf('?') > -1 ? '&' : '?') + (new Date().getTime()); - var img = $('').attr('src', url); - - img.on('load', function() { - $('#mmm-photos-placeholder1').attr('src', url).animate({ - opacity: self.config.opacity - }, self.config.animationSpeed, function() { - $(this).attr('id', 'mmm-photos-placeholder2'); - }); - - $('#mmm-photos-placeholder2').animate({ - opacity: 0 - }, self.config.animationSpeed, function() { - $(this).attr('id', 'mmm-photos-placeholder1'); - }); - }); - - setTimeout(function() { - self.load(); - }, (self.config.updateInterval * 1000)); - - }, - - getDom: function() { - var wrapper = document.createElement("div"); - wrapper.innerHTML = ''; - return wrapper; - }, - getScripts: function() { - return [ - this.file('node_modules/jquery/dist/jquery.min.js') - ] - } + defaults: { + opacity: 0.3, + animationSpeed: 500, + updateInterval: 60, + url: 'https://picsum.photos/', + width: 1920, + height: 1080, + grayscale: false, + blur: false, + blurAmount: 1, // between 1 and 10 + }, + + start: function() { + this.updateTimer = null; + this.load(); + }, + + load: function() { + var self = this; + + var url = self.config.url + self.config.width + "/" + self.config.height + "/" + if(grayscale) { + url = url + (url.indexOf('?') > -1 ? '&' : '?') + "grayscale"; + } + if(blur) { + url = url + (url.indexOf('?') > -1 ? '&' : '?') + "blur"; + if(blurAmount > 1) { + if(blurAmount > 10) { blurAmount = 10; } + url = url + "=" + blurAmount; + } + } + url = url + (url.indexOf('?') > -1 ? '&' : '?') + (new Date().getTime()); + var img = $('').attr('src', url); + + img.on('load', function() { + $('#mmm-photos-placeholder1').attr('src', url).animate({ + opacity: self.config.opacity + }, self.config.animationSpeed, function() { + $(this).attr('id', 'mmm-photos-placeholder2'); + }); + + $('#mmm-photos-placeholder2').animate({ + opacity: 0 + }, self.config.animationSpeed, function() { + $(this).attr('id', 'mmm-photos-placeholder1'); + }); + }); + + this.updateTimer = setTimeout(function() { + self.load(); + }, (self.config.updateInterval * 1000)); + + }, + + getDom: function() { + var wrapper = document.createElement("div"); + wrapper.id = "randomPhoto"; + wrapper.innerHTML = ''; + return wrapper; + }, + + getScripts: function() { + return [ + this.file('node_modules/jquery/dist/jquery.min.js') + ] + } + + getStyles: function() { + return [ + "MMM-RandomPhoto.css" + ]; + }, + + socketNotificationReceived: function(notification, payload) { + if (notification === "RANDOMPHOTO_NEXT"){ + clearTimeout(this.updateTimer); + this.load(); + } + } + }); diff --git a/README.md b/README.md index c18e7db..e032a06 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,8 @@ -# No support -I don't support this module anymore. Few free to fork and modify it if you want to use it or fork another fork. - # MMM-RandomPhoto This a module for the [MagicMirror](https://github.com/MichMich/MagicMirror). It will show a random photo from an url. ## Installation -1. Navigate into your MagicMirror's `modules` folder and execute `git clone https://github.com/diego-vieira/MMM-RandomPhoto.git`. +1. Navigate into your MagicMirror's `modules` folder and execute `git clone https://github.com/skuethe/MMM-RandomPhoto.git`. 2. cd `cd MMM-RandomPhoto` 3. Execute `npm install` to install the node dependencies. @@ -15,22 +12,29 @@ The entry in `config.js` can include the following options: |Option|Description| |---|---| -|`opacity`|The opacity of the image.

**Type:** `double`
Default 0.3| -|`animationSpeed`|How long the fade out and fade in of photos should take.

**Type:** `int`
Default 500| -|`updateInterval`|How long before getting a new image.

**Type:** `int`
Default 60 seconds| -|`url`|URL to pull a new image from.

**Type:** `string`
Default https://unsplash.it/1920/1080/?random| +|`opacity`|The opacity of the image.

**Type:** `double`
Default `0.3`| +|`animationSpeed`|How long the fade out and fade in of photos should take.

**Type:** `int`
Default `500`| +|`updateInterval`|How long before getting a new image.

**Type:** `int`
Default `60` seconds| +|`url`|URL to pull a new image from.

**Type:** `string`
Default `https://picsum.photos/`| +|`width`|The width of the image.

**Type:** `int`
Default `1920` px| +|`height`|The height of the image.

**Type:** `int`
Default `1080` px| +|`grayscale`|Should the image be grayscaled?

**Type:** `boolean`
Default `false`| +|`blur`|Should the image be blurred?

**Type:** `boolean`
Default `false`| +|`blurAmount`|If you want to blur it, how much? Allows a number between `1` and `10`.

**Type:** `int`
Default `1`| Here is an example of an entry in `config.js` ``` { - module: 'MMM-RandomPhoto', - position: 'fullscreen_below', - config: { - opacity: 0.3, - animationSpeed: 500, - updateInterval: 60, - url: 'https://unsplash.it/1920/1080/?random' - } + module: 'MMM-RandomPhoto', + position: 'fullscreen_below', + config: { + opacity: 0.3, + animationSpeed: 500, + updateInterval: 60, + width: 1920, + height: 1080, + grayscale: true, + } }, ``` @@ -39,3 +43,4 @@ Here is an example of an entry in `config.js` ## Special Thanks - [Michael Teeuw](https://github.com/MichMich) for creating the awesome [MagicMirror2](https://github.com/MichMich/MagicMirror) project that made this module possible. +- [Diego Vieira](https://github.com/diego-vieira) for [initially](https://github.com/diego-vieira/MMM-RandomPhoto) creating this module. \ No newline at end of file diff --git a/package.json b/package.json index ce8a646..25e3c20 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "magic-mirror-module-random-photo", - "version": "1.0.0", + "version": "1.0.1", "description": "Show a random photo from an url", "main": "MMM-RandomPhoto.js", - "author": "Diego Vieira", + "author": "skuethe", "license": "ISC", "dependencies": { "jquery": "^3.1.0" From b746e5b851c3d4c651992e612551a0250be536eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20K=C3=BCthe?= Date: Sun, 4 Oct 2020 00:51:29 +0200 Subject: [PATCH 02/21] fixing stuff. adding hidden start option --- MMM-RandomPhoto.css | 2 +- MMM-RandomPhoto.js | 43 +++++++++++++++++++++++++++++-------------- README.md | 12 ++++++++++++ 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/MMM-RandomPhoto.css b/MMM-RandomPhoto.css index 221ed9c..c528981 100644 --- a/MMM-RandomPhoto.css +++ b/MMM-RandomPhoto.css @@ -1,4 +1,4 @@ -img { +#randomPhoto img { opacity:0; position:fixed; top:0; diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index a17e865..eada888 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -19,41 +19,46 @@ Module.register("MMM-RandomPhoto",{ grayscale: false, blur: false, blurAmount: 1, // between 1 and 10 + startHidden: false, }, start: function() { this.updateTimer = null; - this.load(); + if(this.config.startHidden) { + this.hide(); + } else { + this.load(); + } }, load: function() { var self = this; var url = self.config.url + self.config.width + "/" + self.config.height + "/" - if(grayscale) { + if(self.config.grayscale) { url = url + (url.indexOf('?') > -1 ? '&' : '?') + "grayscale"; } - if(blur) { + if(self.config.blur) { url = url + (url.indexOf('?') > -1 ? '&' : '?') + "blur"; - if(blurAmount > 1) { - if(blurAmount > 10) { blurAmount = 10; } - url = url + "=" + blurAmount; + if(self.config.blurAmount > 1) { + if(self.config.blurAmount > 10) { self.config.blurAmount = 10; } + url = url + "=" + self.config.blurAmount; } } url = url + (url.indexOf('?') > -1 ? '&' : '?') + (new Date().getTime()); var img = $('').attr('src', url); img.on('load', function() { - $('#mmm-photos-placeholder1').attr('src', url).animate({ + $('#randomPhoto-placeholder1').attr('src', url).animate({ opacity: self.config.opacity }, self.config.animationSpeed, function() { - $(this).attr('id', 'mmm-photos-placeholder2'); + $(this).attr('id', 'randomPhoto-placeholder2'); }); - $('#mmm-photos-placeholder2').animate({ + $('#randomPhoto-placeholder2').animate({ opacity: 0 }, self.config.animationSpeed, function() { - $(this).attr('id', 'mmm-photos-placeholder1'); + $(this).attr('id', 'randomPhoto-placeholder1'); }); }); @@ -66,7 +71,7 @@ Module.register("MMM-RandomPhoto",{ getDom: function() { var wrapper = document.createElement("div"); wrapper.id = "randomPhoto"; - wrapper.innerHTML = ''; + wrapper.innerHTML = ''; return wrapper; }, @@ -74,7 +79,7 @@ Module.register("MMM-RandomPhoto",{ return [ this.file('node_modules/jquery/dist/jquery.min.js') ] - } + }, getStyles: function() { return [ @@ -82,11 +87,21 @@ Module.register("MMM-RandomPhoto",{ ]; }, - socketNotificationReceived: function(notification, payload) { - if (notification === "RANDOMPHOTO_NEXT"){ + notificationReceived: function(notification, payload, sender) { + if (notification === "RANDOMPHOTO_NEXT") { clearTimeout(this.updateTimer); this.load(); } + }, + + suspend: function() { + Log.info(this.name + ": suspending"); + clearTimeout(this.updateTimer); + }, + + resume: function() { + Log.info(this.name + ": resuming"); + this.load(); } }); diff --git a/README.md b/README.md index e032a06..f34790e 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ The entry in `config.js` can include the following options: |`grayscale`|Should the image be grayscaled?

**Type:** `boolean`
Default `false`| |`blur`|Should the image be blurred?

**Type:** `boolean`
Default `false`| |`blurAmount`|If you want to blur it, how much? Allows a number between `1` and `10`.

**Type:** `int`
Default `1`| +|`startHidden`|Should the module start hidden?
Helpful if you use it as a "screensaver"

**Type:** `boolean`
Default `false`| Here is an example of an entry in `config.js` ``` @@ -34,10 +35,21 @@ Here is an example of an entry in `config.js` width: 1920, height: 1080, grayscale: true, + startHidden: true, } }, ``` +## Notifications +You can control this module by sending a `RANDOMPHOTO_NEXT` notification. +If you do, the next image will bo shown and the updateInterval starts from 0 + +## Ideas +Thinking about implementing the following things: +- possibility to show the user comment from each image on screen (target selectable) +- possibility to stop and resume the automated slideshow (show icon indicator somewhere: pause / play) +- ... + ## Dependencies - [jquery](https://www.npmjs.com/package/jquery) (installed via `npm install`) From 13b8e63df8ba54be780102def86983a9f696d2f0 Mon Sep 17 00:00:00 2001 From: skuethe Date: Wed, 7 Oct 2020 16:47:54 +0200 Subject: [PATCH 03/21] added possibility to pause / resume the image loading and show status icon --- MMM-RandomPhoto.css | 43 +++++++++++++++++-- MMM-RandomPhoto.js | 101 ++++++++++++++++++++++++++++++++++++++------ README.md | 18 ++++++-- 3 files changed, 142 insertions(+), 20 deletions(-) diff --git a/MMM-RandomPhoto.css b/MMM-RandomPhoto.css index c528981..ad635e2 100644 --- a/MMM-RandomPhoto.css +++ b/MMM-RandomPhoto.css @@ -1,5 +1,42 @@ #randomPhoto img { - opacity:0; - position:fixed; - top:0; + opacity: 0; + position: absolute;; + top: 0; + left: 0; +} + +#randomPhotoIcon { + position: absolute; +} + +#randomPhotoIcon.top { + top: 5px; +} + +#randomPhotoIcon.bottom { + bottom: 5px; +} + +#randomPhotoIcon.right { + right: 10px; +} + +#randomPhotoIcon.left { + left: 10px; +} + +#randomPhotoIcon i.hidden { + display: none; +} + +#randomPhotoIcon i.fading { + opacity: 0; + animation: fadeInAndOut 4s; +} + + +@keyframes fadeInAndOut { + 0% { opacity: 0; } + 50% { opacity: 1; } + 100% { opacity: 0; } } \ No newline at end of file diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index eada888..9a53011 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -13,21 +13,40 @@ Module.register("MMM-RandomPhoto",{ opacity: 0.3, animationSpeed: 500, updateInterval: 60, - url: 'https://picsum.photos/', + url: "https://picsum.photos/", width: 1920, height: 1080, grayscale: false, blur: false, blurAmount: 1, // between 1 and 10 startHidden: false, + showStatusIcon: true, + statusIconMode: "show", // one of: "show" (default / fallback) or "fade" + statusIconPosition: "top_right", // one of: "top_right" (default / fallback), "top_left", "bottom_right" or "bottom_left" }, start: function() { this.updateTimer = null; - if(this.config.startHidden) { - this.hide(); - } else { + this.running = false; + }, + + pauseImageLoading: function() { + Log.log(this.name + ": pausing"); + clearTimeout(this.updateTimer); + this.running = false; + if (this.config.showStatusIcon) { + this.loadIcon(); + } + }, + + resumeImageLoading: function() { + Log.log(this.name + ": resuming"); + if (!this.running) { + this.running = true; this.load(); + if (this.config.showStatusIcon) { + this.loadIcon(); + } } }, @@ -62,16 +81,50 @@ Module.register("MMM-RandomPhoto",{ }); }); - this.updateTimer = setTimeout(function() { - self.load(); - }, (self.config.updateInterval * 1000)); - + // Only activate re-loading itself, if we are not in "pause" state + if (this.running) { + this.updateTimer = setTimeout(function() { + self.load(); + }, (this.config.updateInterval * 1000)); + } +}, + + loadIcon: function() { + var pauseIcon = document.getElementById("randomPhotoIconPause"); + var playIcon = document.getElementById("randomPhotoIconPlay"); + if (this.running) { + pauseIcon.classList.add("hidden"); + playIcon.classList.remove("hidden"); + if (this.config.statusIconMode === "fade") { + pauseIcon.classList.remove("fading"); + playIcon.classList.add("fading"); + } + } else { + playIcon.classList.add("hidden"); + pauseIcon.classList.remove("hidden"); + if (this.config.statusIconMode === "fade") { + playIcon.classList.remove("fading"); + pauseIcon.classList.add("fading"); + } + } }, getDom: function() { var wrapper = document.createElement("div"); wrapper.id = "randomPhoto"; wrapper.innerHTML = ''; + if (this.config.showStatusIcon) { + var validatePosition = ['top_right', 'top_left', 'bottom_right', 'bottom_left']; + if (validatePosition.indexOf(this.config.statusIconPosition) === -1) { + this.config.statusIconPosition = 'top_right'; + } + var statusIconObject = document.createElement("span"); + statusIconObject.id = "randomPhotoIcon"; + statusIconObject.className = this.config.statusIconPosition.replace("_", " "); + statusIconObject.classList.add("dimmed"); + statusIconObject.innerHTML = ''; + wrapper.appendChild(statusIconObject); + } return wrapper; }, @@ -83,25 +136,45 @@ Module.register("MMM-RandomPhoto",{ getStyles: function() { return [ - "MMM-RandomPhoto.css" + "MMM-RandomPhoto.css", + "font-awesome.css" ]; }, notificationReceived: function(notification, payload, sender) { + if (notification === "MODULE_DOM_CREATED") { + if (this.config.startHidden) { + this.hide() + } else { + this.resumeImageLoading(); + } + } if (notification === "RANDOMPHOTO_NEXT") { + // Don't call the pause or resume functions here, so we can actually work with both states ("paused" and "active"), so independent of what "this.running" is set to clearTimeout(this.updateTimer); this.load(); } + if (notification === "RANDOMPHOTO_TOGGLE") { + if (this.running) { + this.pauseImageLoading(); + } else { + this.resumeImageLoading(); + } + } + if (notification === "RANDOMPHOTO_PAUSE") { + this.pauseImageLoading(); + } + if (notification === "RANDOMPHOTO_RESUME") { + this.resumeImageLoading(); + } }, suspend: function() { - Log.info(this.name + ": suspending"); - clearTimeout(this.updateTimer); + this.pauseImageLoading(); }, - resume: function() { - Log.info(this.name + ": resuming"); - this.load(); + resume: function() { + this.resumeImageLoading(); } }); diff --git a/README.md b/README.md index f34790e..f8cf4d1 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,9 @@ The entry in `config.js` can include the following options: |`blur`|Should the image be blurred?

**Type:** `boolean`
Default `false`| |`blurAmount`|If you want to blur it, how much? Allows a number between `1` and `10`.

**Type:** `int`
Default `1`| |`startHidden`|Should the module start hidden?
Helpful if you use it as a "screensaver"

**Type:** `boolean`
Default `false`| +|`showStatusIcon`|Do you want to see the status of automatic image loading ("play" / "paused")?

**Type:** `boolean`
Default `true`| +|`statusIconMode`|Do you want to display the icon all the time or just fade in and out on status change?

**Type:** `string`
Possible values: `show` and `fade`
Default `show`| +|`statusIconPosition`|Where do you want to display the status icon?

**Type:** `string`
Possible values: `top_right`, `top_left`, `bottom_right` and `bottom_left`
Default `top_right`| Here is an example of an entry in `config.js` ``` @@ -36,18 +39,27 @@ Here is an example of an entry in `config.js` height: 1080, grayscale: true, startHidden: true, + showStatusIcon: true, + statusIconMode: "show", + statusIconPosition: "top_right", } }, ``` ## Notifications -You can control this module by sending a `RANDOMPHOTO_NEXT` notification. -If you do, the next image will bo shown and the updateInterval starts from 0 +You can control this module by sending specific notifications. +See the following list: + +|Notification|Description| +|---|---| +|`RANDOMPHOTO_NEXT`|Don't wait for `updateInterval` to trigger and immidiately show the next image
Respects the current state of automatic image loading| +|`RANDOMPHOTO_TOGGLE`|Toggle the state of automatic image loading| +|`RANDOMPHOTO_PAUSE`|Pause the loading of new images| +|`RANDOMPHOTO_RESUME`|Resume the loading of new images| ## Ideas Thinking about implementing the following things: - possibility to show the user comment from each image on screen (target selectable) -- possibility to stop and resume the automated slideshow (show icon indicator somewhere: pause / play) - ... ## Dependencies From 3f830b7f9c57897b70a5b575fc628a9a9bcc140f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20K=C3=BCthe?= Date: Wed, 7 Oct 2020 21:17:33 +0200 Subject: [PATCH 04/21] fixing bug with css class names coliding with MMM-pages --- MMM-RandomPhoto.css | 8 ++++---- MMM-RandomPhoto.js | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/MMM-RandomPhoto.css b/MMM-RandomPhoto.css index ad635e2..b7adc59 100644 --- a/MMM-RandomPhoto.css +++ b/MMM-RandomPhoto.css @@ -9,19 +9,19 @@ position: absolute; } -#randomPhotoIcon.top { +#randomPhotoIcon.rpitop { top: 5px; } -#randomPhotoIcon.bottom { +#randomPhotoIcon.rpibottom { bottom: 5px; } -#randomPhotoIcon.right { +#randomPhotoIcon.rpiright { right: 10px; } -#randomPhotoIcon.left { +#randomPhotoIcon.rpileft { left: 10px; } diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index 9a53011..b520a9c 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -87,7 +87,7 @@ Module.register("MMM-RandomPhoto",{ self.load(); }, (this.config.updateInterval * 1000)); } -}, + }, loadIcon: function() { var pauseIcon = document.getElementById("randomPhotoIconPause"); @@ -120,8 +120,10 @@ Module.register("MMM-RandomPhoto",{ } var statusIconObject = document.createElement("span"); statusIconObject.id = "randomPhotoIcon"; - statusIconObject.className = this.config.statusIconPosition.replace("_", " "); statusIconObject.classList.add("dimmed"); + this.config.statusIconPosition.split("_").forEach(function(extractedName) { + statusIconObject.classList.add("rpi" + extractedName); + }); statusIconObject.innerHTML = ''; wrapper.appendChild(statusIconObject); } @@ -144,7 +146,7 @@ Module.register("MMM-RandomPhoto",{ notificationReceived: function(notification, payload, sender) { if (notification === "MODULE_DOM_CREATED") { if (this.config.startHidden) { - this.hide() + this.hide(); } else { this.resumeImageLoading(); } From a857819eb28a73c01bdd3a0b311bb29359854517 Mon Sep 17 00:00:00 2001 From: skuethe Date: Thu, 8 Oct 2020 19:29:31 +0200 Subject: [PATCH 05/21] first commit for nextcloud. some problems with cors --- MMM-RandomPhoto.js | 172 +++++++++++++++++++++++++++++++++++++++++---- node_helper.js | 59 ++++++++++++++++ 2 files changed, 219 insertions(+), 12 deletions(-) create mode 100644 node_helper.js diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index b520a9c..e6a97a5 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -13,9 +13,20 @@ Module.register("MMM-RandomPhoto",{ opacity: 0.3, animationSpeed: 500, updateInterval: 60, - url: "https://picsum.photos/", + imageRepository: "picsum", // Select the image repository source. One of "picsum" (default / fallback) or "nextcloud" + repositoryConfig: { + // if imageRepository = "picsum" -> "url", "username" and "password" are ignored and can be left empty + // if imageRepository = "nextcloud" + // -> "url" will point to your image directory, f.e.: "https://YOUR.NEXTCLOUD.HOST/remote.php/dav/files/USERNAME/PATH/TO/DIRECTORY/" + // -> if the share is private / internally shared only, add "username" and "password" for basic authentication. See documentation on how to create an "device" password: + // https://docs.nextcloud.com/server/latest/user_manual/en/session_management.html#managing-devices + url: "https://picsum.photos/", + username: "", + password: "", + }, width: 1920, height: 1080, + random: true, // Show random images? Has no effect if you select "picsum" as imageRepository - there it is always random grayscale: false, blur: false, blurAmount: 1, // between 1 and 10 @@ -27,7 +38,25 @@ Module.register("MMM-RandomPhoto",{ start: function() { this.updateTimer = null; + this.imageList = null; // used for nextcloud image url list + this.currentImageIndex = 0; // used for nextcloud image url list this.running = false; + if (this.config.imageRepository.toLowerCase() === "nextcloud") { + this.sendSocketNotification('SET_CONFIG', this.config); + this.useNextCloud(); + } else { + // picsum -> force URL + this.config.repositoryConfig.url = "https://picsum.photos/"; + this.sendSocketNotification('SET_CONFIG', this.config); + } + }, + + useNextCloud: function() { + if (typeof this.config.repositoryConfig.url !== "undefined" && this.config.repositoryConfig.url !== null) { + this.sendSocketNotification('FETCH_NEXTCLOUD_IMAGE_LIST'); + } else { + Log.error("[" + this.name + "] Trying to use 'nextcloud' but did not specify any URL."); + } }, pauseImageLoading: function() { @@ -52,19 +81,34 @@ Module.register("MMM-RandomPhoto",{ load: function() { var self = this; + var url = ""; - var url = self.config.url + self.config.width + "/" + self.config.height + "/" - if(self.config.grayscale) { - url = url + (url.indexOf('?') > -1 ? '&' : '?') + "grayscale"; - } - if(self.config.blur) { - url = url + (url.indexOf('?') > -1 ? '&' : '?') + "blur"; - if(self.config.blurAmount > 1) { - if(self.config.blurAmount > 10) { self.config.blurAmount = 10; } - url = url + "=" + self.config.blurAmount; + if (self.config.imageRepository.toLowerCase() === "nextcloud") { + if (self.imageList && self.imageList.length > 0) { + url = this.returnImageFromList(); + if (self.config.repositoryConfig.username && self.config.repositoryConfig.password) { + // basic auth data set, special handling required + url = this.retrieveImageBehindBasicAuth(url); + } + } + if (!url) { + return false; + } + } else { + // picsum default / fallback + url = self.config.repositoryConfig.url + self.config.width + "/" + self.config.height + "/" + if(self.config.grayscale) { + url = url + (url.indexOf('?') > -1 ? '&' : '?') + "grayscale"; + } + if(self.config.blur) { + url = url + (url.indexOf('?') > -1 ? '&' : '?') + "blur"; + if(self.config.blurAmount > 1) { + if(self.config.blurAmount > 10) { self.config.blurAmount = 10; } + url = url + "=" + self.config.blurAmount; + } } + url = url + (url.indexOf('?') > -1 ? '&' : '?') + (new Date().getTime()); } - url = url + (url.indexOf('?') > -1 ? '&' : '?') + (new Date().getTime()); var img = $('').attr('src', url); img.on('load', function() { @@ -89,6 +133,99 @@ Module.register("MMM-RandomPhoto",{ } }, + returnImageFromList: function() { + var indexToFetch = this.currentImageIndex; + const imageList = this.imageList; + + if (this.config.random) { + indexToFetch = Math.floor(Math.random() * imageList.length); + } + var imageSource = imageList[indexToFetch]; + console.log(indexToFetch, imageSource); + + // If we are not doing it random, increase the index counter + if (!this.config.random) { + indexToFetch++; + if (indexToFetch >= imageList.length) { + indexToFetch = 0; + } + this.currentImageIndex = indexToFetch; + } + return imageSource; + }, + + retrieveImageBehindBasicAuth: function(passedImageUrl) { + var self = this; + var basicAuth = btoa(self.config.repositoryConfig.username + ":" + self.config.repositoryConfig.password); + Log.log("[" + self.name + "] -- DEBUG -- passedImageUrl: " + passedImageUrl); + Log.log("[" + self.name + "] -- DEBUG -- basicAuth: " + basicAuth); + + /** + const request = new Request(passedImageUrl, { + method: "GET", + headers: { + "Authorization": "Basic " + basicAuth, + }, + mode: "cors" + }); + + fetch(request) + .then(response => { + console.log("[" + self.name + "] Got response: " + response); + return response; + }) + .catch(err => { + console.log("[" + this.name + "] ERROR: " + err); + return false; + }); + **/ + /** + const requestOptions = { + method: "GET", + headers: { + "Authorization": "Basic " + basicAuth, + } + }; + + https.get(passedImageUrl, requestOptions, function(response) { + var body = ""; + response.on("data", function(data) { + body += data; + }); + response.on("end", function() { + console.log("[" + self.name + "] Got response: " + body); + return body; + }); + }) + .on("error", function(err) { + console.log("[" + this.name + "] ERROR: " + err); + return false; + }); + **/ + + jQuery.ajax({ + method: "GET", + url: passedImageUrl, + dataType: "image/jpg", + crossDomain: false, + headers: { + "Authorization": "Basic " + basicAuth + }, + //beforeSend: function (xhr) { + // xhr.setRequestHeader ("Authorization", "Basic " + btoa(self.config.repositoryConfig.username + ":" + self.config.repositoryConfig.password)); + //}, + }) + .done(function (data) { + Log.log("[" + self.name + "] -- DEBUG -- Got data: " + data); + return "data:image/png;base64," + data; + }) + .fail(function( jqXHR, textStatus ) { + Log.error("[" + self.name + "] Request failed: " + textStatus); + console.dir(jqXHR); + return false; + }); + }, + loadIcon: function() { var pauseIcon = document.getElementById("randomPhotoIconPause"); var playIcon = document.getElementById("randomPhotoIconPlay"); @@ -148,7 +285,10 @@ Module.register("MMM-RandomPhoto",{ if (this.config.startHidden) { this.hide(); } else { - this.resumeImageLoading(); + if (this.config.imageRepository !== "nextcloud") { + // only start "right away" if we display "picsum" images. Otherwise wait until we receive the "NEXTCLOUD_IMAGE_LIST" socketNotification + this.resumeImageLoading(); + } } } if (notification === "RANDOMPHOTO_NEXT") { @@ -171,6 +311,14 @@ Module.register("MMM-RandomPhoto",{ } }, + socketNotificationReceived: function(notification, payload) { + if (notification === "NEXTCLOUD_IMAGE_LIST") { + this.imageList = payload; + // After we now received the image list, go ahead and display them + this.resumeImageLoading(); + } + }, + suspend: function() { this.pauseImageLoading(); }, diff --git a/node_helper.js b/node_helper.js new file mode 100644 index 0000000..bc82962 --- /dev/null +++ b/node_helper.js @@ -0,0 +1,59 @@ +require("url"); +const https = require("https"); +const NodeHelper = require("node_helper"); + +module.exports = NodeHelper.create({ + + fetchNextcloudImageList: function() { + var self = this; + const urlParts = new URL(this.config.repositoryConfig.url); + const requestOptions = { + method: "PROPFIND", + headers: { + "Authorization": "Basic " + new Buffer.from(this.config.repositoryConfig.username + ":" + this.config.repositoryConfig.password).toString("base64") + } + }; + + //console.log("[" + this.name + "] --- DEBUG --- urlParts: " + urlParts); + //console.log("[" + this.name + "] --- DEBUG --- this.config.repositoryConfig.url: " + this.config.repositoryConfig.url); + + var matches = []; + https.get(this.config.repositoryConfig.url, requestOptions, function(response) { + var body = ""; + response.on("data", function(data) { + body += data; + }); + response.on("end", function() { + //console.log("[" + self.name + "] Got response: " + body); + matches = body.match(/href>([^<]+)/g); + matches.shift(); // delete first array entry, because it contains the link to the current folder + if (matches && matches.length > 0) { + matches.forEach(function(item, index) { + matches[index] = urlParts.origin + item.replace("href>", ""); + console.log("[" + self.name + "] Found entry: " + matches[index]); + }); + self.sendSocketNotification("NEXTCLOUD_IMAGE_LIST", matches); + return; + } else { + console.log("[" + this.name + "] WARNING: did not get any images from nextcloud url"); + return false; + } + }); + }) + .on("error", function(err) { + console.log("[" + this.name + "] ERROR: " + err); + return false; + }); + }, + + socketNotificationReceived: function(notification, payload) { + console.log("["+ this.name + "] received a '" + notification + "' with payload: " + payload); + if (notification === "SET_CONFIG") { + this.config = payload; + } + if (notification === "FETCH_NEXTCLOUD_IMAGE_LIST") { + this.fetchNextcloudImageList(); + } + }, + +}); \ No newline at end of file From d596444951adf7852d9a9a0ead508b5ee549d3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20K=C3=BCthe?= Date: Sun, 11 Oct 2020 00:59:56 +0200 Subject: [PATCH 06/21] major changes for nextcloud and localfolder integrations --- MMM-RandomPhoto.css | 15 ++++ MMM-RandomPhoto.js | 211 ++++++++++++++++++++------------------------ node_helper.js | 152 +++++++++++++++++++++++++------ 3 files changed, 239 insertions(+), 139 deletions(-) diff --git a/MMM-RandomPhoto.css b/MMM-RandomPhoto.css index b7adc59..88de85c 100644 --- a/MMM-RandomPhoto.css +++ b/MMM-RandomPhoto.css @@ -1,8 +1,23 @@ +:root { + --blur-value: 0px; +} + #randomPhoto img { opacity: 0; position: absolute;; top: 0; left: 0; + height: 100%; + width: 100%; + object-fit: cover; +} + +#randomPhoto img.grayscale { + filter: grayscale(100%); +} + +#randomPhoto img.blur { + filter: blur(var(--blur-value)); } #randomPhotoIcon { diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index e6a97a5..cb7d483 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -13,14 +13,16 @@ Module.register("MMM-RandomPhoto",{ opacity: 0.3, animationSpeed: 500, updateInterval: 60, - imageRepository: "picsum", // Select the image repository source. One of "picsum" (default / fallback) or "nextcloud" + imageRepository: "picsum", // Select the image repository source. One of "picsum" (default / fallback), "localdirectory" or "nextcloud" (currently broken because of CORS bug in nextcloud) repositoryConfig: { - // if imageRepository = "picsum" -> "url", "username" and "password" are ignored and can be left empty + // if imageRepository = "picsum" -> "path", "username" and "password" are ignored and can be left empty // if imageRepository = "nextcloud" - // -> "url" will point to your image directory, f.e.: "https://YOUR.NEXTCLOUD.HOST/remote.php/dav/files/USERNAME/PATH/TO/DIRECTORY/" + // -> "path" will point to your image directory URL, f.e.: "https://YOUR.NEXTCLOUD.HOST/remote.php/dav/files/USERNAME/PATH/TO/DIRECTORY/" // -> if the share is private / internally shared only, add "username" and "password" for basic authentication. See documentation on how to create an "device" password: // https://docs.nextcloud.com/server/latest/user_manual/en/session_management.html#managing-devices - url: "https://picsum.photos/", + // if imageRepository = "localdirectory" + // -> "path" will point to your local directory, f.e.: "~/someReallyCoolPictures", "username" and "password" are ignored + path: "https://picsum.photos/", username: "", password: "", }, @@ -41,26 +43,40 @@ Module.register("MMM-RandomPhoto",{ this.imageList = null; // used for nextcloud image url list this.currentImageIndex = 0; // used for nextcloud image url list this.running = false; - if (this.config.imageRepository.toLowerCase() === "nextcloud") { + + this.nextcloud = false; + this.localdirectory = false; + + this.config.imageRepository = this.config.imageRepository.toLowerCase(); + if (this.config.imageRepository === "nextcloud") { + this.nextcloud = true; + } else if (this.config.imageRepository === "localdirectory") { + this.localdirectory = true; + } + + // Set blur amount to a max of 10px + if(this.config.blurAmount > 10) { this.config.blurAmount = 10; } + + if (this.nextcloud || this.localdirectory) { this.sendSocketNotification('SET_CONFIG', this.config); - this.useNextCloud(); + this.fetchImageList(); } else { // picsum -> force URL - this.config.repositoryConfig.url = "https://picsum.photos/"; + Log.log(this.name + " --- DEBUG ---: using picsum"); + this.config.repositoryConfig.path = "https://picsum.photos/"; this.sendSocketNotification('SET_CONFIG', this.config); } }, - useNextCloud: function() { - if (typeof this.config.repositoryConfig.url !== "undefined" && this.config.repositoryConfig.url !== null) { - this.sendSocketNotification('FETCH_NEXTCLOUD_IMAGE_LIST'); + fetchImageList: function() { + if (typeof this.config.repositoryConfig.path !== "undefined" && this.config.repositoryConfig.path !== null) { + this.sendSocketNotification('FETCH_IMAGE_LIST'); } else { - Log.error("[" + this.name + "] Trying to use 'nextcloud' but did not specify any URL."); + Log.error("[" + this.name + "] Trying to use 'nextcloud' or 'localdirectory' but did not specify any 'config.repositoryConfig.path'."); } }, pauseImageLoading: function() { - Log.log(this.name + ": pausing"); clearTimeout(this.updateTimer); this.running = false; if (this.config.showStatusIcon) { @@ -69,7 +85,6 @@ Module.register("MMM-RandomPhoto",{ }, resumeImageLoading: function() { - Log.log(this.name + ": resuming"); if (!this.running) { this.running = true; this.load(); @@ -83,20 +98,30 @@ Module.register("MMM-RandomPhoto",{ var self = this; var url = ""; - if (self.config.imageRepository.toLowerCase() === "nextcloud") { + if (self.localdirectory || self.nextcloud) { if (self.imageList && self.imageList.length > 0) { - url = this.returnImageFromList(); - if (self.config.repositoryConfig.username && self.config.repositoryConfig.password) { - // basic auth data set, special handling required - url = this.retrieveImageBehindBasicAuth(url); - } - } - if (!url) { + url = "/" + this.name + "/images/" + this.returnImageFromList(); + + jQuery.ajax({ + method: "GET", + url: url, + }) + .done(function (data) { + self.smoothImageChange(data); + }) + .fail(function( jqXHR, textStatus ) { + Log.error("[" + self.name + "] Request failed: " + textStatus); + console.dir(jqXHR); + return false; + }); + + } else { + Log.error("[" + self.name + "] No images to display. 'this.imageList': " + self.imageList); return false; } } else { // picsum default / fallback - url = self.config.repositoryConfig.url + self.config.width + "/" + self.config.height + "/" + url = self.config.repositoryConfig.path + self.config.width + "/" + self.config.height + "/" if(self.config.grayscale) { url = url + (url.indexOf('?') > -1 ? '&' : '?') + "grayscale"; } @@ -108,22 +133,8 @@ Module.register("MMM-RandomPhoto",{ } } url = url + (url.indexOf('?') > -1 ? '&' : '?') + (new Date().getTime()); + self.smoothImageChange(url); } - var img = $('').attr('src', url); - - img.on('load', function() { - $('#randomPhoto-placeholder1').attr('src', url).animate({ - opacity: self.config.opacity - }, self.config.animationSpeed, function() { - $(this).attr('id', 'randomPhoto-placeholder2'); - }); - - $('#randomPhoto-placeholder2').animate({ - opacity: 0 - }, self.config.animationSpeed, function() { - $(this).attr('id', 'randomPhoto-placeholder1'); - }); - }); // Only activate re-loading itself, if we are not in "pause" state if (this.running) { @@ -133,15 +144,35 @@ Module.register("MMM-RandomPhoto",{ } }, + smoothImageChange: function(url) { + var self = this; + var img = $('').attr('src', url); + img.on('load', function() { + $('#randomPhoto-placeholder1').attr('src', url).animate({ + opacity: self.config.opacity + }, self.config.animationSpeed, function() { + $(this).attr('id', 'randomPhoto-placeholder2'); + }); + + $('#randomPhoto-placeholder2').animate({ + opacity: 0 + }, self.config.animationSpeed, function() { + $(this).attr('id', 'randomPhoto-placeholder1'); + }); + }); + }, + returnImageFromList: function() { var indexToFetch = this.currentImageIndex; const imageList = this.imageList; if (this.config.random) { + Log.info("[" + this.name + "] -- DEBUG -- will fetch a random image"); indexToFetch = Math.floor(Math.random() * imageList.length); } var imageSource = imageList[indexToFetch]; - console.log(indexToFetch, imageSource); + Log.info(indexToFetch, imageSource); + //console.log(indexToFetch, imageSource); // If we are not doing it random, increase the index counter if (!this.config.random) { @@ -154,78 +185,6 @@ Module.register("MMM-RandomPhoto",{ return imageSource; }, - retrieveImageBehindBasicAuth: function(passedImageUrl) { - var self = this; - var basicAuth = btoa(self.config.repositoryConfig.username + ":" + self.config.repositoryConfig.password); - Log.log("[" + self.name + "] -- DEBUG -- passedImageUrl: " + passedImageUrl); - Log.log("[" + self.name + "] -- DEBUG -- basicAuth: " + basicAuth); - - /** - const request = new Request(passedImageUrl, { - method: "GET", - headers: { - "Authorization": "Basic " + basicAuth, - }, - mode: "cors" - }); - - fetch(request) - .then(response => { - console.log("[" + self.name + "] Got response: " + response); - return response; - }) - .catch(err => { - console.log("[" + this.name + "] ERROR: " + err); - return false; - }); - **/ - /** - const requestOptions = { - method: "GET", - headers: { - "Authorization": "Basic " + basicAuth, - } - }; - - https.get(passedImageUrl, requestOptions, function(response) { - var body = ""; - response.on("data", function(data) { - body += data; - }); - response.on("end", function() { - console.log("[" + self.name + "] Got response: " + body); - return body; - }); - }) - .on("error", function(err) { - console.log("[" + this.name + "] ERROR: " + err); - return false; - }); - **/ - - jQuery.ajax({ - method: "GET", - url: passedImageUrl, - dataType: "image/jpg", - crossDomain: false, - headers: { - "Authorization": "Basic " + basicAuth - }, - //beforeSend: function (xhr) { - // xhr.setRequestHeader ("Authorization", "Basic " + btoa(self.config.repositoryConfig.username + ":" + self.config.repositoryConfig.password)); - //}, - }) - .done(function (data) { - Log.log("[" + self.name + "] -- DEBUG -- Got data: " + data); - return "data:image/png;base64," + data; - }) - .fail(function( jqXHR, textStatus ) { - Log.error("[" + self.name + "] Request failed: " + textStatus); - console.dir(jqXHR); - return false; - }); - }, - loadIcon: function() { var pauseIcon = document.getElementById("randomPhotoIconPause"); var playIcon = document.getElementById("randomPhotoIconPlay"); @@ -249,7 +208,29 @@ Module.register("MMM-RandomPhoto",{ getDom: function() { var wrapper = document.createElement("div"); wrapper.id = "randomPhoto"; - wrapper.innerHTML = ''; + + var img1 = document.createElement("img"); + img1.id = "randomPhoto-placeholder1"; + var img2 = document.createElement("img"); + img2.id = "randomPhoto-placeholder2"; + + // Only apply grayscale / blur css classes if we are NOT using picsum, as picsum is doing it via URL parameters + if (this.nextcloud || this.localdirectory) { + if (this.config.grayscale) { + img1.classList.add("grayscale"); + img2.classList.add("grayscale"); + } + if (this.config.blur) { + img1.classList.add("blur"); + img2.classList.add("blur"); + img1.style.setProperty("--blur-value", this.config.blurAmount + "px"); + img2.style.setProperty("--blur-value", this.config.blurAmount + "px"); + } + } + + wrapper.appendChild(img1); + wrapper.appendChild(img2); + //wrapper.innerHTML = ''; if (this.config.showStatusIcon) { var validatePosition = ['top_right', 'top_left', 'bottom_right', 'bottom_left']; if (validatePosition.indexOf(this.config.statusIconPosition) === -1) { @@ -285,8 +266,8 @@ Module.register("MMM-RandomPhoto",{ if (this.config.startHidden) { this.hide(); } else { - if (this.config.imageRepository !== "nextcloud") { - // only start "right away" if we display "picsum" images. Otherwise wait until we receive the "NEXTCLOUD_IMAGE_LIST" socketNotification + if (!this.nextcloud && !this.localdirectory) { + // only start "right away" if we display "picsum" images. Otherwise wait until we receive the "IMAGE_LIST" socketNotification this.resumeImageLoading(); } } @@ -312,7 +293,9 @@ Module.register("MMM-RandomPhoto",{ }, socketNotificationReceived: function(notification, payload) { - if (notification === "NEXTCLOUD_IMAGE_LIST") { + Log.log("["+ this.name + "] received a '" + notification + "' with payload: " + payload); + console.dir(payload); + if (notification === "IMAGE_LIST") { this.imageList = payload; // After we now received the image list, go ahead and display them this.resumeImageLoading(); diff --git a/node_helper.js b/node_helper.js index bc82962..9c300ce 100644 --- a/node_helper.js +++ b/node_helper.js @@ -1,38 +1,99 @@ -require("url"); -const https = require("https"); +require("url"); // for nextcloud +const https = require("https"); // for nextcloud +const fs = require("fs"); // for localdirectory + const NodeHelper = require("node_helper"); module.exports = NodeHelper.create({ + start: function() { + var self = this; + + this.nextcloud = false; + this.localdirectory = false; + + this.imageList = []; + this.expressApp.get("/" + this.name + "/images/:randomImageName(*)", async function(request, response) { + var imageBase64Encoded = await self.fetchEncodedImage(request.params.randomImageName); + response.send(imageBase64Encoded); + }); + }, + + + socketNotificationReceived: function(notification, payload) { + //console.log("["+ this.name + "] received a '" + notification + "' with payload: " + payload); + if (notification === "SET_CONFIG") { + this.config = payload; + if (this.config.imageRepository === "nextcloud") { + this.nextcloud = true; + } else if (this.config.imageRepository === "localdirectory") { + this.localdirectory = true; + } + } + if (notification === "FETCH_IMAGE_LIST") { + if (this.config.imageRepository === "nextcloud") { + this.fetchNextcloudImageList(); + } + if (this.config.imageRepository === "localdirectory") { + this.fetchLocalImageList(); + } + } + }, + + fetchLocalImageList: function() { + var self = this; + var imageList = []; + var path = self.config.repositoryConfig.path; + + // Validate path + if (!fs.existsSync(path)) { + console.log("["+ self.name + "] ERROR - specified path does not exist: " + path); + return false; + } + + var fileList = fs.readdirSync(path, { withFileTypes: true }); + if (fileList.length > 0) { + for (var f = 0; f < fileList.length; f++) { + if (fileList[f].isFile()) { + //TODO: add mime type check here + imageList.push(fileList[f].name); + } + } + this.imageList = imageList; + self.sendSocketNotification("IMAGE_LIST", imageList); + return; + } + return false; + }, + + fetchNextcloudImageList: function() { var self = this; - const urlParts = new URL(this.config.repositoryConfig.url); + var imageList = []; + var path = self.config.repositoryConfig.path; + + const urlParts = new URL(path); const requestOptions = { method: "PROPFIND", headers: { "Authorization": "Basic " + new Buffer.from(this.config.repositoryConfig.username + ":" + this.config.repositoryConfig.password).toString("base64") } }; - - //console.log("[" + this.name + "] --- DEBUG --- urlParts: " + urlParts); - //console.log("[" + this.name + "] --- DEBUG --- this.config.repositoryConfig.url: " + this.config.repositoryConfig.url); - - var matches = []; - https.get(this.config.repositoryConfig.url, requestOptions, function(response) { + https.get(path, requestOptions, function(response) { var body = ""; response.on("data", function(data) { body += data; }); response.on("end", function() { - //console.log("[" + self.name + "] Got response: " + body); - matches = body.match(/href>([^<]+)/g); - matches.shift(); // delete first array entry, because it contains the link to the current folder - if (matches && matches.length > 0) { - matches.forEach(function(item, index) { - matches[index] = urlParts.origin + item.replace("href>", ""); - console.log("[" + self.name + "] Found entry: " + matches[index]); + imageList = body.match(/href>\/[^<]+/g); + imageList.shift(); // delete first array entry, because it contains the link to the current folder + if (imageList && imageList.length > 0) { + imageList.forEach(function(item, index) { + // remove clutter and the pathing from the entry -> only save file name + imageList[index] = item.replace("href>" + urlParts.pathname, ""); + //console.log("[" + self.name + "] Found entry: " + imageList[index]); }); - self.sendSocketNotification("NEXTCLOUD_IMAGE_LIST", matches); + self.sendSocketNotification("IMAGE_LIST", imageList); return; } else { console.log("[" + this.name + "] WARNING: did not get any images from nextcloud url"); @@ -46,14 +107,55 @@ module.exports = NodeHelper.create({ }); }, - socketNotificationReceived: function(notification, payload) { - console.log("["+ this.name + "] received a '" + notification + "' with payload: " + payload); - if (notification === "SET_CONFIG") { - this.config = payload; - } - if (notification === "FETCH_NEXTCLOUD_IMAGE_LIST") { - this.fetchNextcloudImageList(); - } + + fetchEncodedImage: async function(passedImageName) { + var self = this; + return new Promise(function(resolve, reject) { + var fullImagePath = self.config.repositoryConfig.path + passedImageName; + + // Local files + if (self.localdirectory) { + var fileEncoded = "data:image/jpeg;base64," + fs.readFileSync(fullImagePath, { encoding: 'base64' }); + resolve(fileEncoded); + } + + // Nextcloud + else if (self.nextcloud) { + const requestOptions = { + method: "GET", + headers: { + "Authorization": "Basic " + new Buffer.from(self.config.repositoryConfig.username + ":" + self.config.repositoryConfig.password).toString("base64") + } + }; + https.get(fullImagePath, requestOptions, (response) => { + response.setEncoding('base64'); + var fileEncoded = "data:" + response.headers["content-type"] + ";base64,"; + response.on("data", (data) => { fileEncoded += data; }); + response.on("end", () => { + resolve(fileEncoded); + }); + }) + .on("error", function(err) { + console.log("[" + this.name + "] ERROR: " + err); + return false; + }); + } + }) + + + /** + var getMimeObject = spawn("file", ["-b", "--mime-type", "-0", "-0", file]); + getMimeObject.stdout.on('data', (data) => { + var mimeType = data.toString().replace("\0", ""); + //console.log("mime type is: '" + mimeType + "'"); + var fileEncoded = "data:" + mimeType + ";base64,"; + fileEncoded += fs.readFileSync(file, { encoding: 'base64' }); + //console.log("base64:"); + console.log(""); + //return fileEncoded; + }); + **/ }, + }); \ No newline at end of file From 82b42def609a2084db2768b8dcc0210a00c51a63 Mon Sep 17 00:00:00 2001 From: skuethe Date: Mon, 12 Oct 2020 10:17:17 +0200 Subject: [PATCH 07/21] added "startPaused" option --- MMM-RandomPhoto.js | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index cb7d483..9d7da06 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -32,7 +32,8 @@ Module.register("MMM-RandomPhoto",{ grayscale: false, blur: false, blurAmount: 1, // between 1 and 10 - startHidden: false, + startHidden: false, // helpful if you use it as a screensaver and only want to show it f.e. after a specific time or with a specific touch gesture + startPaused: false, // start in "paused" mode -> automatic image loading is paused showStatusIcon: true, statusIconMode: "show", // one of: "show" (default / fallback) or "fade" statusIconPosition: "top_right", // one of: "top_right" (default / fallback), "top_left", "bottom_right" or "bottom_left" @@ -84,9 +85,13 @@ Module.register("MMM-RandomPhoto",{ } }, - resumeImageLoading: function() { + resumeImageLoading: function(respectPausedState) { if (!this.running) { - this.running = true; + if (respectPausedState && this.config.startPaused) { + this.running = false; + } else { + this.running = true; + } this.load(); if (this.config.showStatusIcon) { this.loadIcon(); @@ -268,7 +273,7 @@ Module.register("MMM-RandomPhoto",{ } else { if (!this.nextcloud && !this.localdirectory) { // only start "right away" if we display "picsum" images. Otherwise wait until we receive the "IMAGE_LIST" socketNotification - this.resumeImageLoading(); + this.resumeImageLoading(true); } } } @@ -281,24 +286,26 @@ Module.register("MMM-RandomPhoto",{ if (this.running) { this.pauseImageLoading(); } else { - this.resumeImageLoading(); + this.resumeImageLoading(false); } } if (notification === "RANDOMPHOTO_PAUSE") { this.pauseImageLoading(); } if (notification === "RANDOMPHOTO_RESUME") { - this.resumeImageLoading(); + this.resumeImageLoading(false); } }, socketNotificationReceived: function(notification, payload) { - Log.log("["+ this.name + "] received a '" + notification + "' with payload: " + payload); - console.dir(payload); + //Log.log("["+ this.name + "] received a '" + notification + "' with payload: " + payload); + //console.dir(payload); if (notification === "IMAGE_LIST") { this.imageList = payload; - // After we now received the image list, go ahead and display them - this.resumeImageLoading(); + // After we now received the image list, go ahead and display them (only when not starting as hidden) + if(!this.config.startHidden) { + this.resumeImageLoading(true); + } } }, @@ -307,7 +314,7 @@ Module.register("MMM-RandomPhoto",{ }, resume: function() { - this.resumeImageLoading(); + this.resumeImageLoading(true); } }); From 081c3382afaf7c2c1a6c3b197297a0288cab5f96 Mon Sep 17 00:00:00 2001 From: skuethe Date: Mon, 12 Oct 2020 10:45:49 +0200 Subject: [PATCH 08/21] added option to "go back" an image if NOT in random mode and not showing picsum images --- MMM-RandomPhoto.js | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index 9d7da06..2df9a8a 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -41,8 +41,8 @@ Module.register("MMM-RandomPhoto",{ start: function() { this.updateTimer = null; - this.imageList = null; // used for nextcloud image url list - this.currentImageIndex = 0; // used for nextcloud image url list + this.imageList = null; // Used for nextcloud and localdirectory image list + this.currentImageIndex = -1; // Used for nextcloud and localdirectory image list this.running = false; this.nextcloud = false; @@ -99,13 +99,13 @@ Module.register("MMM-RandomPhoto",{ } }, - load: function() { + load: function(mode="next") { var self = this; var url = ""; if (self.localdirectory || self.nextcloud) { if (self.imageList && self.imageList.length > 0) { - url = "/" + this.name + "/images/" + this.returnImageFromList(); + url = "/" + this.name + "/images/" + this.returnImageFromList(mode); jQuery.ajax({ method: "GET", @@ -167,26 +167,31 @@ Module.register("MMM-RandomPhoto",{ }); }, - returnImageFromList: function() { + returnImageFromList: function(mode) { var indexToFetch = this.currentImageIndex; const imageList = this.imageList; if (this.config.random) { Log.info("[" + this.name + "] -- DEBUG -- will fetch a random image"); indexToFetch = Math.floor(Math.random() * imageList.length); + } else { + if (mode === "previous") { + indexToFetch--; + if (indexToFetch < 0) { + indexToFetch = (imageList.length - 1); + } + } else { + indexToFetch++; + if (indexToFetch >= imageList.length) { + indexToFetch = 0; + } + } + this.currentImageIndex = indexToFetch; } var imageSource = imageList[indexToFetch]; Log.info(indexToFetch, imageSource); //console.log(indexToFetch, imageSource); - // If we are not doing it random, increase the index counter - if (!this.config.random) { - indexToFetch++; - if (indexToFetch >= imageList.length) { - indexToFetch = 0; - } - this.currentImageIndex = indexToFetch; - } return imageSource; }, @@ -282,6 +287,13 @@ Module.register("MMM-RandomPhoto",{ clearTimeout(this.updateTimer); this.load(); } + if (notification === "RANDOMPHOTO_PREVIOUS") { + // Only allow this if we are NOT in random mode and NOT use picsum as a source + if (!this.config.random && (this.nextcloud || this.localdirectory)) { + clearTimeout(this.updateTimer); + this.load("previous"); + } + } if (notification === "RANDOMPHOTO_TOGGLE") { if (this.running) { this.pauseImageLoading(); From 1f01c5920fd36b607e54cd531028aa1a6f13c558 Mon Sep 17 00:00:00 2001 From: skuethe Date: Mon, 12 Oct 2020 10:57:09 +0200 Subject: [PATCH 09/21] dont show the same picture if in random mode --- MMM-RandomPhoto.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index 2df9a8a..ab8ef3f 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -172,8 +172,10 @@ Module.register("MMM-RandomPhoto",{ const imageList = this.imageList; if (this.config.random) { - Log.info("[" + this.name + "] -- DEBUG -- will fetch a random image"); - indexToFetch = Math.floor(Math.random() * imageList.length); + //Log.info("[" + this.name + "] -- DEBUG -- will fetch a random image"); + do { + indexToFetch = Math.floor(Math.random() * imageList.length); + } while (imageList.length > 1 && indexToFetch === this.currentImageIndex); } else { if (mode === "previous") { indexToFetch--; @@ -186,11 +188,10 @@ Module.register("MMM-RandomPhoto",{ indexToFetch = 0; } } - this.currentImageIndex = indexToFetch; } var imageSource = imageList[indexToFetch]; Log.info(indexToFetch, imageSource); - //console.log(indexToFetch, imageSource); + this.currentImageIndex = indexToFetch; return imageSource; }, From 4ea7aa48b2ccf03e8ea722c53053f08f779c9a2d Mon Sep 17 00:00:00 2001 From: skuethe Date: Mon, 12 Oct 2020 12:40:22 +0200 Subject: [PATCH 10/21] updated README to reflect latest changes --- README.md | 139 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 105 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index f8cf4d1..9a355d5 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,68 @@ # MMM-RandomPhoto -This a module for the [MagicMirror](https://github.com/MichMich/MagicMirror). It will show a random photo from an url. + +This a module for the [MagicMirror](https://github.com/MichMich/MagicMirror). It will show a (random) photo from any of these sources: + +- [picsum.photos](https://picsum.photos) +- a share of any [nextcloud](https://nextcloud.com/) instance +- a local directory on the Raspberry Pi ## Installation -1. Navigate into your MagicMirror's `modules` folder and execute `git clone https://github.com/skuethe/MMM-RandomPhoto.git`. -2. cd `cd MMM-RandomPhoto` -3. Execute `npm install` to install the node dependencies. + +1. Navigate into your MagicMirror's `modules` folder and execute: + +```bash +git clone https://github.com/skuethe/MMM-RandomPhoto.git && cd MMM-RandomPhoto +``` + +2. Install the node dependencies: + +```bash +npm install +``` ## Config + The entry in `config.js` can include the following options: +| Option | Description +|-----------------------|------------ +| `imageRepository` | *Optional* - The image source.

**Type:** `string`
**Allowed:** `picsum`, `nextcloud`, `localdirectory`
**Default:** `picsum` +| `repositoryConfig` | *Optional* - The configuration block for the selected image repository. See below.

**Type:** `Object` +| `random` | *Optional* - Should the images be shown at random? Has **NO** effect when `imageRepository` is set to `picsum`, as it is forced there.

**Type:** `boolean`
**Default:** `true` +| `width` | *Optional* - The width of the image in px.

**Type:** `int`
**Default:** `1920` +| `height` | *Optional* - The height of the image in px.

**Type:** `int`
**Default:** `1080` +| `grayscale` | *Optional* - Should the image be grayscaled?

**Type:** `boolean`
**Default:** `false` +| `blur` | *Optional* - Should the image be blurred?

**Type:** `boolean`
**Default:** `false` +| `blurAmount` | *Optional* - If you want to blur it, how much?

**Type:** `int`
**Allowed:** minimum: `0`, maximum: `10`
Default `1` +| `opacity` | *Optional* - The opacity of the image.

**Type:** `double`
**Default:** `0.3` +| `animationSpeed` | *Optional* - How long the fade out and fade in of photos should take.

**Type:** `int`
**Default:** `500` +| `updateInterval` | *Optional* - How long before getting a new image.

**Type:** `int`
**Default:** `60` seconds +| `startHidden` | *Optional* - Should the module start hidden? Useful if you use it as a "screensaver"

**Type:** `boolean`
**Default:** `false` +| `startPaused` | *Optional* - Should the module start in "paused" (automatic image loading will be paused) mode?

**Type:** `boolean`
**Default:** `false` +| `showStatusIcon` | *Optional* - Do you want to see the current status of automatic image loading ("play" / "paused" mode)?

**Type:** `boolean`
**Default:** `true` +| `statusIconMode` | *Optional* - Do you want to display the icon all the time or just fade in and out on status change?

**Type:** `string`
**Allowed:** `show` or `fade`
**Default:** `show` +| `statusIconPosition` | *Optional* - Where do you want to display the status icon?

**Type:** `string`
**Allowed:** `top_right`, `top_left`, `bottom_right` or `bottom_left`
**Default:** `top_right` -|Option|Description| -|---|---| -|`opacity`|The opacity of the image.

**Type:** `double`
Default `0.3`| -|`animationSpeed`|How long the fade out and fade in of photos should take.

**Type:** `int`
Default `500`| -|`updateInterval`|How long before getting a new image.

**Type:** `int`
Default `60` seconds| -|`url`|URL to pull a new image from.

**Type:** `string`
Default `https://picsum.photos/`| -|`width`|The width of the image.

**Type:** `int`
Default `1920` px| -|`height`|The height of the image.

**Type:** `int`
Default `1080` px| -|`grayscale`|Should the image be grayscaled?

**Type:** `boolean`
Default `false`| -|`blur`|Should the image be blurred?

**Type:** `boolean`
Default `false`| -|`blurAmount`|If you want to blur it, how much? Allows a number between `1` and `10`.

**Type:** `int`
Default `1`| -|`startHidden`|Should the module start hidden?
Helpful if you use it as a "screensaver"

**Type:** `boolean`
Default `false`| -|`showStatusIcon`|Do you want to see the status of automatic image loading ("play" / "paused")?

**Type:** `boolean`
Default `true`| -|`statusIconMode`|Do you want to display the icon all the time or just fade in and out on status change?

**Type:** `string`
Possible values: `show` and `fade`
Default `show`| -|`statusIconPosition`|Where do you want to display the status icon?

**Type:** `string`
Possible values: `top_right`, `top_left`, `bottom_right` and `bottom_left`
Default `top_right`| - -Here is an example of an entry in `config.js` -``` +Options for `repositoryConfig` - [more information](https://github.com/skuethe/MMM-RandomPhoto/blob/master/MMM-RandomPhoto.js#L18-L24): + +| Option | Description +|-----------------------|------------ +| `path` | *Required* - Path / URL to fetch images from.
- if `imageRepository` is set to `picsum` it is **ignored**
- if `imageRepository` is set to `nextcloud` it has to point to your nextcloud instance's specific share path
- if `imageRepository` is set to `localdirectory` it has to point to a local Path

**Type:** `string`
**Default:** `https://picsum.photos/` +| `username` | *Required* - The username if images require basic authentication (f.e. nextcloud).

**Type:** `string`
**Default:** `` +| `password` | *Required* - The password if images require basic authentication (f.e. nextcloud).

**Type:** `string`
**Default:** `` + +Here are some examples for entries in `config.js` + +**picsum**: + +```js { module: 'MMM-RandomPhoto', position: 'fullscreen_below', config: { - opacity: 0.3, - animationSpeed: 500, - updateInterval: 60, + imageRepository: "picsum", + repositoryConfig: { + }, width: 1920, height: 1080, grayscale: true, @@ -46,25 +74,68 @@ Here is an example of an entry in `config.js` }, ``` +**NextCloud**: + +*Hint*: [Create a "device secret"](https://docs.nextcloud.com/server/latest/user_manual/en/session_management.html#managing-devices) for accessing a share behind basic authentication. + +```js +{ + module: 'MMM-RandomPhoto', + position: 'fullscreen_below', + config: { + imageRepository: "nextcloud", + repositoryConfig: { + path: "https://YOUR.NEXTCLOUD.HOST/remote.php/dav/files/USERNAME/PATH/TO/DIRECTORY/", + username: "USERNAME", + password: "YOURDEVICESECRET", + }, + grayscale: true, + startPaused: true, + showStatusIcon: true, + } +}, +``` + +**local directory**: + +```js +{ + module: 'MMM-RandomPhoto', + position: 'fullscreen_below', + config: { + imageRepository: "localdirectory", + repositoryConfig: { + path: "/home/USER/pictures/background/", + }, + } +}, +``` + ## Notifications + You can control this module by sending specific notifications. See the following list: -|Notification|Description| -|---|---| -|`RANDOMPHOTO_NEXT`|Don't wait for `updateInterval` to trigger and immidiately show the next image
Respects the current state of automatic image loading| -|`RANDOMPHOTO_TOGGLE`|Toggle the state of automatic image loading| -|`RANDOMPHOTO_PAUSE`|Pause the loading of new images| -|`RANDOMPHOTO_RESUME`|Resume the loading of new images| +| Option | Description +|-----------------------|------------ +| `RANDOMPHOTO_NEXT` | Don't wait for `updateInterval` to trigger and immidiately show the next image
Respects the current state of automatic image loading +| `RANDOMPHOTO_PREVIOUS`| Show the previous image.
Only works if `config.random` is set to `false` and `imageRepository` is **NOT** set to `picsum` +| `RANDOMPHOTO_TOGGLE` | Toggle the state of automatic image loading ("play" / "pause" mode) +| `RANDOMPHOTO_PAUSE` | Pause the loading of new images +| `RANDOMPHOTO_RESUME` | Resume the loading of new images ## Ideas + Thinking about implementing the following things: -- possibility to show the user comment from each image on screen (target selectable) + +- possibility to show the EXIF comment from each image on screen (target selectable) - ... ## Dependencies -- [jquery](https://www.npmjs.com/package/jquery) (installed via `npm install`) + +- [jQuery](https://www.npmjs.com/package/jquery) (installed via `npm install`) ## Special Thanks + - [Michael Teeuw](https://github.com/MichMich) for creating the awesome [MagicMirror2](https://github.com/MichMich/MagicMirror) project that made this module possible. -- [Diego Vieira](https://github.com/diego-vieira) for [initially](https://github.com/diego-vieira/MMM-RandomPhoto) creating this module. \ No newline at end of file +- [Diego Vieira](https://github.com/diego-vieira) for [initially](https://github.com/diego-vieira/MMM-RandomPhoto) creating this module. From 9efad4f61444a437bce3bcbfe636921db99c697c Mon Sep 17 00:00:00 2001 From: skuethe Date: Mon, 12 Oct 2020 12:45:51 +0200 Subject: [PATCH 11/21] fixing some MD --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9a355d5..5d9e80c 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,8 @@ The entry in `config.js` can include the following options: | `imageRepository` | *Optional* - The image source.

**Type:** `string`
**Allowed:** `picsum`, `nextcloud`, `localdirectory`
**Default:** `picsum` | `repositoryConfig` | *Optional* - The configuration block for the selected image repository. See below.

**Type:** `Object` | `random` | *Optional* - Should the images be shown at random? Has **NO** effect when `imageRepository` is set to `picsum`, as it is forced there.

**Type:** `boolean`
**Default:** `true` -| `width` | *Optional* - The width of the image in px.

**Type:** `int`
**Default:** `1920` -| `height` | *Optional* - The height of the image in px.

**Type:** `int`
**Default:** `1080` +| `width` | *Optional* - The width of the image in px. Only used when `imageRepository` is set to `picsum`

**Type:** `int`
**Default:** `1920` +| `height` | *Optional* - The height of the image in px. Only used when `imageRepository` is set to `picsum`

**Type:** `int`
**Default:** `1080` | `grayscale` | *Optional* - Should the image be grayscaled?

**Type:** `boolean`
**Default:** `false` | `blur` | *Optional* - Should the image be blurred?

**Type:** `boolean`
**Default:** `false` | `blurAmount` | *Optional* - If you want to blur it, how much?

**Type:** `int`
**Allowed:** minimum: `0`, maximum: `10`
Default `1` @@ -47,9 +47,9 @@ Options for `repositoryConfig` - [more information](https://github.com/skuethe/M | Option | Description |-----------------------|------------ -| `path` | *Required* - Path / URL to fetch images from.
- if `imageRepository` is set to `picsum` it is **ignored**
- if `imageRepository` is set to `nextcloud` it has to point to your nextcloud instance's specific share path
- if `imageRepository` is set to `localdirectory` it has to point to a local Path

**Type:** `string`
**Default:** `https://picsum.photos/` -| `username` | *Required* - The username if images require basic authentication (f.e. nextcloud).

**Type:** `string`
**Default:** `` -| `password` | *Required* - The password if images require basic authentication (f.e. nextcloud).

**Type:** `string`
**Default:** `` +| `path` | *Required for nextcloud and localdirectory* - Path / URL to fetch images from.
- if `imageRepository` is set to `picsum` it is **ignored**
- if `imageRepository` is set to `nextcloud` it has to point to your nextcloud instance's specific share path
- if `imageRepository` is set to `localdirectory` it has to point to a local Path

**Type:** `string`
**Default:** `https://picsum.photos/` +| `username` | *Required for nextcloud with basic auth* - The username if images require basic authentication.

**Type:** `string`
**Default:** `` +| `password` | *Required for nextcloud with basic auth* - The password if images require basic authentication.

**Type:** `string`
**Default:** `` Here are some examples for entries in `config.js` From bc40f71a720226cd1e552241e76ae5b8704c8fd5 Mon Sep 17 00:00:00 2001 From: skuethe Date: Mon, 12 Oct 2020 22:49:36 +0200 Subject: [PATCH 12/21] reworking animations of statusIcon --- MMM-RandomPhoto.css | 19 ++++++-- MMM-RandomPhoto.js | 110 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 109 insertions(+), 20 deletions(-) diff --git a/MMM-RandomPhoto.css b/MMM-RandomPhoto.css index 88de85c..0f14ef6 100644 --- a/MMM-RandomPhoto.css +++ b/MMM-RandomPhoto.css @@ -40,18 +40,27 @@ left: 10px; } -#randomPhotoIcon i.hidden { - display: none; +#randomPhotoIcon i { + opacity: 1; } -#randomPhotoIcon i.fading { +#randomPhotoIcon i.rpihidden { opacity: 0; - animation: fadeInAndOut 4s; } +@keyframes fadeOut { + 0% { opacity: 1; } + 100% { opacity: 0; } +} + +@keyframes fadeIn { + 0% { opacity: 0; } + 100% { opacity: 1; } +} @keyframes fadeInAndOut { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } -} \ No newline at end of file +} + diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index ab8ef3f..793ff6b 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -196,23 +196,97 @@ Module.register("MMM-RandomPhoto",{ return imageSource; }, - loadIcon: function() { - var pauseIcon = document.getElementById("randomPhotoIconPause"); - var playIcon = document.getElementById("randomPhotoIconPlay"); - if (this.running) { - pauseIcon.classList.add("hidden"); - playIcon.classList.remove("hidden"); - if (this.config.statusIconMode === "fade") { - pauseIcon.classList.remove("fading"); - playIcon.classList.add("fading"); + loadIcon: function(navigate="none") { + var self = this; + const statusIcon = document.getElementById("randomPhotoStatusIcon"); + + let currentIndex = -1; + let iconloadInProgress = false; + + // Animation stuff + const animationSteps = []; + const animateToNextState = () => { + requestAnimationFrame( () => { + currentIndex++; + if (currentIndex < animationSteps.length) { + animationSteps[currentIndex](); + //console.log("animateToNextState(): " + animationSteps[currentIndex].toString()); + } + }); + }; + const cleanupAnimation = () => { + statusIcon.style.animation = null; + iconloadInProgress = false; + } + + // MutationObserver to listen to class change events + const attrObserver = new MutationObserver((mutations) => { + mutations.forEach(mu => { + if (mu.attributeName === "class" && iconloadInProgress) { + animateToNextState(); + } + }); + }); + attrObserver.observe(statusIcon, { attributes: true }); + + // Eventlistener to listen to animation end events + statusIcon.addEventListener("animationend", () => { + animateToNextState(); + }); + + // Some helper strings for fontawsome icons + var translateStatus = ""; + if (self.running) { + translateStatus = "play" + } else { + translateStatus = "pause" + } + + // If we used the "next" / "previous" notifications + if (navigate != "none") { + if (!statusIcon.classList.contains("rpihidden")) { + animationSteps.push( + () => statusIcon.style.animation = "fadeOut 1s", + ); } + animationSteps.push( + () => statusIcon.className = "far fa-arrow-alt-circle-" + navigate + " rpihidden", + () => statusIcon.style.animation = "fadeInAndOut 4s", + () => statusIcon.className = "far fa-" + translateStatus + "-circle rpihidden", + ); + if (self.config.statusIconMode != "fade") { + animationSteps.push( + () => statusIcon.style.animation = "fadeIn 1s", + () => statusIcon.classList.remove("rpihidden"), + ); + } + animationSteps.push( + () => cleanupAnimation() + ); + iconloadInProgress = true; + animateToNextState(); } else { - playIcon.classList.add("hidden"); - pauseIcon.classList.remove("hidden"); - if (this.config.statusIconMode === "fade") { - playIcon.classList.remove("fading"); - pauseIcon.classList.add("fading"); + if (!statusIcon.classList.contains("rpihidden")) { + animationSteps.push( + () => statusIcon.style.animation = "fadeOut 1s", + ); + } + animationSteps.push( + () => statusIcon.className = "far fa-" + translateStatus + "-circle rpihidden", + () => statusIcon.style.animation = "fadeIn 1s", + () => statusIcon.classList.remove("rpihidden"), + ); + if (self.config.statusIconMode === "fade") { + animationSteps.push( + () => statusIcon.style.animation = "fadeOut 4s", + () => statusIcon.classList.add("rpihidden"), + ); } + animationSteps.push( + () => cleanupAnimation() + ); + iconloadInProgress = true; + animateToNextState(); } }, @@ -253,7 +327,7 @@ Module.register("MMM-RandomPhoto",{ this.config.statusIconPosition.split("_").forEach(function(extractedName) { statusIconObject.classList.add("rpi" + extractedName); }); - statusIconObject.innerHTML = ''; + statusIconObject.innerHTML = ''; wrapper.appendChild(statusIconObject); } return wrapper; @@ -287,12 +361,18 @@ Module.register("MMM-RandomPhoto",{ // Don't call the pause or resume functions here, so we can actually work with both states ("paused" and "active"), so independent of what "this.running" is set to clearTimeout(this.updateTimer); this.load(); + if (this.config.showStatusIcon) { + this.loadIcon("right"); + } } if (notification === "RANDOMPHOTO_PREVIOUS") { // Only allow this if we are NOT in random mode and NOT use picsum as a source if (!this.config.random && (this.nextcloud || this.localdirectory)) { clearTimeout(this.updateTimer); this.load("previous"); + if (this.config.showStatusIcon) { + this.loadIcon("left"); + } } } if (notification === "RANDOMPHOTO_TOGGLE") { From 926fec0904c8308ca102ce83cd8a1cef7e49ac27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20K=C3=BCthe?= Date: Sun, 18 Oct 2020 15:16:50 +0200 Subject: [PATCH 13/21] renamed global css property; reduced icon flashing from 4 to 2 seconds --- MMM-RandomPhoto.css | 4 ++-- MMM-RandomPhoto.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MMM-RandomPhoto.css b/MMM-RandomPhoto.css index 0f14ef6..b53ec0e 100644 --- a/MMM-RandomPhoto.css +++ b/MMM-RandomPhoto.css @@ -1,5 +1,5 @@ :root { - --blur-value: 0px; + --randomphoto-blur-value: 0px; } #randomPhoto img { @@ -17,7 +17,7 @@ } #randomPhoto img.blur { - filter: blur(var(--blur-value)); + filter: blur(var(--randomphoto-blur-value)); } #randomPhotoIcon { diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index 793ff6b..ccb3978 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -251,7 +251,7 @@ Module.register("MMM-RandomPhoto",{ } animationSteps.push( () => statusIcon.className = "far fa-arrow-alt-circle-" + navigate + " rpihidden", - () => statusIcon.style.animation = "fadeInAndOut 4s", + () => statusIcon.style.animation = "fadeInAndOut 2s", () => statusIcon.className = "far fa-" + translateStatus + "-circle rpihidden", ); if (self.config.statusIconMode != "fade") { @@ -308,8 +308,8 @@ Module.register("MMM-RandomPhoto",{ if (this.config.blur) { img1.classList.add("blur"); img2.classList.add("blur"); - img1.style.setProperty("--blur-value", this.config.blurAmount + "px"); - img2.style.setProperty("--blur-value", this.config.blurAmount + "px"); + img1.style.setProperty("--randomphoto-blur-value", this.config.blurAmount + "px"); + img2.style.setProperty("--randomphoto-blur-value", this.config.blurAmount + "px"); } } From ee97f25f0d603f200d3385a80e3c2db163b751e4 Mon Sep 17 00:00:00 2001 From: Dominik Bacher Date: Tue, 15 Feb 2022 17:31:27 +0100 Subject: [PATCH 14/21] add recursive search for localdirectory --- MMM-RandomPhoto.js | 1 + README.md | 2 ++ node_helper.js | 25 +++++++++++++++++-------- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index ccb3978..2612774 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -25,6 +25,7 @@ Module.register("MMM-RandomPhoto",{ path: "https://picsum.photos/", username: "", password: "", + recursive: false, }, width: 1920, height: 1080, diff --git a/README.md b/README.md index 5d9e80c..899417f 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ Options for `repositoryConfig` - [more information](https://github.com/skuethe/M | `path` | *Required for nextcloud and localdirectory* - Path / URL to fetch images from.
- if `imageRepository` is set to `picsum` it is **ignored**
- if `imageRepository` is set to `nextcloud` it has to point to your nextcloud instance's specific share path
- if `imageRepository` is set to `localdirectory` it has to point to a local Path

**Type:** `string`
**Default:** `https://picsum.photos/` | `username` | *Required for nextcloud with basic auth* - The username if images require basic authentication.

**Type:** `string`
**Default:** `` | `password` | *Required for nextcloud with basic auth* - The password if images require basic authentication.

**Type:** `string`
**Default:** `` +| `recursive` | *Optional for localdirectory* - Search recursive for images in path.

**Type:** `boolean`
**Default:** `false` Here are some examples for entries in `config.js` @@ -106,6 +107,7 @@ Here are some examples for entries in `config.js` imageRepository: "localdirectory", repositoryConfig: { path: "/home/USER/pictures/background/", + recursive: true, }, } }, diff --git a/node_helper.js b/node_helper.js index 9c300ce..4163328 100644 --- a/node_helper.js +++ b/node_helper.js @@ -40,10 +40,8 @@ module.exports = NodeHelper.create({ } }, - fetchLocalImageList: function() { + fetchLocalImageDirectory: function(path) { var self = this; - var imageList = []; - var path = self.config.repositoryConfig.path; // Validate path if (!fs.existsSync(path)) { @@ -56,13 +54,24 @@ module.exports = NodeHelper.create({ for (var f = 0; f < fileList.length; f++) { if (fileList[f].isFile()) { //TODO: add mime type check here - imageList.push(fileList[f].name); + self.imageList.push(path + "/" + fileList[f].name); + } + if ((self.config.repositoryConfig.recursive === true) && fileList[f].isDirectory()) { + self.fetchLocalImageDirectory(path + "/" + fileList[f].name); } } - this.imageList = imageList; - self.sendSocketNotification("IMAGE_LIST", imageList); return; } + }, + + fetchLocalImageList: function() { + var self = this; + var path = self.config.repositoryConfig.path; + + self.imageList = []; + self.fetchLocalImageDirectory(path); + + self.sendSocketNotification("IMAGE_LIST", self.imageList); return false; }, @@ -111,7 +120,7 @@ module.exports = NodeHelper.create({ fetchEncodedImage: async function(passedImageName) { var self = this; return new Promise(function(resolve, reject) { - var fullImagePath = self.config.repositoryConfig.path + passedImageName; + var fullImagePath = passedImageName; // Local files if (self.localdirectory) { @@ -158,4 +167,4 @@ module.exports = NodeHelper.create({ }, -}); \ No newline at end of file +}); From 04f104a654346fd6536d2544dab4ee575de450a7 Mon Sep 17 00:00:00 2001 From: the0frastus Date: Sun, 20 Feb 2022 15:41:43 +0100 Subject: [PATCH 15/21] prepend fullImagePath with nextcloud path --- node_helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node_helper.js b/node_helper.js index 4163328..00c7a1a 100644 --- a/node_helper.js +++ b/node_helper.js @@ -136,7 +136,7 @@ module.exports = NodeHelper.create({ "Authorization": "Basic " + new Buffer.from(self.config.repositoryConfig.username + ":" + self.config.repositoryConfig.password).toString("base64") } }; - https.get(fullImagePath, requestOptions, (response) => { + https.get(self.config.repositoryConfig.path + fullImagePath, requestOptions, (response) => { response.setEncoding('base64'); var fileEncoded = "data:" + response.headers["content-type"] + ";base64,"; response.on("data", (data) => { fileEncoded += data; }); From e63d1ea140d974d2011507a45f5808fefee374b3 Mon Sep 17 00:00:00 2001 From: dathbe Date: Fri, 29 Mar 2024 10:11:25 -0700 Subject: [PATCH 16/21] Non-substantive changes per recommendations (#14) * Update package.json Add repository and keywords * Add files via upload * Update MMM-RandomPhoto.js magic mirror > magicmirror2 * Update MMM-RandomPhoto.js new Date() > new Date(Date.now()) * Update README.md michmich > magicmirror * Update node_helper.js https > node:https * Update README.md superscript 2 * Update package.json change version Co-authored-by: skuethe <56306041+skuethe@users.noreply.github.com> * Update package.json spacing * Update package.json --------- Co-authored-by: skuethe <56306041+skuethe@users.noreply.github.com> --- MMM-RandomPhoto.js | 4 ++-- README.md | 4 ++-- example1.png | Bin 0 -> 12555 bytes node_helper.js | 2 +- package.json | 14 +++++++++++++- 5 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 example1.png diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index 2612774..d26af66 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -1,6 +1,6 @@ /* global Module */ -/* Magic Mirror +/* MagicMirror² * Module: MMM-RandomPhoto * * By Diego Vieira @@ -138,7 +138,7 @@ Module.register("MMM-RandomPhoto",{ url = url + "=" + self.config.blurAmount; } } - url = url + (url.indexOf('?') > -1 ? '&' : '?') + (new Date().getTime()); + url = url + (url.indexOf('?') > -1 ? '&' : '?') + (new Date(Date.now()).getTime()); self.smoothImageChange(url); } diff --git a/README.md b/README.md index 899417f..1748f40 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MMM-RandomPhoto -This a module for the [MagicMirror](https://github.com/MichMich/MagicMirror). It will show a (random) photo from any of these sources: +This a module for the [MagicMirror²](https://github.com/MagicMirrorOrg/MagicMirror). It will show a (random) photo from any of these sources: - [picsum.photos](https://picsum.photos) - a share of any [nextcloud](https://nextcloud.com/) instance @@ -139,5 +139,5 @@ Thinking about implementing the following things: ## Special Thanks -- [Michael Teeuw](https://github.com/MichMich) for creating the awesome [MagicMirror2](https://github.com/MichMich/MagicMirror) project that made this module possible. +- [Michael Teeuw](https://github.com/MichMich) for creating the awesome [MagicMirror²](https://github.com/MagicMirrorOrg/MagicMirror) project that made this module possible. - [Diego Vieira](https://github.com/diego-vieira) for [initially](https://github.com/diego-vieira/MMM-RandomPhoto) creating this module. diff --git a/example1.png b/example1.png new file mode 100644 index 0000000000000000000000000000000000000000..9161d4fec48537c7877f849add0f255686987c1d GIT binary patch literal 12555 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 AmjD0& literal 0 HcmV?d00001 diff --git a/node_helper.js b/node_helper.js index 00c7a1a..7cff2c1 100644 --- a/node_helper.js +++ b/node_helper.js @@ -1,5 +1,5 @@ require("url"); // for nextcloud -const https = require("https"); // for nextcloud +const https = require("node:https"); // for nextcloud const fs = require("fs"); // for localdirectory const NodeHelper = require("node_helper"); diff --git a/package.json b/package.json index 25e3c20..73e588d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,19 @@ { "name": "magic-mirror-module-random-photo", - "version": "1.0.1", + "version": "1.1.0", "description": "Show a random photo from an url", + "repository": { + "type": "git", + "url": "git+https://github.com/skuethe/MMM-RandomPhoto.git" + }, + "keywords": [ + "MagicMirror", + "Module", + "Photo", + "Image", + "Picsum", + "Nextcloud" + ], "main": "MMM-RandomPhoto.js", "author": "skuethe", "license": "ISC", From 37fe8b6c5cd87b5304fdc36def23fa1adc7ac3c1 Mon Sep 17 00:00:00 2001 From: skuethe <56306041+skuethe@users.noreply.github.com> Date: Sat, 5 Jul 2025 13:29:52 +0200 Subject: [PATCH 17/21] fix: migrate express route parameter to v5 Signed-off-by: skuethe <56306041+skuethe@users.noreply.github.com> --- node_helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node_helper.js b/node_helper.js index 7cff2c1..20a6de0 100644 --- a/node_helper.js +++ b/node_helper.js @@ -13,7 +13,7 @@ module.exports = NodeHelper.create({ this.localdirectory = false; this.imageList = []; - this.expressApp.get("/" + this.name + "/images/:randomImageName(*)", async function(request, response) { + this.expressApp.get("/" + this.name + "/images/:randomImageName", async function(request, response) { var imageBase64Encoded = await self.fetchEncodedImage(request.params.randomImageName); response.send(imageBase64Encoded); }); From 7bceb3adff192931c7f784f0d41a55088dab6a50 Mon Sep 17 00:00:00 2001 From: skuethe <56306041+skuethe@users.noreply.github.com> Date: Wed, 16 Jul 2025 10:06:03 +0200 Subject: [PATCH 18/21] fix: url-encode image paths Signed-off-by: skuethe <56306041+skuethe@users.noreply.github.com> --- node_helper.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node_helper.js b/node_helper.js index 20a6de0..1222d54 100644 --- a/node_helper.js +++ b/node_helper.js @@ -14,7 +14,7 @@ module.exports = NodeHelper.create({ this.imageList = []; this.expressApp.get("/" + this.name + "/images/:randomImageName", async function(request, response) { - var imageBase64Encoded = await self.fetchEncodedImage(request.params.randomImageName); + var imageBase64Encoded = await self.fetchEncodedImage(decodeURIComponent(request.params.randomImageName)); response.send(imageBase64Encoded); }); }, @@ -54,7 +54,7 @@ module.exports = NodeHelper.create({ for (var f = 0; f < fileList.length; f++) { if (fileList[f].isFile()) { //TODO: add mime type check here - self.imageList.push(path + "/" + fileList[f].name); + self.imageList.push(encodeURIComponent(path + "/" + fileList[f].name)); } if ((self.config.repositoryConfig.recursive === true) && fileList[f].isDirectory()) { self.fetchLocalImageDirectory(path + "/" + fileList[f].name); @@ -99,7 +99,7 @@ module.exports = NodeHelper.create({ if (imageList && imageList.length > 0) { imageList.forEach(function(item, index) { // remove clutter and the pathing from the entry -> only save file name - imageList[index] = item.replace("href>" + urlParts.pathname, ""); + imageList[index] = encodeURIComponent(item.replace("href>" + urlParts.pathname, "")); //console.log("[" + self.name + "] Found entry: " + imageList[index]); }); self.sendSocketNotification("IMAGE_LIST", imageList); From d8c034a33188df3a8d9d5a652c4229701ac0de3b Mon Sep 17 00:00:00 2001 From: skuethe <56306041+skuethe@users.noreply.github.com> Date: Wed, 16 Jul 2025 10:19:11 +0200 Subject: [PATCH 19/21] chore: bump version Signed-off-by: skuethe <56306041+skuethe@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 73e588d..e31fa9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "magic-mirror-module-random-photo", - "version": "1.1.0", + "version": "1.1.2", "description": "Show a random photo from an url", "repository": { "type": "git", From 7c7f0031a717f5a13ade7d13f4b05785b203ccd1 Mon Sep 17 00:00:00 2001 From: henrynvn09 Date: Thu, 17 Jul 2025 21:20:44 -0700 Subject: [PATCH 20/21] add regex exclude to localdirectory --- .gitignore | 1 + MMM-RandomPhoto.js | 3 ++- README.md | 2 ++ node_helper.js | 7 ++++++- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3c3629e..d5f19d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +package-lock.json diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index d26af66..ace35d9 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -26,6 +26,7 @@ Module.register("MMM-RandomPhoto",{ username: "", password: "", recursive: false, + exclude: [], }, width: 1920, height: 1080, @@ -107,7 +108,7 @@ Module.register("MMM-RandomPhoto",{ if (self.localdirectory || self.nextcloud) { if (self.imageList && self.imageList.length > 0) { url = "/" + this.name + "/images/" + this.returnImageFromList(mode); - + jQuery.ajax({ method: "GET", url: url, diff --git a/README.md b/README.md index 1748f40..3a13c11 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Options for `repositoryConfig` - [more information](https://github.com/skuethe/M | `username` | *Required for nextcloud with basic auth* - The username if images require basic authentication.

**Type:** `string`
**Default:** `` | `password` | *Required for nextcloud with basic auth* - The password if images require basic authentication.

**Type:** `string`
**Default:** `` | `recursive` | *Optional for localdirectory* - Search recursive for images in path.

**Type:** `boolean`
**Default:** `false` +| `exclude` | *Optional for localdirectory* - Exclude matching regex files.

**Type:** `list`
**Default:** `[]` Here are some examples for entries in `config.js` @@ -108,6 +109,7 @@ Here are some examples for entries in `config.js` repositoryConfig: { path: "/home/USER/pictures/background/", recursive: true, + exclude: ["tmp", "#recycle"], }, } }, diff --git a/node_helper.js b/node_helper.js index 1222d54..bc9c32d 100644 --- a/node_helper.js +++ b/node_helper.js @@ -1,6 +1,7 @@ require("url"); // for nextcloud const https = require("node:https"); // for nextcloud const fs = require("fs"); // for localdirectory +const Log = require("logger"); const NodeHelper = require("node_helper"); @@ -49,14 +50,18 @@ module.exports = NodeHelper.create({ return false; } + const excludePattern = self.config.repositoryConfig.exclude?.map(pattern => new RegExp(pattern)); + var fileList = fs.readdirSync(path, { withFileTypes: true }); if (fileList.length > 0) { for (var f = 0; f < fileList.length; f++) { + if (excludePattern?.some(regex => regex.test(fileList[f].name))) continue; + if (fileList[f].isFile()) { //TODO: add mime type check here self.imageList.push(encodeURIComponent(path + "/" + fileList[f].name)); } - if ((self.config.repositoryConfig.recursive === true) && fileList[f].isDirectory()) { + if ((self.config.repositoryConfig.recursive === true) && fileList[f].isDirectory()) { self.fetchLocalImageDirectory(path + "/" + fileList[f].name); } } From ff87f77365ea559cb5b830dcb0f71beca4f4d36b Mon Sep 17 00:00:00 2001 From: Henry Nguyen <43089637+henrynvn09@users.noreply.github.com> Date: Fri, 18 Jul 2025 12:57:43 -0700 Subject: [PATCH 21/21] reindent --- MMM-RandomPhoto.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MMM-RandomPhoto.js b/MMM-RandomPhoto.js index ace35d9..a403906 100644 --- a/MMM-RandomPhoto.js +++ b/MMM-RandomPhoto.js @@ -26,7 +26,7 @@ Module.register("MMM-RandomPhoto",{ username: "", password: "", recursive: false, - exclude: [], + exclude: [], }, width: 1920, height: 1080,