summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Keller <tjk@tjkeller.xyz>2025-05-31 17:17:49 -0500
committerTim Keller <tjk@tjkeller.xyz>2025-05-31 17:17:49 -0500
commit9e8bc4a1e2361c4a4e4a0657df9cd893acc68682 (patch)
treef4b165ebbb404c5b720a1fba85231bc924a45a2c
parentcaacbe50c567da5d267558b52b69120d1ccabd42 (diff)
downloadimmich-frame-9e8bc4a1e2361c4a4e4a0657df9cd893acc68682.tar.xz
immich-frame-9e8bc4a1e2361c4a4e4a0657df9cd893acc68682.zip
connector communicates with websocket and make slides into class
-rw-r--r--package-lock.json157
-rw-r--r--package.json3
-rw-r--r--src/connector.js21
-rw-r--r--src/slides.js98
-rw-r--r--src/style.css2
5 files changed, 239 insertions, 42 deletions
diff --git a/package-lock.json b/package-lock.json
index 372f481..a2bc97d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,8 @@
"": {
"dependencies": {
"@fontsource/overpass": "^5.2.5",
- "flickity": "^3.0.0"
+ "flickity": "^3.0.0",
+ "socket.io-client": "^4.8.1"
},
"devDependencies": {
"autoprefixer": "^10.4.21",
@@ -193,6 +194,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@socket.io/component-emitter": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+ "license": "MIT"
+ },
"node_modules/@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -1914,6 +1921,72 @@
"node": ">= 0.8"
}
},
+ "node_modules/engine.io-client": {
+ "version": "6.6.3",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
+ "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.17.1",
+ "xmlhttprequest-ssl": "~2.1.1"
+ }
+ },
+ "node_modules/engine.io-client/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/engine.io-client/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/engine.io-client/node_modules/ws": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/engine.io-parser": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+ "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/enhanced-resolve": {
"version": "5.18.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
@@ -5898,6 +5971,80 @@
"node": ">=0.10.0"
}
},
+ "node_modules/socket.io-client": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
+ "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.2",
+ "engine.io-client": "~6.6.1",
+ "socket.io-parser": "~4.2.4"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/socket.io-client/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io-client/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/socket.io-parser": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+ "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/socket.io-parser/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io-parser/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
"node_modules/sockjs": {
"version": "0.3.24",
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",
@@ -7459,6 +7606,14 @@
"optional": true
}
}
+ },
+ "node_modules/xmlhttprequest-ssl": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
+ "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
}
}
}
diff --git a/package.json b/package.json
index be6dc67..7034250 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
},
"dependencies": {
"@fontsource/overpass": "^5.2.5",
- "flickity": "^3.0.0"
+ "flickity": "^3.0.0",
+ "socket.io-client": "^4.8.1"
},
"devDependencies": {
"autoprefixer": "^10.4.21",
diff --git a/src/connector.js b/src/connector.js
index 1fa513b..6c557a8 100644
--- a/src/connector.js
+++ b/src/connector.js
@@ -1,6 +1,24 @@
+import io from "socket.io-client"
+
class APIConnector {
constructor(url) {
this.url = url ?? ""
+ this.socket = io(url)
+
+ this.asset_index = 0
+ this.asset = null
+ this.prevAssets = null
+ this.nextAssets = null
+ this.seekCallbacks = []
+
+ this.socket.on("seek", e => {
+ this.asset_index = e.asset_index
+ this.asset = e.asset
+ this.prevAssets = e.prev_assets
+ this.nextAssets = e.next_assets
+ for (const cb of this.seekCallbacks)
+ cb()
+ })
}
#fetch(endpoint) {
@@ -23,7 +41,8 @@ class APIConnector {
}
seek(increment) {
- return this.fetch(`/seek?increment=${increment}`)
+ //return this.fetch(`/seek?increment=${increment}`)
+ this.socket.emit("seek", increment)
}
fetchAlbums() {
diff --git a/src/slides.js b/src/slides.js
index 8b4080f..94af74e 100644
--- a/src/slides.js
+++ b/src/slides.js
@@ -2,17 +2,57 @@ const Flickity = require("flickity")
import "flickity/dist/flickity.min.css"
import apiConnector from "./connector.js"
-export default function initSlides(slidesContainer) {
+class Slides {
+ constructor(slidesContainer) {
+ /* previous selected index */
+ this.selectedIndex = 0
+
+ this.slidesContainer = slidesContainer
+
+ /* initialize slides */
+ this.flickity = new Flickity("#slideshow-carousel", {
+ wrapAround: true,
+ prevNextButtons: false,
+ pageDots: false,
+ resize: true,
+ setGallerySize: false,
+ })
+
+ this.flickity.on("scroll", progress => { this.scroll(progress) })
+ this.flickity.on("staticClick", (e, pointer, cellElement, cellIndex) => { this.staticClick(e, pointer, cellElement, cellIndex) })
+ this.flickity.on("dragEnd", () => { this.seek() })
+ this.initImages()
+
+ /* initialize seek buttons */
+ const seekPrevButton = this.slidesContainer.querySelector("#prevSlide")
+ const seekNextButton = this.slidesContainer.querySelector("#nextSlide")
+
+ seekPrevButton.addEventListener("click", () => { this.flickity.previous() ; this.seek() })
+ seekNextButton.addEventListener("click", () => { this.flickity.next() ; this.seek() })
+ }
+
+ seek() {
+ // this is just like calculating movement in lazycachelist.py
+ // gets the min of the absolute values and returns signed value
+ const increment = [
+ this.flickity.selectedIndex - this.selectedIndex, // no list wrap
+ this.flickity.selectedIndex - this.flickity.cells.length, // wrap backwards (0 -> -1)
+ this.flickity.cells.length - this.selectedIndex, // wrap forwards (-1 -> 0)
+ ].reduce((key, v) => Math.abs(v) < Math.abs(key) ? v : key)
+ this.selectedIndex = this.flickity.selectedIndex
+ apiConnector.seek(increment)
+ }
+
/* Flickity function for scrolling to ensure next and prev pics are always
* visible and to transition between states */
- function scroll(progress) {
- const normalizedProgress = progress / (1 / (flkty.cells.length-1))
+ scroll(progress) {
+ const normalizedProgress = progress / (1 / (this.flickity.cells.length-1))
const liveSelectedIndex = Math.round(normalizedProgress)
const localizedProgress = normalizedProgress - liveSelectedIndex
- const prevSelectedCell = flkty.cells.at(liveSelectedIndex-1).element
- const liveSelectedCell = flkty.cells.at(liveSelectedIndex % flkty.cells.length).element
- const nextSelectedCell = flkty.cells.at((liveSelectedIndex+1) % flkty.cells.length).element
+ const prevSelectedCell = this.flickity.cells.at((liveSelectedIndex-1) % this.flickity.cells.length).element
+ const liveSelectedCell = this.flickity.cells.at((liveSelectedIndex ) % this.flickity.cells.length).element
+ const nextSelectedCell = this.flickity.cells.at((liveSelectedIndex+1) % this.flickity.cells.length).element
const prevSelectedImage = prevSelectedCell.firstElementChild
const liveSelectedImage = liveSelectedCell.firstElementChild
@@ -28,52 +68,32 @@ export default function initSlides(slidesContainer) {
}
/* jump to clicked on slide */
- function staticClick(e, pointer, cellElement, cellIndex) {
- flkty.select(cellIndex)
+ staticClick(e, pointer, cellElement, cellIndex) {
+ this.flickity.select(cellIndex)
+ this.seek()
}
/* make sure images have correct margin when loaded since scroll function
* depends on them being loaded */
- function positionImageStatic(img) {
+ positionImageStatic(img) {
const i = parseInt(img.dataset.index)
- if (i == flkty.selectedIndex)
+ if (i == this.flickity.selectedIndex)
img.style.marginLeft = (img.parentElement.clientWidth - img.clientWidth) / 2 + "px"
- else if ((i + 1) % flkty.cells.length == flkty.selectedIndex)
+ else if ((i + 1) % this.flickity.cells.length == this.flickity.selectedIndex)
img.style.marginLeft = img.parentElement.clientWidth - img.clientWidth + "px"
}
- function imageLoaded(e) { positionImageStatic(e.target) }
- function initImages() {
- const imgs = slidesContainer.querySelectorAll("#slideshow-carousel img")
+ imageLoaded(e) { this.positionImageStatic(e.target) }
+ initImages() {
+ const imgs = this.slidesContainer.querySelectorAll("#slideshow-carousel img")
for (let i = 0; i < imgs.length; i++) {
const img = imgs[i]
img.dataset.index = i
- img.addEventListener("load", imageLoaded)
+ img.addEventListener("load", this.imageLoaded)
if (img.complete)
- positionImageStatic(img)
+ this.positionImageStatic(img)
}
}
-
- /* initialize slides */
- const flkty = new Flickity('#slideshow-carousel', {
- wrapAround: true,
- prevNextButtons: false,
- pageDots: false,
- resize: true,
- setGallerySize: false,
- })
-
- flkty.on("scroll", scroll)
- flkty.on("staticClick", staticClick)
- initImages()
-
- /* initialize seek buttons */
- const seekPrevButton = slidesContainer.querySelector("#prevSlide")
- const seekNextButton = slidesContainer.querySelector("#nextSlide")
-
- seekPrevButton.addEventListener("click", () => { apiConnector.seek(-1) })
- seekNextButton.addEventListener("click", () => { apiConnector.seek(+1) })
-
- /* done */
- return true
}
+
+export default slidesContainer => { new Slides(slidesContainer); return true }
diff --git a/src/style.css b/src/style.css
index 607e6ec..e84921e 100644
--- a/src/style.css
+++ b/src/style.css
@@ -94,6 +94,7 @@ svg {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 2.5rem;
+ max-width: 1366px;
margin: auto;
padding: 2rem;
@@ -111,6 +112,7 @@ svg {
#albums-container {
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
+ width: 100%;
.album-info { font-size-adjust: .4; }
}
}