aboutsummaryrefslogtreecommitdiffstats
path: root/static/modules
diff options
context:
space:
mode:
Diffstat (limited to 'static/modules')
-rw-r--r--static/modules/music.js63
-rw-r--r--static/modules/obs.js9
-rw-r--r--static/modules/sbbuilder.js49
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);
}
},
};