From 77bcd2887e2642dd3ed4c6f4185a133c75407d75 Mon Sep 17 00:00:00 2001 From: Steven Vandevelde Date: Mon, 27 Apr 2026 22:49:36 +0200 Subject: [PATCH] feat: queue facet --- src/_data/facets.json | 8 +- src/facets/playback/queue/index.html | 144 ++++++++++++ src/facets/playback/queue/index.inline.js | 212 ++++++++++++++++++ .../lit-html/directives/async-append.js | 1 + .../lit-html/directives/async-replace.js | 1 + src/vendor/lit-html/directives/cache.js | 1 + src/vendor/lit-html/directives/choose.js | 1 + src/vendor/lit-html/directives/class-map.js | 1 + src/vendor/lit-html/directives/guard.js | 1 + src/vendor/lit-html/directives/if-defined.js | 1 + src/vendor/lit-html/directives/join.js | 1 + src/vendor/lit-html/directives/keyed.js | 1 + src/vendor/lit-html/directives/live.js | 1 + src/vendor/lit-html/directives/map.js | 1 + src/vendor/lit-html/directives/range.js | 1 + src/vendor/lit-html/directives/ref.js | 1 + src/vendor/lit-html/directives/repeat.js | 1 + src/vendor/lit-html/directives/style-map.js | 1 + .../lit-html/directives/template-content.js | 1 + src/vendor/lit-html/directives/unsafe-html.js | 1 + .../lit-html/directives/unsafe-mathml.js | 1 + src/vendor/lit-html/directives/unsafe-svg.js | 1 + src/vendor/lit-html/directives/until.js | 1 + src/vendor/lit-html/directives/when.js | 1 + 24 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 src/facets/playback/queue/index.html create mode 100644 src/facets/playback/queue/index.inline.js create mode 100644 src/vendor/lit-html/directives/async-append.js create mode 100644 src/vendor/lit-html/directives/async-replace.js create mode 100644 src/vendor/lit-html/directives/cache.js create mode 100644 src/vendor/lit-html/directives/choose.js create mode 100644 src/vendor/lit-html/directives/class-map.js create mode 100644 src/vendor/lit-html/directives/guard.js create mode 100644 src/vendor/lit-html/directives/if-defined.js create mode 100644 src/vendor/lit-html/directives/join.js create mode 100644 src/vendor/lit-html/directives/keyed.js create mode 100644 src/vendor/lit-html/directives/live.js create mode 100644 src/vendor/lit-html/directives/map.js create mode 100644 src/vendor/lit-html/directives/range.js create mode 100644 src/vendor/lit-html/directives/ref.js create mode 100644 src/vendor/lit-html/directives/repeat.js create mode 100644 src/vendor/lit-html/directives/style-map.js create mode 100644 src/vendor/lit-html/directives/template-content.js create mode 100644 src/vendor/lit-html/directives/unsafe-html.js create mode 100644 src/vendor/lit-html/directives/unsafe-mathml.js create mode 100644 src/vendor/lit-html/directives/unsafe-svg.js create mode 100644 src/vendor/lit-html/directives/until.js create mode 100644 src/vendor/lit-html/directives/when.js diff --git a/src/_data/facets.json b/src/_data/facets.json index 90d6eb30..8b919a1e 100644 --- a/src/_data/facets.json +++ b/src/_data/facets.json @@ -31,7 +31,6 @@ "url": "facets/themes/blur/browser/facet/index.html", "title": "Blur / Browser", "category": "Browsing", - "featured": true, "desc": "Collection browser and search with favourite toggling, date grouping, and virtual scrolling." }, { @@ -168,6 +167,13 @@ "category": "Data", "desc": "Process all your audio sources into tracks. Shows a progress bar when processing is occuring." }, + { + "url": "facets/playback/queue/index.html", + "title": "Queue", + "category": "Playback", + "featured": true, + "desc": "Manage your playback queue. Reorder upcoming tracks, clear the queue, and view playback history." + }, { "url": "facets/misc/scrobble/index.html", "title": "Scrobble", diff --git a/src/facets/playback/queue/index.html b/src/facets/playback/queue/index.html new file mode 100644 index 00000000..d241183f --- /dev/null +++ b/src/facets/playback/queue/index.html @@ -0,0 +1,144 @@ + + +
+
+
+ + + Diffuse + + + +
+

Queue

+

+ Manage your playback queue. Reorder upcoming tracks or clear the queue. Tracks marked + M were manually added and survive an auto-clear. +

+

+ + +

+
+ +
+ + + +

The queue is empty.

+
+
+ + diff --git a/src/facets/playback/queue/index.inline.js b/src/facets/playback/queue/index.inline.js new file mode 100644 index 00000000..c191bb31 --- /dev/null +++ b/src/facets/playback/queue/index.inline.js @@ -0,0 +1,212 @@ +import { html, render as litRender } from "lit-html"; +import { keyed } from "~/vendor/lit-html/directives/keyed.js"; + +import foundation from "~/common/foundation.js"; +import { effect } from "~/common/signal.js"; + +/** + * @import { Track } from "~/definitions/types.d.ts" + */ + +foundation.setup({ title: "Queue | Diffuse" }); + +//////////////////////////////////////////// +// SETUP +//////////////////////////////////////////// + +const [queue, outputOrchestrator] = await Promise.all([ + foundation.engine.queue(), + foundation.orchestrator.output(), +]); + +await Promise.all([ + customElements.whenDefined(queue.localName), + customElements.whenDefined(outputOrchestrator.localName), +]); + +//////////////////////////////////////////// +// ELEMENTS +//////////////////////////////////////////// + +const nowSection = + /** @type {HTMLElement} */ (document.querySelector("#now-section")); +const nowList = + /** @type {HTMLElement} */ (document.querySelector("#now-list")); +const futureSection = + /** @type {HTMLElement} */ (document.querySelector("#future-section")); +const futureList = + /** @type {HTMLElement} */ (document.querySelector("#future-list")); +const pastSection = + /** @type {HTMLElement} */ (document.querySelector("#past-section")); +const pastList = + /** @type {HTMLElement} */ (document.querySelector("#past-list")); +const queueEmpty = + /** @type {HTMLElement} */ (document.querySelector("#queue-empty")); + +//////////////////////////////////////////// +// HELPERS +//////////////////////////////////////////// + +/** + * @param {string} id + * @param {Track[]} tracks + */ +const findTrack = (id, tracks) => tracks.find((t) => t.id === id); + +/** + * @param {Track | undefined} track + * @param {string} fallbackId + */ +const trackTitle = (track, fallbackId) => track?.tags?.title ?? fallbackId; + +/** + * @param {Track | undefined} track + */ +const trackArtist = (track) => + track?.tags?.artist ?? track?.tags?.albumartist ?? null; + +//////////////////////////////////////////// +// RENDER +//////////////////////////////////////////// + +effect(() => { + const now = queue.now(); + const past = queue.past(); + const future = queue.future(); + + const tracksCol = outputOrchestrator.tracks.collection(); + const tracks = tracksCol.state === "loaded" ? tracksCol.data : []; + + const hasAnything = now !== null || past.length > 0 || future.length > 0; + queueEmpty.hidden = hasAnything; + + // Now playing + nowSection.hidden = now === null; + if (now !== null) { + const track = findTrack(now.id, tracks); + litRender( + html` +
  • +
    + ${trackTitle( + track, + now.id, + )} + ${trackArtist(track) + ? html` + ${trackArtist(track)} + ` + : null} +
    +
  • + `, + nowList, + ); + } + + // Up next — move() operates on the flat [past..., now, future...] list + futureSection.hidden = future.length === 0; + if (future.length > 0) { + const offset = past.length + (now !== null ? 1 : 0); + + litRender( + html` + ${future.map((item, i) => { + const track = findTrack(item.id, tracks); + return keyed(item.id, html` +
  • +
    + ${trackTitle( + track, + item.id, + )} + ${trackArtist(track) + ? html` + ${trackArtist( + track, + )} + ` + : null} +
    + ${item.manualEntry + ? html` + M + ` + : null} +
    + + +
    +
  • + `); + })} + `, + futureList, + ); + } + + // History — most recent first + pastSection.hidden = past.length === 0; + if (past.length > 0) { + const reversed = [...past].reverse(); + litRender( + html` + ${reversed.map((item) => { + const track = findTrack(item.id, tracks); + return keyed(item.id, html` +
  • +
    + ${trackTitle( + track, + item.id, + )} + ${trackArtist(track) + ? html` + ${trackArtist( + track, + )} + ` + : null} +
    +
  • + `); + })} + `, + pastList, + ); + } +}); + +//////////////////////////////////////////// +// ACTIONS +//////////////////////////////////////////// + +document.querySelector("#clear-all-btn")?.addEventListener("click", () => { + queue.clear({ keepManual: false }); +}); + +document.querySelector("#clear-auto-btn")?.addEventListener("click", () => { + queue.clear({ keepManual: true }); +}); + +//////////////////////////////////////////// +// 🚀 +//////////////////////////////////////////// + +foundation.ready(); diff --git a/src/vendor/lit-html/directives/async-append.js b/src/vendor/lit-html/directives/async-append.js new file mode 100644 index 00000000..c8381209 --- /dev/null +++ b/src/vendor/lit-html/directives/async-append.js @@ -0,0 +1 @@ +export * from "lit-html/directives/async-append.js"; diff --git a/src/vendor/lit-html/directives/async-replace.js b/src/vendor/lit-html/directives/async-replace.js new file mode 100644 index 00000000..df5023c4 --- /dev/null +++ b/src/vendor/lit-html/directives/async-replace.js @@ -0,0 +1 @@ +export * from "lit-html/directives/async-replace.js"; diff --git a/src/vendor/lit-html/directives/cache.js b/src/vendor/lit-html/directives/cache.js new file mode 100644 index 00000000..e9ecf214 --- /dev/null +++ b/src/vendor/lit-html/directives/cache.js @@ -0,0 +1 @@ +export * from "lit-html/directives/cache.js"; diff --git a/src/vendor/lit-html/directives/choose.js b/src/vendor/lit-html/directives/choose.js new file mode 100644 index 00000000..851c5593 --- /dev/null +++ b/src/vendor/lit-html/directives/choose.js @@ -0,0 +1 @@ +export * from "lit-html/directives/choose.js"; diff --git a/src/vendor/lit-html/directives/class-map.js b/src/vendor/lit-html/directives/class-map.js new file mode 100644 index 00000000..cdf94496 --- /dev/null +++ b/src/vendor/lit-html/directives/class-map.js @@ -0,0 +1 @@ +export * from "lit-html/directives/class-map.js"; diff --git a/src/vendor/lit-html/directives/guard.js b/src/vendor/lit-html/directives/guard.js new file mode 100644 index 00000000..c76f8339 --- /dev/null +++ b/src/vendor/lit-html/directives/guard.js @@ -0,0 +1 @@ +export * from "lit-html/directives/guard.js"; diff --git a/src/vendor/lit-html/directives/if-defined.js b/src/vendor/lit-html/directives/if-defined.js new file mode 100644 index 00000000..573c79d1 --- /dev/null +++ b/src/vendor/lit-html/directives/if-defined.js @@ -0,0 +1 @@ +export * from "lit-html/directives/if-defined.js"; diff --git a/src/vendor/lit-html/directives/join.js b/src/vendor/lit-html/directives/join.js new file mode 100644 index 00000000..14576649 --- /dev/null +++ b/src/vendor/lit-html/directives/join.js @@ -0,0 +1 @@ +export * from "lit-html/directives/join.js"; diff --git a/src/vendor/lit-html/directives/keyed.js b/src/vendor/lit-html/directives/keyed.js new file mode 100644 index 00000000..a12bd32e --- /dev/null +++ b/src/vendor/lit-html/directives/keyed.js @@ -0,0 +1 @@ +export * from "lit-html/directives/keyed.js"; diff --git a/src/vendor/lit-html/directives/live.js b/src/vendor/lit-html/directives/live.js new file mode 100644 index 00000000..a9f7a00a --- /dev/null +++ b/src/vendor/lit-html/directives/live.js @@ -0,0 +1 @@ +export * from "lit-html/directives/live.js"; diff --git a/src/vendor/lit-html/directives/map.js b/src/vendor/lit-html/directives/map.js new file mode 100644 index 00000000..741a827b --- /dev/null +++ b/src/vendor/lit-html/directives/map.js @@ -0,0 +1 @@ +export * from "lit-html/directives/map.js"; diff --git a/src/vendor/lit-html/directives/range.js b/src/vendor/lit-html/directives/range.js new file mode 100644 index 00000000..1231a3f4 --- /dev/null +++ b/src/vendor/lit-html/directives/range.js @@ -0,0 +1 @@ +export * from "lit-html/directives/range.js"; diff --git a/src/vendor/lit-html/directives/ref.js b/src/vendor/lit-html/directives/ref.js new file mode 100644 index 00000000..010c5c03 --- /dev/null +++ b/src/vendor/lit-html/directives/ref.js @@ -0,0 +1 @@ +export * from "lit-html/directives/ref.js"; diff --git a/src/vendor/lit-html/directives/repeat.js b/src/vendor/lit-html/directives/repeat.js new file mode 100644 index 00000000..f9444dd6 --- /dev/null +++ b/src/vendor/lit-html/directives/repeat.js @@ -0,0 +1 @@ +export * from "lit-html/directives/repeat.js"; diff --git a/src/vendor/lit-html/directives/style-map.js b/src/vendor/lit-html/directives/style-map.js new file mode 100644 index 00000000..f9577281 --- /dev/null +++ b/src/vendor/lit-html/directives/style-map.js @@ -0,0 +1 @@ +export * from "lit-html/directives/style-map.js"; diff --git a/src/vendor/lit-html/directives/template-content.js b/src/vendor/lit-html/directives/template-content.js new file mode 100644 index 00000000..3a0e33c6 --- /dev/null +++ b/src/vendor/lit-html/directives/template-content.js @@ -0,0 +1 @@ +export * from "lit-html/directives/template-content.js"; diff --git a/src/vendor/lit-html/directives/unsafe-html.js b/src/vendor/lit-html/directives/unsafe-html.js new file mode 100644 index 00000000..e76e4b05 --- /dev/null +++ b/src/vendor/lit-html/directives/unsafe-html.js @@ -0,0 +1 @@ +export * from "lit-html/directives/unsafe-html.js"; diff --git a/src/vendor/lit-html/directives/unsafe-mathml.js b/src/vendor/lit-html/directives/unsafe-mathml.js new file mode 100644 index 00000000..20176fec --- /dev/null +++ b/src/vendor/lit-html/directives/unsafe-mathml.js @@ -0,0 +1 @@ +export * from "lit-html/directives/unsafe-mathml.js"; diff --git a/src/vendor/lit-html/directives/unsafe-svg.js b/src/vendor/lit-html/directives/unsafe-svg.js new file mode 100644 index 00000000..839a9fdf --- /dev/null +++ b/src/vendor/lit-html/directives/unsafe-svg.js @@ -0,0 +1 @@ +export * from "lit-html/directives/unsafe-svg.js"; diff --git a/src/vendor/lit-html/directives/until.js b/src/vendor/lit-html/directives/until.js new file mode 100644 index 00000000..f870b90b --- /dev/null +++ b/src/vendor/lit-html/directives/until.js @@ -0,0 +1 @@ +export * from "lit-html/directives/until.js"; diff --git a/src/vendor/lit-html/directives/when.js b/src/vendor/lit-html/directives/when.js new file mode 100644 index 00000000..1b7d4e4a --- /dev/null +++ b/src/vendor/lit-html/directives/when.js @@ -0,0 +1 @@ +export * from "lit-html/directives/when.js"; -- 2.51.2