metronome/src/progbox.js
Me Here f1146e720a Phase C — per-page plain/info split + per-device program box
Each form-factor page is now plain by default: title + summary + the front view +
a program I/O box (shared src/progbox.* — paste a patch OR a base64 set-list code,
decoded + linted + loaded; copy; reflects the device state and posts it to an
embedding parent). All technical content — description, BOM, dimensions, top/side
views, embedding — hidden behind a "Show technical info" checkbox; ?info=1 opens it
checked. Teacher's top-edge view + dimensions are marked .tech so they hide too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 11:22:23 -05:00

60 lines
3.9 KiB
JavaScript

/* Per-device program I/O + "Show info" toggle — assembled into each form-factor page.
Uses the engine codec (patchToSetup / setupToPatch / codeToSetlist) for decode + lint.
Host page provides: window.currentProgramString() and window.loadProgramString(plain).
Exposes window.progRefresh() — call it after the device's program changes. When the
page is embedded it posts {type:'varasys-prog'} to the parent instead of touching the box. */
(function () {
var box = document.getElementById("dProg");
var msg = document.getElementById("dProgMsg");
var embedded = document.documentElement.dataset.embed === "1";
function setMsg(t, ok) { if (!msg) return; msg.textContent = t || ""; msg.classList.toggle("ok", !!ok && !!t); msg.classList.toggle("bad", !ok && !!t); }
function lint(text) {
text = (text || "").trim(); if (!text) return { ok: false, msg: "empty" };
var m = text.match(/[#?&](p|sl)=([^&\s]+)/), kind = null, payload = text;
if (m) { kind = m[1]; try { payload = decodeURIComponent(m[2]); } catch (e) { payload = m[2]; } }
var b64 = /^[A-Za-z0-9_-]{12,}$/.test(payload) && !/[;:]/.test(payload);
try {
if (kind === "sl" || (kind !== "p" && b64)) {
var sl = codeToSetlist(payload); if (!sl.items || !sl.items.length) throw new Error("set-list code has no items");
return { ok: true, plain: setupToPatch(sl.items[0]), msg: "decoded set list “" + sl.title + "” — item 1" };
}
var s = patchToSetup(payload); if (!s.lanes.length) throw new Error("no lanes — try e.g. kick:4");
return { ok: true, plain: setupToPatch(s), msg: s.lanes.length + " lane" + (s.lanes.length > 1 ? "s" : "") + " · " + s.bpm + " BPM" };
} catch (e) { return { ok: false, msg: "✗ " + e.message }; }
}
var editing = false;
// report the current program: to the parent when embedded, else into the box.
// No-op on pages without a program hook (e.g. panel-based Teacher/Player).
window.progRefresh = function () {
if (!window.currentProgramString) return;
var p = ""; try { p = window.currentProgramString(); } catch (e) {}
if (embedded) { try { parent.postMessage({ type: "varasys-prog", patch: p }, "*"); } catch (e) {} return; }
if (box && !editing) { box.value = p; setMsg("", true); }
};
if (box) {
box.addEventListener("focus", function () { editing = true; });
box.addEventListener("blur", function () { editing = false; });
box.addEventListener("input", function () { box.classList.remove("err"); });
box.addEventListener("keydown", function (e) { if (e.key === "Enter") { e.preventDefault(); doLoad(); } });
var loadBtn = document.getElementById("dProgLoad"); if (loadBtn) loadBtn.addEventListener("click", doLoad);
var copyBtn = document.getElementById("dProgCopy"); if (copyBtn) copyBtn.addEventListener("click", function () {
try { navigator.clipboard.writeText(box.value); copyBtn.textContent = "Copied!"; setTimeout(function () { copyBtn.textContent = "Copy"; }, 1200); } catch (e) { box.select(); }
});
}
function doLoad() {
var r = lint(box.value);
if (!r.ok) { box.classList.add("err"); setMsg(r.msg, false); return; }
box.classList.remove("err"); box.value = r.plain; setMsg("✓ " + r.msg, true);
try { if (window.loadProgramString) window.loadProgramString(r.plain); } catch (e) { setMsg("✗ " + e.message, false); }
}
// "Show info" toggle — reveals the technical section; ?info=1 opens it checked
var tog = document.getElementById("infoToggle"), tech = document.getElementById("techinfo");
if (tog) {
var extra = document.querySelectorAll(".tech"); // e.g. dimensioned views outside #techinfo
var apply = function (on) { if (tech) tech.hidden = !on; for (var i = 0; i < extra.length; i++) extra[i].hidden = !on; };
var open = /[?&]info=1/.test(location.search);
tog.checked = open; apply(open);
tog.addEventListener("change", function () { apply(tog.checked); });
}
window.progRefresh();
})();