Editor: subtle live program-string bar (editable, copy, paste)
A faded one-line monospace bar under the app shows the current patch via currentPatch(); it brightens on hover/focus. Press Enter or blur to apply it (applyPatch) — paste a program string and it loads; the field renormalises to the canonical string and flashes on apply, tints red on parse failure. A focus guard keeps refreshUI from overwriting the field while you're typing in it. Hidden in embed mode. Refreshed from the existing updateCtx() path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1b5e430b2a
commit
884bbcb244
1 changed files with 48 additions and 1 deletions
49
index.html
49
index.html
|
|
@ -135,6 +135,20 @@
|
|||
.x:hover { color:#ff9a8a; border-color:#c0392b; }
|
||||
.hint { font-size:11px; color:var(--muted); margin-top:8px; }
|
||||
code { background:#0d1014; padding:1px 5px; border-radius:4px; color:#cfe3ff; }
|
||||
|
||||
/* subtle live program string: shows the current patch; editable + copy/paste.
|
||||
Faded by default so it doesn't compete with the editor; lights up on hover/focus. */
|
||||
.patchbar { display:flex; align-items:center; gap:9px; max-width:1100px; margin:16px auto 0;
|
||||
padding:6px 11px; border:1px solid var(--edge); border-radius:9px; background:var(--panel);
|
||||
opacity:.55; transition:opacity .15s, box-shadow .25s; }
|
||||
.patchbar:hover, .patchbar:focus-within { opacity:1; }
|
||||
.patchbar > label { flex:0 0 auto; font-size:10px; text-transform:uppercase; letter-spacing:.09em; color:var(--muted); }
|
||||
.patchbar input { flex:1; min-width:0; background:transparent; border:0; outline:none; color:var(--txt);
|
||||
font-family:"Courier New",monospace; font-size:12px; padding:3px 0; }
|
||||
.patchbar button { flex:0 0 auto; font-size:11px; padding:4px 10px; }
|
||||
.patchbar.applied { box-shadow:0 0 0 2px var(--cyan) inset; }
|
||||
.patchbar.err input { color:#ff8a7a; }
|
||||
[data-embed] .patchbar { display:none !important; }
|
||||
.ex-item { display:flex; gap:8px; align-items:center; padding:7px 9px; border:1px solid var(--edge); border-radius:8px; margin-bottom:6px; font-size:13px; background:var(--panel); cursor:pointer; }
|
||||
.ex-item:hover { border-color:var(--muted); }
|
||||
.ex-item.active { border-color:#2e7d32; box-shadow:inset 3px 0 0 #2e7d32; } /* loaded / playing */
|
||||
|
|
@ -359,6 +373,14 @@
|
|||
</aside>
|
||||
</div><!-- /#app -->
|
||||
|
||||
<!-- Subtle live program string for what's loaded: editable, copy & paste. -->
|
||||
<div class="patchbar" id="patchBar" title="Program string for what's loaded — edit it and press Enter (or paste one) to apply">
|
||||
<label for="patchField">program</label>
|
||||
<input id="patchField" spellcheck="false" autocomplete="off" autocapitalize="off"
|
||||
placeholder="v1;t120;kick:4;snare:4=.X.X;hatClosed:4/2" aria-label="program string (editable)">
|
||||
<button id="patchCopy" title="Copy the program string">Copy</button>
|
||||
</div>
|
||||
|
||||
<div id="shortcutsOverlay" class="overlay" hidden>
|
||||
<div class="overlay-box">
|
||||
<div class="tray-head"><h2 style="margin:0">Keyboard shortcuts</h2><button class="x" id="shortcutsClose" style="margin-left:0">✕</button></div>
|
||||
|
|
@ -1142,7 +1164,24 @@ function updateStatus() {
|
|||
ctxDisplay.textContent = s;
|
||||
ctxDisplay.classList.toggle("muted-cue", muted || !!pendingSwitch);
|
||||
}
|
||||
function updateCtx() { updateStatus(); }
|
||||
function updateCtx() { updateStatus(); refreshPatchField(); }
|
||||
|
||||
/* keep the subtle program-string bar in sync with what's loaded, unless the
|
||||
user is mid-edit in it (don't yank text out from under them). */
|
||||
function refreshPatchField() {
|
||||
const el = $("patchField"); if (!el || document.activeElement === el) return;
|
||||
try { el.value = currentPatch(); } catch (e) {}
|
||||
$("patchBar").classList.remove("err");
|
||||
}
|
||||
function commitPatchField() {
|
||||
const el = $("patchField"), bar = $("patchBar");
|
||||
try {
|
||||
applyPatch(el.value.trim()); // parse + apply (throws on garbage)
|
||||
bar.classList.remove("err");
|
||||
el.blur(); refreshPatchField(); // normalise to the canonical string
|
||||
bar.classList.add("applied"); setTimeout(() => bar.classList.remove("applied"), 450);
|
||||
} catch (e) { bar.classList.add("err"); }
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
UI WIRING
|
||||
|
|
@ -1222,6 +1261,14 @@ $("shareSetlistBtn").addEventListener("click", () => { $("trayMenu").hidden = tr
|
|||
$("shareClose").addEventListener("click", () => $("shareOverlay").hidden = true);
|
||||
$("shareOverlay").addEventListener("click", (e) => { if (e.target.id === "shareOverlay") $("shareOverlay").hidden = true; });
|
||||
$("shareCopy").addEventListener("click", async () => { try { await navigator.clipboard.writeText($("shareUrl").value); const b = $("shareCopy"); b.textContent = "Copied!"; setTimeout(() => b.textContent = "Copy link", 1200); } catch (e) { $("shareUrl").select(); } });
|
||||
|
||||
/* program-string bar: apply on Enter / blur, copy button, drop the error tint while typing */
|
||||
$("patchField").addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); commitPatchField(); } });
|
||||
$("patchField").addEventListener("change", commitPatchField);
|
||||
$("patchField").addEventListener("input", () => $("patchBar").classList.remove("err"));
|
||||
$("patchCopy").addEventListener("click", async () => {
|
||||
const el = $("patchField"); try { await navigator.clipboard.writeText(el.value); const b = $("patchCopy"); b.textContent = "Copied!"; setTimeout(() => b.textContent = "Copy", 1200); } catch (e) { el.select(); }
|
||||
});
|
||||
$("shareOpen").addEventListener("click", () => window.open($("shareUrl").value, "_blank"));
|
||||
window.addEventListener("keydown", (e) => {
|
||||
const t = e.target, tag = t ? t.tagName : "", type = (t && t.type ? String(t.type) : "").toLowerCase();
|
||||
|
|
|
|||
Loading…
Reference in a new issue