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.
This commit is contained in:
gator 2026-09-07 21:16:16 +00:00
parent 28b98f8326
commit 838f308ba9

View file

@ -195,6 +195,155 @@ var petSel = document.getElementById("pet");
}).join(""); }).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"
? '<video src="' + esc(c.url) + '#t=' + (c.in_seconds || 0) + '" preload="metadata" muted '
+ 'style="width:100%;aspect-ratio:1;object-fit:cover;border-radius:9px;border:1px solid var(--line);display:block;background:#000"></video>'
+ '<div style="font:11px/1.6 var(--mono);color:var(--accent2)">footage &middot; ' + (c.seconds || 0) + 's</div>'
: '<img src="' + esc(c.url) + '" alt="' + esc(c.description || "") + '" '
+ 'style="width:100%;aspect-ratio:1;object-fit:cover;border-radius:9px;border:1px solid var(--line);display:block">';
return '<div>' + body
+ '<div style="font-size:12.5px;color:var(--muted);margin-top:5px">' + esc(c.description || "") + '</div>'
+ '<button type="button" data-rm="' + esc(c.id) + '" style="background:none;border:0;color:var(--muted);'
+ 'cursor:pointer;text-decoration:underline;font:11px/1.8 var(--mono);padding:0">remove</button></div>';
}
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) { document.getElementById("form").addEventListener("submit", async function (e) {
e.preventDefault(); e.preventDefault();
hideMsg("msg"); 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 (!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; } 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", { var r = await api("create_project", {
email: email, pet_id: petSel.value, email: email, pet_id: petSel.value,
pet_name: opt ? opt.getAttribute("data-name") : "", pet_name: opt ? opt.getAttribute("data-name") : "",
source_url: url, owner_notes: notes, scene: scene, source_url: url, owner_notes: notes, scene: scene,
format: document.getElementById("format").value, 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) {
if (!r.ok) { showMsg("msg", r.error || "That did not go through. Please try again."); return; } btn.disabled = false; btn.textContent = "Next · add pictures and footage";
showMsg("msg", "Writing it now…", "ok"); showMsg("msg", r.error || "That did not go through. Please try again."); return;
setTimeout(function () { location.href = "/script.html?id=" + encodeURIComponent(r.project.id); }, 500); }
PID = r.project.id;
history.replaceState(null, "", "/create.html?id=" + encodeURIComponent(PID));
showCollateral();
}); });
</script> </script>
</body> </body>