From 15e384bc1d97c84df0545555bf4c298702c9eacb Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 9 Nov 2026 19:08:51 +0100 Subject: feat: card headers, music art/seek, clearer build card --- static/modules/music.js | 63 +++++++++++++++++++++++++++++++++------------ static/modules/obs.js | 9 ++++--- static/modules/sbbuilder.js | 49 +++++++++++++++++++++++------------ 3 files changed, 84 insertions(+), 37 deletions(-) (limited to 'static/modules') diff --git a/static/modules/music.js b/static/modules/music.js index de9df7f..e4388f7 100644 --- a/static/modules/music.js +++ b/static/modules/music.js @@ -1,9 +1,14 @@ -async function post(action) { +function clock(s) { + s = Math.max(0, Math.floor(s || 0)); + return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`; +} + +async function post(action, body = {}) { try { await fetch(`/api/music/${action}`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: "{}", + body: JSON.stringify(body), }); } catch { // the next poll shows the real state @@ -22,27 +27,51 @@ export default { interval: 2, mount(el) { - const box = document.createElement("div"); - box.className = "music"; - box.innerHTML = ` -
—
-
-
`; - box.querySelector(".controls").append( - button("⏮", "prev"), button("⏯", "play"), button("⏭", "next")); - box.querySelector(".vol").append( + el.innerHTML = ` +
+
♪
+
+
Nothing playing
+
+
+
+
+
+ + 0:00 +
+
`; + el.querySelector(".controls").append( + button("⏮", "prev"), button("⏯", "play"), button("⏭", "next"), button("Vol−", "voldown"), button("Mute", "mute"), button("Vol+", "volup")); - el.append(box); + const seek = el.querySelector(".seek input"); + seek.addEventListener("pointerdown", () => { el._dragging = true; }); + seek.addEventListener("pointerup", () => { el._dragging = false; }); + seek.addEventListener("change", () => post("seek", { position: Number(seek.value) })); }, update(st, el) { - const box = el.querySelector(".music"); - box.querySelector(".title").textContent = st.title || "Nothing playing"; - box.querySelector(".artist").textContent = st.artist || ""; - box.querySelector(".controls button:nth-child(2)").textContent = + if (st.art) { + let img = el.querySelector("img.art"); + if (!img) { + img = document.createElement("img"); + img.className = "art"; + img.alt = ""; + el.querySelector(".art").replaceWith(img); + } + if (img.getAttribute("src") !== st.art) img.src = st.art; + } + el.querySelector(".title").textContent = st.title || "Nothing playing"; + el.querySelector(".artist").textContent = st.artist || ""; + el.querySelector(".album").textContent = st.album || ""; + el.querySelector(".controls button:nth-child(2)").textContent = st.status === "Playing" ? "⏸" : "▶"; - const mute = box.querySelector(".vol button:nth-child(2)"); + const mute = el.querySelector(".controls button:nth-child(5)"); mute.classList.toggle("on", Boolean(st.volume?.muted)); mute.textContent = st.volume?.muted ? "Unmute" : "Mute"; + const seek = el.querySelector(".seek input"); + seek.max = Math.max(1, Math.floor(st.length || 0)); + if (!el._dragging) seek.value = Math.min(Number(seek.max), Math.floor(st.position || 0)); + el.querySelector(".time").textContent = `${clock(st.position)} / ${clock(st.length)}`; }, }; diff --git a/static/modules/obs.js b/static/modules/obs.js index 9c5c34c..7270f87 100644 --- a/static/modules/obs.js +++ b/static/modules/obs.js @@ -27,9 +27,9 @@ export default { wrap.className = "obs"; wrap.innerHTML = `
-
-
-
`; +
Scenes
+
Audio
+
Sources
`; el.append(wrap); }, @@ -39,6 +39,9 @@ export default { const audio = el.querySelector(".obs-audio"); const sources = el.querySelector(".obs-sources"); + el.querySelectorAll(".section-label").forEach((label) => { + label.style.display = state.connected ? "" : "none"; + }); if (!state.connected) { controls.textContent = state.error ? `OBS offline: ${state.error}` : "OBS offline"; scenes.textContent = audio.textContent = sources.textContent = ""; diff --git a/static/modules/sbbuilder.js b/static/modules/sbbuilder.js index 16745cb..56802a9 100644 --- a/static/modules/sbbuilder.js +++ b/static/modules/sbbuilder.js @@ -5,39 +5,54 @@ function chipClass(status) { return "muted"; } +function when(ts) { + if (!ts) return ""; + return new Date(ts * 1000).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); +} + +function summary(jobs) { + const running = jobs.filter((j) => ["running", "busy", "starting"].includes(j.status)).length; + const failed = jobs.filter((j) => ["failed", "lost", "killed"].includes(j.status)).length; + const parts = []; + if (running) parts.push(`${running} running`); + if (failed) parts.push(`${failed} failed`); + if (!parts.length) parts.push(jobs.length ? `${jobs.length} recent` : "idle"); + return parts.join(" · "); +} + export default { - title: "Builds", + title: "Build VM", interval: 5, mount(el) { - const box = document.createElement("div"); - box.className = "sbb"; - el.append(box); + el.innerHTML = `
`; }, update(state, el) { - const box = el.querySelector(".sbb"); - box.textContent = ""; + const sum = el.querySelector(".sbb-summary"); + const list = el.querySelector(".sbb-list"); if (state.offline) { - box.textContent = "build VM unreachable"; + sum.textContent = ""; + list.textContent = "build VM unreachable"; return; } const jobs = state.jobs ?? []; - if (!jobs.length) { - box.textContent = "No jobs"; - return; - } + sum.textContent = summary(jobs); + list.textContent = ""; for (const job of jobs.slice(0, 8)) { const row = document.createElement("div"); - row.className = "row"; - const id = document.createElement("span"); - id.className = "num id"; - id.textContent = job.id; + row.className = "row job"; + const kind = document.createElement("span"); + kind.className = "kind"; + kind.textContent = job.kind || "job"; + const time = document.createElement("span"); + time.className = "muted num"; + time.textContent = when(job.created); const status = document.createElement("span"); status.className = chipClass(job.status); status.textContent = job.status; - row.append(id, status); - box.append(row); + row.append(kind, time, status); + list.append(row); } }, }; -- cgit v1.2.3