diff options
| author | Danilo M. <danix@danix.xyz> | 2026-11-09 19:08:51 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-11-09 19:08:51 +0100 |
| commit | 15e384bc1d97c84df0545555bf4c298702c9eacb (patch) | |
| tree | f386400b398ebf55f9c3f2d060642e9b01255a95 /static/modules | |
| parent | 4632e6183d38bfd239f066d7e9698f3d118a3f77 (diff) | |
| download | deck-it-15e384bc1d97c84df0545555bf4c298702c9eacb.tar.gz deck-it-15e384bc1d97c84df0545555bf4c298702c9eacb.zip | |
feat: card headers, music art/seek, clearer build card
Diffstat (limited to 'static/modules')
| -rw-r--r-- | static/modules/music.js | 63 | ||||
| -rw-r--r-- | static/modules/obs.js | 9 | ||||
| -rw-r--r-- | static/modules/sbbuilder.js | 49 |
3 files changed, 84 insertions, 37 deletions
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 = ` - <div class="now"><b class="title">—</b><span class="artist muted"></span></div> - <div class="row controls"></div> - <div class="row vol"></div>`; - box.querySelector(".controls").append( - button("⏮", "prev"), button("⏯", "play"), button("⏭", "next")); - box.querySelector(".vol").append( + el.innerHTML = ` + <div class="top"> + <div class="art empty">♪</div> + <div class="meta"> + <div class="title">Nothing playing</div> + <div class="artist muted"></div> + <div class="album muted"></div> + </div> + </div> + <div class="seek"> + <input type="range" min="0" max="1" step="1" value="0" aria-label="Seek"> + <span class="time num">0:00</span> + </div> + <div class="controls"></div>`; + 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 = ` <div class="row obs-controls"></div> - <div class="obs-scenes"></div> - <div class="obs-audio"></div> - <div class="obs-sources"></div>`; + <div class="section-label">Scenes</div><div class="obs-scenes"></div> + <div class="section-label">Audio</div><div class="obs-audio"></div> + <div class="section-label">Sources</div><div class="obs-sources"></div>`; 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 = `<div class="sbb-summary muted"></div><div class="sbb-list"></div>`; }, 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); } }, }; |
