Implement 'call' button
This commit is contained in:
parent
19cd65387d
commit
7706459daa
3 changed files with 204 additions and 1 deletions
126
app.js
126
app.js
|
|
@ -880,7 +880,131 @@ function openRefsModal(tuneId) {
|
||||||
refsModal.showModal();
|
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 => {
|
btnBulkEdit.addEventListener('click', e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (!bulkEditPopover.hidden) { bulkEditPopover.hidden = true; return; }
|
if (!bulkEditPopover.hidden) { bulkEditPopover.hidden = true; return; }
|
||||||
|
|
|
||||||
11
index.html
11
index.html
|
|
@ -14,6 +14,7 @@
|
||||||
<input type="search" id="filter-search" placeholder="Search…" />
|
<input type="search" id="filter-search" placeholder="Search…" />
|
||||||
<button id="btn-clear-filters" hidden>✕ Clear filters</button>
|
<button id="btn-clear-filters" hidden>✕ Clear filters</button>
|
||||||
<span id="status"></span>
|
<span id="status"></span>
|
||||||
|
<button id="btn-call">Call</button>
|
||||||
<button id="btn-bulk-edit" hidden>⚡ Bulk Edit</button>
|
<button id="btn-bulk-edit" hidden>⚡ Bulk Edit</button>
|
||||||
<button id="btn-add-tune" hidden>+ Add tune</button>
|
<button id="btn-add-tune" hidden>+ Add tune</button>
|
||||||
<button id="btn-unlock">🔒 Unlock</button>
|
<button id="btn-unlock">🔒 Unlock</button>
|
||||||
|
|
@ -182,6 +183,16 @@
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
|
<!-- ── Call modal ─────────────────────────────────────────── -->
|
||||||
|
<dialog id="call-modal">
|
||||||
|
<div id="call-header">
|
||||||
|
<button id="call-back" hidden>← Back</button>
|
||||||
|
<h2 id="call-title"></h2>
|
||||||
|
<button id="call-close">✕</button>
|
||||||
|
</div>
|
||||||
|
<div id="call-options"></div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
<!-- Tooltip for truncated mobile text -->
|
<!-- Tooltip for truncated mobile text -->
|
||||||
<div id="mob-tooltip" hidden></div>
|
<div id="mob-tooltip" hidden></div>
|
||||||
|
|
||||||
|
|
|
||||||
68
style.css
68
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; }
|
#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-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 ──────────────────────────────────────── */
|
||||||
#bulk-refs-modal { max-width: 760px; }
|
#bulk-refs-modal { max-width: 760px; }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue