diff --git a/trailer.html b/trailer.html index 5d230cb..bcff926 100644 --- a/trailer.html +++ b/trailer.html @@ -11,8 +11,10 @@ .tab{border:1px solid var(--line);border-bottom:0;background:none;color:var(--muted); padding:10px 16px;border-radius:10px 10px 0 0;cursor:pointer;font:inherit;font-size:14px} .tab.on{color:var(--ink);border-color:var(--line);background:var(--panel)} + .tab.lead{color:var(--accent)} .tab .sc{color:var(--accent2);margin-left:7px;font-weight:600} .tab .st{color:var(--muted);margin-left:7px;font-size:12px} + .tab .crown{margin-left:6px} .head{display:flex;gap:18px;flex-wrap:wrap;align-items:baseline;margin-bottom:6px} .head h2{margin:0;font-size:22px} .tag{color:var(--muted);font-style:italic} @@ -59,6 +61,15 @@ border-radius:999px;padding:0 6px} .vobadge{color:var(--muted);font-size:11px;border:1px solid var(--line); border-radius:999px;padding:0 6px} + .revs{margin:0 0 22px} + .rev{border:1px solid var(--line);border-radius:10px;padding:12px 14px;margin-bottom:10px} + .rev h4{margin:0 0 6px;font-size:14px} + .rev h4 .n{color:var(--accent2);font-weight:600;margin-left:8px} + .rev .v{color:var(--muted);font-size:13px;margin:0 0 8px} + .rev ul{margin:4px 0 8px;padding-left:18px;font-size:13px} + .rev .er{color:#e0685f} + .rev .kp{color:var(--accent2)} + .rev .sh{color:var(--muted);font-variant-numeric:tabular-nums}
@@ -80,44 +91,59 @@ if (!requireSession()) return; navBar("trailer"); - var RUN = null, DRAFTS = [], CUR = null, PKG = {}, NOTES = {}, timer = null; + var RUN = null, DRAFTS = [], CUR = null, PKG = {}, NOTES = {}, REVIEWS = []; + var timer = null; var runId = new URLSearchParams(location.search).get("run") || ""; var audio = new Audio(); if (!runId) { showMsg("msg", "No run was named in the link."); return; } - // A draft is busy while it is being written or drawn. Both are minutes long - // and both report progress on the row, so one poll covers them. - function busyState(d) { + // A draft is busy while it is being written or drawn; a run is busy while it + // is reviewing or revising. Both are minutes long and report on the row. + function busyDraft(d) { var s = String(d.draw_state || ""); return d.state === "writing" || (s && !/^(drawn|failed)/.test(s)); } + function busyRun() { + return RUN && /^(writing|reviewing|revising)$/.test(String(RUN.stage || "")); + } async function load(repaint) { var r = await api("run_status", { run_id: runId }); if (!r.ok) { showMsg("msg", r.error || "Could not load that run."); return; } - var wasBusy = DRAFTS.length && DRAFTS.some(busyState); + var wasBusy = (DRAFTS.length && DRAFTS.some(busyDraft)) || busyRun(); RUN = r.run; DRAFTS = r.drafts || []; document.getElementById("title").textContent = RUN.book_title || "Trailers"; document.getElementById("sub").textContent = sub(); - if (!CUR && DRAFTS.length) CUR = DRAFTS[0].id; + if (!CUR && DRAFTS.length) CUR = leadDraftId() || DRAFTS[0].id; paintTabs(); - var busy = DRAFTS.some(busyState); + var rv = await api("list_reviews", { run_id: runId }); + REVIEWS = (rv && rv.reviews) || []; + + var busy = DRAFTS.some(busyDraft) || busyRun(); if (busy && !timer) timer = setInterval(load, 5000); if (!busy && timer) { clearInterval(timer); timer = null; } var me = DRAFTS.filter(function (d) { return d.id === CUR; })[0]; if (me && me.state === "writing") { paintWriting(); return; } - // Repaint when asked, on first paint, or when work has just finished. if (repaint !== false && (wasBusy || !document.querySelector(".shot"))) openTab(CUR); else paintBar(); } + function leadDraftId() { + var best = null; + DRAFTS.forEach(function (d) { + if (d.writer === RUN.lead_writer + && (!best || Number(d.iteration) > Number(best.iteration))) best = d; + }); + return best && best.id; + } + function sub() { var bits = []; if (RUN.stage) bits.push(RUN.stage); - bits.push(DRAFTS.length + " writer" + (DRAFTS.length === 1 ? "" : "s")); + bits.push(DRAFTS.length + " script" + (DRAFTS.length === 1 ? "" : "s")); var spent = DRAFTS.reduce(function (a, d) { return a + Number(d.write_usd || 0) + Number(d.draw_usd || 0); }, 0); if (spent) bits.push("$" + spent.toFixed(2) + " so far"); @@ -133,16 +159,19 @@ : (s && !/^(drawn|failed)/.test(s)) ? '' + esc(s) + '' : d.score ? '' + d.score + '' : ''; + var isLead = d.writer === RUN.lead_writer; return ''; }).join(""); } function paintWriting() { document.getElementById("pane").innerHTML = - 'Each writer is reading the whole book and writing its own ' - + 'trailer. This takes a few minutes.
'; + 'Working. Reading the whole book takes a few minutes.
'; } document.getElementById("tabs").addEventListener("click", function (e) { @@ -160,7 +189,9 @@ var pane = document.getElementById("pane"); if (meta.state === "failed") { pane.innerHTML = '' + esc(meta.label) + ' did not finish.
'
- + esc(meta.note || "No reason was recorded.") + '
' + esc(p.logline) + '
' : '') - + '' + esc(meta.label) + ' · ' + shots.length + ' shots · ' + + '
' + esc(meta.label) + + (Number(meta.iteration) > 1 ? ' · revision ' + meta.iteration : '') + + ' · ' + shots.length + ' shots · ' + Math.floor(secs / 60) + ':' + String(Math.round(secs % 60)).padStart(2, "0") + ' · ' + cast.length + ' characters' + (meta.write_usd ? ' · $' + Number(meta.write_usd).toFixed(2) + ' to write' : '') + + (meta.note ? ' · ' + esc(meta.note) : '') + '
'; var probs = ""; @@ -240,7 +330,8 @@ + 'continuity: ' + esc(s.continuity) + '
' : '') + lines + '' + ''; } @@ -282,17 +373,61 @@ // ------------------------------------------------------------ actions document.getElementById("pane").addEventListener("click", async function (e) { - var draw = e.target.closest ? e.target.closest("[data-draw]") : null; + var t = e.target.closest ? e.target : null; + if (!t) return; + + var lead = e.target.closest("[data-lead]"); + if (lead) { + lead.disabled = true; + var a = await api("set_lead", { run_id: runId, draft_id: lead.getAttribute("data-lead") }); + if (!a.ok) { lead.disabled = false; showMsg("msg", a.error || ""); return; } + hideMsg("msg"); await load(); openTab(CUR); return; + } + + var draw = e.target.closest("[data-draw]"); if (draw) { draw.disabled = true; var d = await api("draw_storyboard", { draft_id: draw.getAttribute("data-draw") }); - if (!d.ok) { draw.disabled = false; showMsg("msg", d.error || "That did not start."); return; } - hideMsg("msg"); - load(); + if (!d.ok) { draw.disabled = false; showMsg("msg", d.error || ""); return; } + hideMsg("msg"); load(); return; + } + + var rev = e.target.closest("[data-review]"); + if (rev) { + rev.disabled = true; + var v = await api("start_review", { run_id: runId }); + if (!v.ok) { rev.disabled = false; showMsg("msg", v.error || ""); return; } + hideMsg("msg"); load(); return; + } + + var rvs = e.target.closest("[data-revise]"); + if (rvs) { + rvs.disabled = true; + var w = await api("start_revision", { run_id: runId }); + if (!w.ok) { rvs.disabled = false; showMsg("msg", w.error || ""); return; } + hideMsg("msg"); load(); return; + } + + var film = e.target.closest("[data-film]"); + if (film) { + film.disabled = true; + var f = await api("film_draft", { draft_id: film.getAttribute("data-film") }); + if (!f.ok) { film.disabled = false; showMsg("msg", f.error || ""); return; } + showMsg("msg", "Filming " + f.shots + " shots, about $" + f.usd + + ". It appears in My videos as it goes.", "ok"); + setTimeout(function () { location.href = "/videos.html"; }, 2500); return; } - var btn = e.target.closest ? e.target.closest(".play") : null; + var retry = e.target.closest("[data-retry]"); + if (retry) { + retry.disabled = true; + var y = await api("retry_writer", { draft_id: retry.getAttribute("data-retry") }); + if (!y.ok) { retry.disabled = false; showMsg("msg", y.error || ""); return; } + hideMsg("msg"); load(); return; + } + + var btn = e.target.closest(".play"); if (!btn) return; var sid = btn.getAttribute("data-shot"); btn.disabled = true; btn.innerHTML = "…";