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; }
|
.x:hover { color:#ff9a8a; border-color:#c0392b; }
|
||||||
.hint { font-size:11px; color:var(--muted); margin-top:8px; }
|
.hint { font-size:11px; color:var(--muted); margin-top:8px; }
|
||||||
code { background:#0d1014; padding:1px 5px; border-radius:4px; color:#cfe3ff; }
|
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 { 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:hover { border-color:var(--muted); }
|
||||||
.ex-item.active { border-color:#2e7d32; box-shadow:inset 3px 0 0 #2e7d32; } /* loaded / playing */
|
.ex-item.active { border-color:#2e7d32; box-shadow:inset 3px 0 0 #2e7d32; } /* loaded / playing */
|
||||||
|
|
@ -359,6 +373,14 @@
|
||||||
</aside>
|
</aside>
|
||||||
</div><!-- /#app -->
|
</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 id="shortcutsOverlay" class="overlay" hidden>
|
||||||
<div class="overlay-box">
|
<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>
|
<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.textContent = s;
|
||||||
ctxDisplay.classList.toggle("muted-cue", muted || !!pendingSwitch);
|
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
|
UI WIRING
|
||||||
|
|
@ -1222,6 +1261,14 @@ $("shareSetlistBtn").addEventListener("click", () => { $("trayMenu").hidden = tr
|
||||||
$("shareClose").addEventListener("click", () => $("shareOverlay").hidden = true);
|
$("shareClose").addEventListener("click", () => $("shareOverlay").hidden = true);
|
||||||
$("shareOverlay").addEventListener("click", (e) => { if (e.target.id === "shareOverlay") $("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(); } });
|
$("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"));
|
$("shareOpen").addEventListener("click", () => window.open($("shareUrl").value, "_blank"));
|
||||||
window.addEventListener("keydown", (e) => {
|
window.addEventListener("keydown", (e) => {
|
||||||
const t = e.target, tag = t ? t.tagName : "", type = (t && t.type ? String(t.type) : "").toLowerCase();
|
const t = e.target, tag = t ? t.tagName : "", type = (t && t.type ? String(t.type) : "").toLowerCase();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue