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();