From 838f308ba902ebe756ec1010f49f5abb7b017dab Mon Sep 17 00:00:00 2001 From: gator Date: Mon, 7 Sep 2026 21:16:16 +0000 Subject: [PATCH] Wire the collateral step Step one creates the project on hold and reveals the collateral panel; step two writes the script. A held project can be reopened with ?id= so a half-finished setup is not lost. --- create.html | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 159 insertions(+), 6 deletions(-) diff --git a/create.html b/create.html index ea0bbfc..d339609 100644 --- a/create.html +++ b/create.html @@ -195,6 +195,155 @@ var petSel = document.getElementById("pet"); }).join(""); })(); +// The project id, once step one has created it. Everything in the collateral +// step hangs off this. +var PID = new URLSearchParams(location.search).get("id") || ""; +var COL = [], VID = null; + +function showCollateral() { + document.getElementById("colcard").style.display = ""; + document.getElementById("go").disabled = true; + document.getElementById("go").textContent = "Saved"; + document.getElementById("colcard").scrollIntoView({ behavior: "smooth", block: "start" }); + loadCol(); +} + +function colHtml(c) { + var body = c.kind === "video" + ? '' + + '
footage · ' + (c.seconds || 0) + 's
' + : '' + esc(c.description || '; + return '
' + body + + '
' + esc(c.description || "") + '
' + + '
'; +} + +async function loadCol() { + if (!PID) return; + var r = await api("list_project_images", { email: email, project_id: PID }); + COL = (r.ok && r.images) || []; + var el = document.getElementById("pics"); + el.innerHTML = COL.map(colHtml).join(""); + [].forEach.call(el.querySelectorAll("[data-rm]"), function (b) { + b.onclick = async function () { + var d = await api("delete_project_image", { email: email, image_id: b.getAttribute("data-rm") }); + if (!d.ok) { showMsg("msg", d.error || "Could not remove that."); return; } + loadCol(); + }; + }); +} + +async function addPicture(file) { + hideMsg("msg"); + if (!file || !/^image\//.test(file.type)) { showMsg("msg", "That is not an image."); return; } + var desc = prompt("What is in this picture? The writer uses this to decide where it belongs."); + if (!desc || desc.trim().length < 3) { showMsg("msg", "It needs a description to be usable."); return; } + var drop = document.getElementById("drop"); + drop.textContent = "Uploading…"; + var img; + try { img = await resizeImage(file, 1600, 0.85); } + catch (err) { drop.textContent = "Paste a picture, drop one here, or click to choose"; showMsg("msg", err.message || "Could not read that."); return; } + var r = await api("add_project_image", { email: email, project_id: PID, + description: desc.trim(), data_b64: img.b64 }); + drop.textContent = "Paste a picture, drop one here, or click to choose"; + if (!r.ok) { showMsg("msg", r.error || "That did not upload."); return; } + loadCol(); +} + +(function wireCollateral() { + var drop = document.getElementById("drop"), picker = document.getElementById("picker"); + drop.onclick = function () { picker.click(); }; + picker.onchange = function () { [].forEach.call(picker.files || [], addPicture); picker.value = ""; }; + drop.addEventListener("dragover", function (e) { e.preventDefault(); drop.classList.add("hot"); }); + drop.addEventListener("dragleave", function () { drop.classList.remove("hot"); }); + drop.addEventListener("drop", function (e) { + e.preventDefault(); drop.classList.remove("hot"); + [].forEach.call((e.dataTransfer && e.dataTransfer.files) || [], addPicture); + }); + document.addEventListener("paste", function (e) { + if (!PID) return; + var items = (e.clipboardData && e.clipboardData.items) || []; + for (var i = 0; i < items.length; i++) { + if (items[i].type && items[i].type.indexOf("image") === 0) { + var f = items[i].getAsFile(); + if (f) { e.preventDefault(); addPicture(f); } + return; + } + } + }); + + var v = document.getElementById("vprev"), + vin = document.getElementById("vin"), vout = document.getElementById("vout"); + + function labels() { + document.getElementById("inlab").textContent = (+vin.value).toFixed(1) + "s"; + document.getElementById("outlab").textContent = (+vout.value).toFixed(1) + "s"; + var len = (+vout.value) - (+vin.value); + document.getElementById("vlen").textContent = len > 0 + ? "That is " + len.toFixed(1) + " seconds of footage." + : "The end has to come after the start."; + } + + document.getElementById("vload").onclick = function () { + hideMsg("msg"); + var u = document.getElementById("vurl").value.trim(); + if (!/^https?:\/\//.test(u)) { showMsg("msg", "Paste a link starting http."); return; } + v.src = u; + v.onloadedmetadata = function () { + // Duration is readable cross-origin even when the pixels are not, which + // is all the scrubber needs. + var d = isFinite(v.duration) ? v.duration : 0; + if (!d) { showMsg("msg", "That link did not load as a video file."); return; } + vin.max = vout.max = d.toFixed(1); + vin.value = 0; vout.value = Math.min(5, d).toFixed(1); + labels(); + document.getElementById("vedit").style.display = ""; + VID = u; + }; + v.onerror = function () { showMsg("msg", "That link did not load as a video file. It has to be the file itself, not a page."); }; + }; + + vin.oninput = function () { labels(); v.currentTime = +vin.value; }; + vout.oninput = labels; + + document.getElementById("vplay").onclick = function () { + v.currentTime = +vin.value; + v.play(); + var stop = setInterval(function () { + if (v.currentTime >= +vout.value) { v.pause(); clearInterval(stop); } + }, 80); + }; + + document.getElementById("vadd").onclick = async function () { + hideMsg("msg"); + var d = document.getElementById("vdesc").value.trim(); + if (d.length < 3) { showMsg("msg", "Say what is in the clip."); return; } + var r = await api("add_project_video", { + email: email, project_id: PID, source_url: VID, description: d, + in_seconds: +vin.value, out_seconds: +vout.value + }); + if (!r.ok) { showMsg("msg", r.error || "Could not use that clip."); return; } + document.getElementById("vedit").style.display = "none"; + document.getElementById("vurl").value = ""; + document.getElementById("vdesc").value = ""; + loadCol(); + }; + + document.getElementById("write").onclick = async function () { + var b = this; + hideMsg("msg"); + b.disabled = true; b.textContent = "Writing…"; + var r = await api("write_script", { email: email, project_id: PID }); + if (!r.ok) { b.disabled = false; b.textContent = "Write the script"; showMsg("msg", r.error || "That did not start."); return; } + location.href = "/script.html?id=" + encodeURIComponent(PID); + }; + + if (PID) showCollateral(); +})(); + document.getElementById("form").addEventListener("submit", async function (e) { e.preventDefault(); hideMsg("msg"); @@ -208,18 +357,22 @@ document.getElementById("form").addEventListener("submit", async function (e) { if (!url) { showMsg("msg", "Paste a link to whatever you want them to react to."); return; } if (notes.length < 10) { showMsg("msg", "Tell the director what you think, in a sentence or two."); return; } - btn.disabled = true; btn.textContent = "Writing…"; + btn.disabled = true; btn.textContent = "Saving…"; + // Held, so nothing is written until the collateral is attached. var r = await api("create_project", { email: email, pet_id: petSel.value, pet_name: opt ? opt.getAttribute("data-name") : "", source_url: url, owner_notes: notes, scene: scene, format: document.getElementById("format").value, - stance: stance, segments: segments + stance: stance, segments: segments, hold: true }); - btn.disabled = false; btn.textContent = "Write the script"; - if (!r.ok) { showMsg("msg", r.error || "That did not go through. Please try again."); return; } - showMsg("msg", "Writing it now…", "ok"); - setTimeout(function () { location.href = "/script.html?id=" + encodeURIComponent(r.project.id); }, 500); + if (!r.ok) { + btn.disabled = false; btn.textContent = "Next · add pictures and footage"; + showMsg("msg", r.error || "That did not go through. Please try again."); return; + } + PID = r.project.id; + history.replaceState(null, "", "/create.html?id=" + encodeURIComponent(PID)); + showCollateral(); });