Files
OpenMontage/backlot/ui/library.js
2026-07-10 23:39:33 -07:00

92 lines
3.5 KiB
JavaScript

import { el, fmtAgo, getJSON, subscribe, thumbURL } from "/ui/lib.js";
const grid = document.getElementById("grid");
const THEME_KEY = "backlot.theme";
let currentTheme = localStorage.getItem(THEME_KEY) === "light" ? "light" : "dark";
function applyTheme(theme) {
currentTheme = theme === "light" ? "light" : "dark";
document.documentElement.dataset.theme = currentTheme;
localStorage.setItem(THEME_KEY, currentTheme);
}
function renderThemeToggle() {
const next = currentTheme === "light" ? "dark" : "light";
return el("button", {
class: "theme-toggle",
type: "button",
title: `Switch to ${next} theme`,
"aria-label": `Switch to ${next} theme`,
"aria-pressed": currentTheme === "light" ? "true" : "false",
onclick: () => {
applyTheme(next);
const replacement = renderThemeToggle();
document.querySelector(".theme-toggle").replaceWith(replacement);
},
}, el("span", { class: "theme-toggle-icon", "aria-hidden": "true" }, currentTheme === "light" ? "☾" : "☀"));
}
applyTheme(currentTheme);
document.getElementById("liveBadge").before(renderThemeToggle());
function miniRail(states) {
const rail = el("div", { class: "mini-rail" });
for (const s of states) {
const cls = s.status === "completed" ? "d"
: s.status === "in_progress" ? "a"
: s.status === "awaiting_human" ? "w" : "";
rail.append(el("i", { class: cls, title: `${s.name}: ${s.status}` }));
}
return rail;
}
function card(p) {
const poster = el("div", { class: "lib-poster" });
if (p.poster) {
poster.append(el("img", { src: thumbURL(p.project_id, p.poster, 640), loading: "lazy", alt: "" }));
} else {
poster.append(el("span", { class: "lp-txt" }, "NO MEDIA YET"));
}
if (p.live && p.active_stage) {
poster.append(el("span", { class: "lp-live" },
el("span", { class: "dot" }),
p.awaiting_human ? "◈ AWAITING YOU" : `LIVE · ${p.active_stage.toUpperCase()}`));
} else if (p.awaiting_human) {
poster.append(el("span", { class: "lp-live" }, "◈ AWAITING YOU"));
}
const meta = el("div", { class: "lb-meta" },
el("span", { class: "chip" }, p.pipeline_type || "unknown"),
p.scene_count ? el("span", { class: "chip" }, `${p.scene_count} scenes`) : null,
p.render_count ? el("span", { class: "chip" }, `${p.render_count} renders`) : null,
el("span", { class: "when" }, fmtAgo(p.last_activity)),
);
const staticSuffix = new URLSearchParams(location.search).has("static") ? "?static=1" : "";
return el("a", { class: `lib-card${p.live ? " live-card" : ""}`, href: `/p/${p.project_id}${staticSuffix}`, style: "text-decoration:none;color:inherit" },
poster,
el("div", { class: "lib-body" },
el("h3", {}, (p.title || p.project_id).toUpperCase()),
meta,
p.stage_states.length ? miniRail(p.stage_states) : null,
),
);
}
async function render() {
const projects = await getJSON("/api/projects");
document.getElementById("count").textContent = `${projects.length} projects`;
const liveCount = projects.filter((p) => p.live).length;
const badge = document.getElementById("liveBadge");
badge.classList.toggle("idle", liveCount === 0);
document.getElementById("liveText").textContent = liveCount ? `${liveCount} LIVE` : "IDLE";
grid.innerHTML = "";
document.getElementById("empty").style.display = projects.length ? "none" : "block";
for (const p of projects) grid.append(card(p));
}
render().catch(console.error);
if (!new URLSearchParams(location.search).has("static")) {
subscribe("/api/library/events", () => render().catch(console.error));
}