diff options
| author | Tim Keller <tjk@tjkeller.xyz> | 2025-05-31 17:17:49 -0500 |
|---|---|---|
| committer | Tim Keller <tjk@tjkeller.xyz> | 2025-05-31 17:17:49 -0500 |
| commit | 9e8bc4a1e2361c4a4e4a0657df9cd893acc68682 (patch) | |
| tree | f4b165ebbb404c5b720a1fba85231bc924a45a2c | |
| parent | caacbe50c567da5d267558b52b69120d1ccabd42 (diff) | |
| download | immich-frame-9e8bc4a1e2361c4a4e4a0657df9cd893acc68682.tar.xz immich-frame-9e8bc4a1e2361c4a4e4a0657df9cd893acc68682.zip | |
connector communicates with websocket and make slides into class
| -rw-r--r-- | package-lock.json | 157 | ||||
| -rw-r--r-- | package.json | 3 | ||||
| -rw-r--r-- | src/connector.js | 21 | ||||
| -rw-r--r-- | src/slides.js | 98 | ||||
| -rw-r--r-- | src/style.css | 2 |
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; } } } |
