From 7706459daa8cd7cf5925abe6c85c08e8dc5c871e Mon Sep 17 00:00:00 2001 From: Ian Keane Date: Thu, 11 Jun 2026 12:30:11 -0400 Subject: [PATCH] Implement 'call' button --- app.js | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++- index.html | 11 +++++ style.css | 68 +++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 1 deletion(-) diff --git a/app.js b/app.js index 7d97353..d7df592 100644 --- a/app.js +++ b/app.js @@ -880,7 +880,131 @@ function openRefsModal(tuneId) { refsModal.showModal(); } -// ── Bulk Edit ───────────────────────────────────────────── +// ── Call mode ───────────────────────────────────────────────────── +const callModal = $('call-modal'); +let callStep = 0; +let callInstrumentId = null; +let callTuningId = null; + +function callableEntries() { + const result = []; + for (const tune of allTunes) + for (const entry of tune.instruments) + if (entry.callable) result.push({ tune, entry }); + return result; +} + +function callGetInstruments() { + const map = new Map(); + callableEntries().forEach(({ entry }) => { + if (!map.has(entry.instrument_id)) map.set(entry.instrument_id, entry.instrument_name); + }); + return [...map.entries()].map(([id, name]) => ({ id, name })) + .sort((a, b) => a.name.localeCompare(b.name)); +} + +function callGetTunings() { + const map = new Map(); + callableEntries() + .filter(({ entry }) => callInstrumentId === null || entry.instrument_id === callInstrumentId) + .forEach(({ entry }) => { + if (entry.tuning_id != null && !map.has(entry.tuning_id)) + map.set(entry.tuning_id, entry.tuning); + }); + return [...map.entries()].map(([id, name]) => ({ id, name })) + .sort((a, b) => a.name.localeCompare(b.name)); +} + +function callGetKeys() { + const map = new Map(); + callableEntries() + .filter(({ entry }) => + (callInstrumentId === null || entry.instrument_id === callInstrumentId) && + (callTuningId === null || entry.tuning_id === callTuningId) + ) + .forEach(({ tune }) => { + if (tune.key) { + const k = tune.key + '|' + (tune.modal ? '1' : '0'); + if (!map.has(k)) map.set(k, { key: tune.key, modal: !!tune.modal }); + } + }); + return [...map.values()] + .sort((a, b) => a.key.localeCompare(b.key) || (a.modal ? 1 : -1)); +} + +function openCallModal() { + callStep = 0; + callInstrumentId = null; + callTuningId = null; + renderCallStep(); + callModal.showModal(); +} + +function renderCallStep() { + const items = callStep === 0 ? callGetInstruments() + : callStep === 1 ? callGetTunings() + : callGetKeys(); + + // Skip this screen if there's only one option + if (items.length === 1) { + callSelect(callStep === 2 ? items[0].key : items[0].id, + callStep === 2 ? items[0].modal : null); + return; + } + + $('call-title').textContent = ['Instrument', 'Tuning', 'Key'][callStep]; + $('call-back').hidden = callStep === 0; + const opts = $('call-options'); + opts.innerHTML = ''; + const allBtn = document.createElement('button'); + allBtn.className = 'call-option-btn call-all-btn'; + allBtn.textContent = 'All'; + allBtn.addEventListener('click', () => callSelect(null, null)); + opts.appendChild(allBtn); + items.forEach(item => { + const btn = document.createElement('button'); + btn.className = 'call-option-btn'; + btn.textContent = callStep === 2 + ? (item.modal ? item.key + ' modal' : item.key) + : item.name; + btn.addEventListener('click', () => + callSelect(callStep === 2 ? item.key : item.id, + callStep === 2 ? item.modal : null)); + opts.appendChild(btn); + }); +} + +function callSelect(value, modal) { + if (callStep === 0) { + callInstrumentId = value; + callStep = 1; + renderCallStep(); + } else if (callStep === 1) { + callTuningId = value; + callStep = 2; + renderCallStep(); + } else { + filters.callable = true; + filters.instrument = callInstrumentId !== null ? String(callInstrumentId) : null; + filters.tuning = callTuningId !== null ? String(callTuningId) : null; + filters.key = value; + filters.modal = value !== null ? modal : null; + updateFilterIndicators(); + renderTable(); + callModal.close(); + } +} + +$('btn-call').addEventListener('click', openCallModal); +$('call-close').addEventListener('click', () => callModal.close()); +$('call-back').addEventListener('click', () => { + callStep--; + if (callStep === 0) callInstrumentId = null; + else if (callStep === 1) callTuningId = null; + renderCallStep(); +}); + +// ── Bulk Edit ────────────────────────────────────────────── btnBulkEdit.addEventListener('click', e => { e.stopPropagation(); if (!bulkEditPopover.hidden) { bulkEditPopover.hidden = true; return; } diff --git a/index.html b/index.html index 8613c52..bd03bc5 100644 --- a/index.html +++ b/index.html @@ -14,6 +14,7 @@ + @@ -182,6 +183,16 @@ + + +
+ +

+ +
+
+
+ diff --git a/style.css b/style.css index f263563..8b1fdd9 100644 --- a/style.css +++ b/style.css @@ -489,6 +489,74 @@ fieldset.active .fs-body { display: contents; } #bulk-edit-popover button:hover { background: rgba(255,255,255,.08); opacity: 1; } #btn-bulk-edit { background: transparent; border: 1px solid var(--border); color: var(--muted); } +#btn-call { background: #1b4332; color: #74c69d; border: 1px solid #2d6a4f; font-weight: 600; } + +/* ── Call modal ───────────────────────────────────────────── */ +#call-modal { + position: fixed; + inset: 0; + width: 100%; + max-width: 100%; + height: 100%; + max-height: 100%; + border-radius: 0; + border: none; + margin: 0; + padding: 0; + overflow: hidden; +} +#call-modal[open] { + display: flex; + flex-direction: column; +} + +#call-header { + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem 1.25rem; + border-bottom: 2px solid var(--border); + flex-shrink: 0; +} + +#call-title { flex: 1; margin: 0; font-size: 1.5rem; } + +#call-back, +#call-close { + background: transparent; + border: 1px solid var(--border); + color: var(--muted); + font-size: 1rem; + padding: .45rem .85rem; +} + +#call-options { + flex: 1; + overflow-y: auto; + padding: 1rem 1.25rem; +} + +.call-option-btn { + display: block; + width: 100%; + padding: 1.1rem 1.25rem; + font-size: 1.15rem; + text-align: left; + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text); + margin-bottom: .5rem; + cursor: pointer; +} +.call-option-btn:hover, +.call-option-btn:active { background: rgba(255,255,255,.08); opacity: 1; } + +.call-all-btn { + background: var(--accent2); + margin-bottom: .85rem; + font-weight: 600; +} /* ── Bulk refs modal ──────────────────────────────────────── */ #bulk-refs-modal { max-width: 760px; }