mirror of
https://github.com/calesthio/OpenMontage.git
synced 2026-08-23 08:37:31 +08:00
- vanilla ESM frontend on the mockup design system (no build step): library contact-sheet with mini rails + live badges; project board with slate header, cost meter, clickable stage rail, stage drawers (artifact view, review findings, gate-skipped chip, versions), screenplay card + full-script modal, decisions + activity rails, storyboard filmstrip (duration-width cards, shimmer/spec/missing/text-card states, takes, narration + waveform playback), render player with versions, degraded found-media view - /thumb endpoint: cached downscaled JPEGs (Pillow) + ffmpeg poster-frame extraction for videos — the library was loading 73 full-res PNGs - library summaries cached, invalidated by the watcher - asset path resolution tolerates project-relative, repo-relative and absolute manifest paths (real-world variance found in why-do-we-dream) - ?static=1 disables SSE (screenshots/static export) - verified in browser against signal-from-tomorrow, why-do-we-dream, done-beats-perfect and the 73-project library
64 lines
2.4 KiB
JavaScript
64 lines
2.4 KiB
JavaScript
import { el, fmtAgo, getJSON, subscribe, thumbURL } from "/ui/lib.js";
|
|
|
|
const grid = document.getElementById("grid");
|
|
|
|
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)),
|
|
);
|
|
|
|
return el("a", { class: `lib-card${p.live ? " live-card" : ""}`, href: `/p/${p.project_id}`, 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));
|
|
}
|