From d90cebb478ded4d99bc4cf08faeefd87af0479e0 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L de Mello" Date: Mon, 10 Nov 2025 15:03:01 -0300 Subject: [PATCH] feat(ipub,soundtrck): on screen threshold trigger to start playing --- .epub/example/OEBPS/scripts/ipub.js | 177 ++++++++++++++++-- .../example/OEBPS/sections/section0001.xhtml | 3 + .epub/example/OEBPS/styles/stylesheet.css | 12 ++ .gitignore | 1 - .scrollable-audio | 1 + go.work.sum | 49 +---- repository/role.go | 17 ++ smalltrip | 2 +- templates/projects.html | 1 + 9 files changed, 204 insertions(+), 59 deletions(-) create mode 160000 .scrollable-audio create mode 100644 repository/role.go create mode 100644 templates/projects.html diff --git a/.epub/example/OEBPS/scripts/ipub.js b/.epub/example/OEBPS/scripts/ipub.js index d5d2e5b..7e10214 100644 --- a/.epub/example/OEBPS/scripts/ipub.js +++ b/.epub/example/OEBPS/scripts/ipub.js @@ -132,6 +132,7 @@ class IPUBBody extends IPUBElement { IPUBImage, IPUBInteraction, IPUBSoundtrack, + IPUBTrigger, ]) { console.info(`IPUBBody: Defining custom element <${e.elementName}>`); globalThis.customElements.define(e.elementName, e); @@ -282,6 +283,7 @@ class IPUBAudio extends IPUBElement { } if (this.#isFading) { + // TODO: Be able to force fading to be canceled return; } @@ -553,7 +555,7 @@ class IPUBSoundtrack extends IPUBElement { `IPUBSoundtrack: error while trying to play audio, error: ${e}`, { error: e, - audio: audio, + audio: last, }, ); } @@ -563,23 +565,32 @@ class IPUBSoundtrack extends IPUBElement { * @private */ static #observer = (() => { - return new IntersectionObserver((entries) => { - for (const { intersectionRatio, target, time } of entries) { - /** @type {IPUBSoundtrack} */ - const soundtrack = target; + return new IntersectionObserver( + (entries) => { + for (const { intersectionRatio, target, time } of entries) { + /** @type {IPUBSoundtrack} */ + const soundtrack = + target.tagName === IPUBTrigger.elementName + ? getAncestor(target, IPUBSoundtrack.elementName) + : target; - if (intersectionRatio > 0) { - console.debug(`${soundtrack.id} is on screen at ${time}`, soundtrack); - this.#onScreenStack.add(soundtrack); - } else { - console.debug( - `${soundtrack.id} is not on screen ${time}`, - soundtrack, - ); - this.#onScreenStack.delete(soundtrack); + if (intersectionRatio === 1) { + console.debug( + `${soundtrack.id} is on screen at ${time}`, + soundtrack, + ); + this.#onScreenStack.add(soundtrack); + } else { + console.debug( + `${soundtrack.id} is not on screen ${time}`, + soundtrack, + ); + this.#onScreenStack.delete(soundtrack); + } } - } - }); + }, + { threshold: 1 }, + ); })(); /** @@ -637,10 +648,142 @@ class IPUBSoundtrack extends IPUBElement { return; } - IPUBSoundtrack.#observer.observe(this); + const trigger = this.querySelector(IPUBTrigger.elementName); + if (trigger) { + IPUBSoundtrack.#observer.observe(trigger); + } else { + IPUBSoundtrack.#observer.observe(this); + } } // TODO(guz013): Handle if element is moved, it's group should be updated + // TODO(guz013): Handle if element is deleted/disconnected, it should be removed from observer +} + +class IPUBTrigger extends IPUBElement { + static elementName = "ipub-trigger"; + static observedAttributes = ["height", "width"].concat( + IPUBElement.observedAttributes, + ); + + // TODO: Make this observer global + /** @private */ + static #resizeObserver = new ResizeObserver((bodies) => { + for (const { target: body, contentRect } of bodies) { + const height = Math.max(body.scrollHeight, contentRect.height); + const width = Math.max(body.scrollWidth, contentRect.width); + + for (const trigger of IPUBTrigger.#resizableTriggers.get(body)) { + const percH = trigger.getAttribute("height"); + if (percH) { + trigger.style.setProperty( + "--ipub-height", + `${Math.round((height / 100) * Number.parseFloat(percH))}px`, + ); + } + + const percW = trigger.getAttribute("width"); + if (percW) { + trigger.style.setProperty( + "--ipub-width", + `${Math.round((width / 100) * Number.parseFloat(percW))}px`, + ); + } + } + } + }); + /** + * @private + * @type {Map>} + */ + static #resizableTriggers = new Map(); + + // FIXME: trigger can be the same size as viewport, cap it to 80% of viewport + // height and 100% of viewport width + + connectedCallback() { + super.connectedCallback(); + + const body = getAncestor(this, "ipub-body"); + if (!body) { + console.error("IPUBTrigger: element must be a descendant of ipub-body"); + return; + } + + console.debug( + `IPUBTrigger#${this.id}: adding ${IPUBBody.elementName}#${body.id} from resize observer`, + ); + IPUBTrigger.#resizeObserver.observe(body); + if (this.getAttribute("height") || this.getAttribute("width")) { + IPUBTrigger.#resizableTriggers.set( + body, + (IPUBTrigger.#resizableTriggers.get(body) || new Set()).add(this), + ); + } + } + + attributeChangedCallback(name, oldValue, newValue) { + super.attributeChangedCallback(name, oldValue, newValue); + + const body = getAncestor(this, "ipub-body"); + if (!body) { + console.error("IPUBTrigger: element must be a descendant of ipub-body"); + return; + } + + const set = IPUBTrigger.#resizableTriggers.get(body) || new Set(); + if (this.getAttribute("height") || this.getAttribute("width")) { + set.add(this); + } else { + set.delete(this); + } + + if (name === "width" || name === "height") { + const height = Math.max( + body.scrollHeight, + body.getBoundingClientRect().height, + ); + const width = Math.max( + body.scrollWidth, + body.getBoundingClientRect().width, + ); + + const percH = this.getAttribute("height"); + if (percH) { + this.style.setProperty( + "--ipub-height", + `${Math.round((height / 100) * Number.parseFloat(percH))}px`, + ); + } + + const percW = this.getAttribute("width"); + if (percW) { + this.style.setProperty( + "--ipub-width", + `${Math.round((width / 100) * Number.parseFloat(percW))}px`, + ); + } + } + } + + disconnectedCallback() { + const set = IPUBTrigger.#resizableTriggers.get(body) || new Set(); + set.delete(this); + + if (set.size === 0) { + const body = getAncestor(this, "ipub-body"); + if (!body) { + console.error("IPUBTrigger: element must be a descendant of ipub-body"); + return; + } + + console.debug( + `IPUBTrigger#${this.id}: removing ${IPUBBody.elementName}#${body.id} from resize observer`, + ); + IPUBTrigger.#resizableTriggers.delete(body); + IPUBTrigger.#resizeObserver.unobserve(body); + } + } } /** diff --git a/.epub/example/OEBPS/sections/section0001.xhtml b/.epub/example/OEBPS/sections/section0001.xhtml index 7cee4eb..875d6ea 100644 --- a/.epub/example/OEBPS/sections/section0001.xhtml +++ b/.epub/example/OEBPS/sections/section0001.xhtml @@ -28,6 +28,7 @@ +