Slim dict for faster loading

This commit is contained in:
Ian Keane 2026-06-11 14:51:57 -04:00
parent 7f19e11885
commit 5812d3cc76

120
app.js
View file

@ -416,7 +416,7 @@ function renderTable() {
emptyMsg.hidden = rows.length > 0; emptyMsg.hidden = rows.length > 0;
rows.forEach(({ tune, entry }) => { rows.forEach(({ tune, entry }) => {
const hasNotes = (tune.notes?.length > 0) || (entry?.notes?.length > 0); const hasNotes = tune.has_notes ?? false;
// ── Mobile 2-line card data ────────────────────────────────── // ── Mobile 2-line card data ──────────────────────────────────
const nameRaw = tune.name ?? ''; const nameRaw = tune.name ?? '';
@ -431,7 +431,7 @@ function renderTable() {
const mobPills = entry ? pillStatus(entry.status, 'mob-pill') : ''; const mobPills = entry ? pillStatus(entry.status, 'mob-pill') : '';
const mobActions = [ const mobActions = [
hasNotes ? `<button class="action-btn btn-notes" data-tune-id="${tune.id}" data-entry-id="${entry?.id ?? ''}">📝</button>` : '', hasNotes ? `<button class="action-btn btn-notes" data-tune-id="${tune.id}" data-entry-id="${entry?.id ?? ''}">📝</button>` : '',
tune.references?.length > 0 ? `<button class="action-btn btn-show-refs" data-tune-id="${tune.id}">🔗</button>` : '', (tune.reference_count ?? 0) > 0 ? `<button class="action-btn btn-show-refs" data-tune-id="${tune.id}">🔗</button>` : '',
`<button class="action-btn btn-edit-row" data-tune-id="${tune.id}" data-entry-id="${entry?.id ?? ''}" ${unlocked ? '' : 'hidden'}>✏️</button>`, `<button class="action-btn btn-edit-row" data-tune-id="${tune.id}" data-entry-id="${entry?.id ?? ''}" ${unlocked ? '' : 'hidden'}>✏️</button>`,
].join(''); ].join('');
@ -446,7 +446,7 @@ function renderTable() {
<td>${hasNotes <td>${hasNotes
? `<button class="action-btn btn-notes" data-tune-id="${tune.id}" data-entry-id="${entry?.id ?? ''}">📝</button>` ? `<button class="action-btn btn-notes" data-tune-id="${tune.id}" data-entry-id="${entry?.id ?? ''}">📝</button>`
: ''}</td> : ''}</td>
<td>${tune.references?.length > 0 <td>${(tune.reference_count ?? 0) > 0
? `<button class="action-btn btn-show-refs" data-tune-id="${tune.id}" title="References">🔗</button>` ? `<button class="action-btn btn-show-refs" data-tune-id="${tune.id}" title="References">🔗</button>`
: ''}</td> : ''}</td>
<td><button class="action-btn btn-edit-row" <td><button class="action-btn btn-edit-row"
@ -512,22 +512,37 @@ function openAdd() {
tuneModal.showModal(); tuneModal.showModal();
} }
function openEdit(tuneId, entryId) { async function openEdit(tuneId, entryId) {
editingTuneId = tuneId; editingTuneId = tuneId;
editingEntryId = entryId; editingEntryId = entryId;
const tune = allTunes.find(t => t.id === tuneId); // Open modal immediately — name from slim data gives instant feedback
const entry = entryId ? tune?.instruments.find(e => e.id === entryId) : null; const slimTune = allTunes.find(t => t.id === tuneId);
$('modal-title').textContent = `Edit: ${slimTune?.name ?? ''}`;
$('modal-title').textContent = `Edit: ${tune?.name ?? ''}`;
tuneForm.reset(); tuneForm.reset();
refsList.innerHTML = '';
btnDeleteTune.hidden = false;
btnEditNotes.hidden = false;
tuneModal.showModal();
tuneForm.elements.name.value = tune?.name ?? ''; // Fetch full tune for references and confirmed field values
tuneForm.elements.key.value = tune?.key ?? ''; let fullTune;
tuneForm.elements.modal.checked = !!tune?.modal; try {
sourceInput.value = tune?.source?.name ?? ''; fullTune = await apiFetch(`/tunes/${tuneId}`);
} catch (e) {
setStatus(e.message, true);
tuneModal.close();
return;
}
const entry = entryId ? fullTune.instruments.find(e => e.id === entryId) : null;
$('modal-title').textContent = `Edit: ${fullTune.name ?? ''}`;
tuneForm.elements.name.value = fullTune.name ?? '';
tuneForm.elements.key.value = fullTune.key ?? '';
tuneForm.elements.modal.checked = !!fullTune.modal;
sourceInput.value = fullTune.source?.name ?? '';
// Instrument entry
populateInstrumentSelect(entry?.instrument_id ?? null); populateInstrumentSelect(entry?.instrument_id ?? null);
if (entry) { if (entry) {
tuneForm.elements.inst_tuning.value = entry.tuning ?? ''; tuneForm.elements.inst_tuning.value = entry.tuning ?? '';
@ -537,13 +552,7 @@ function openEdit(tuneId, entryId) {
tuneForm.elements.inst_difficulty.value = entry.difficulty ?? ''; tuneForm.elements.inst_difficulty.value = entry.difficulty ?? '';
} }
// References (fullTune.references ?? []).forEach(r => addRefRow(r.link, r.site?.name ?? '', r.id, r.site_id, r.musicians ?? []));
refsList.innerHTML = '';
(tune?.references ?? []).forEach(r => addRefRow(r.link, r.site?.name ?? '', r.id, r.site_id, r.musicians ?? []));
btnDeleteTune.hidden = false;
btnEditNotes.hidden = false;
tuneModal.showModal();
} }
// ── Musician chip picker (shared by ref rows + bulk form) ────── // ── Musician chip picker (shared by ref rows + bulk form) ──────
@ -739,22 +748,30 @@ async function openNotesModal(tuneId, entryId) {
notesTuneId = tuneId; notesTuneId = tuneId;
notesEntryId = entryId; notesEntryId = entryId;
const tune = allTunes.find(t => t.id === tuneId); // Open immediately with name from slim data
const entry = entryId ? tune?.instruments.find(e => e.id === entryId) : null; const slimTune = allTunes.find(t => t.id === tuneId);
$('notes-modal-title').textContent = `Notes — ${slimTune?.name ?? ''}`;
$('notes-modal-title').textContent = `Notes — ${tune?.name ?? ''}`; $('tune-notes-list').innerHTML = '';
$('inst-notes-heading').textContent = entry $('inst-notes-list').innerHTML = '';
? `${entry.instrument_name} notes` $('inst-notes-section').hidden = !entryId;
: 'Instrument notes';
$('inst-notes-section').hidden = !entry;
renderTuneNotes(tune?.notes ?? []);
renderInstNotes(entry?.notes ?? []);
$('btn-add-tune-note').hidden = !unlocked; $('btn-add-tune-note').hidden = !unlocked;
$('btn-add-inst-note').hidden = !unlocked || !entryId;
notesModal.showModal();
// Fetch full tune for note data
try {
const fullTune = await apiFetch(`/tunes/${tuneId}`);
const entry = entryId ? fullTune.instruments.find(e => e.id === entryId) : null;
$('inst-notes-heading').textContent = entry ? `${entry.instrument_name} notes` : 'Instrument notes';
$('inst-notes-section').hidden = !entry;
$('btn-add-inst-note').hidden = !unlocked || !entry; $('btn-add-inst-note').hidden = !unlocked || !entry;
notesModal.showModal(); renderTuneNotes(fullTune.notes ?? []);
renderInstNotes(entry?.notes ?? []);
} catch (e) {
setStatus(e.message, true);
}
} }
function renderTuneNotes(notes) { function renderTuneNotes(notes) {
@ -848,11 +865,20 @@ async function deleteInstNote(noteId) {
} }
async function reloadNotesModal() { async function reloadNotesModal() {
await loadAll(); try {
const tune = allTunes.find(t => t.id === notesTuneId); const fullTune = await apiFetch(`/tunes/${notesTuneId}`);
const entry = notesEntryId ? tune?.instruments.find(e => e.id === notesEntryId) : null; const entry = notesEntryId ? fullTune.instruments.find(e => e.id === notesEntryId) : null;
renderTuneNotes(tune?.notes ?? []); renderTuneNotes(fullTune.notes ?? []);
renderInstNotes(entry?.notes ?? []); renderInstNotes(entry?.notes ?? []);
// Keep has_notes in sync so the 📝 button stays accurate
const idx = allTunes.findIndex(t => t.id === notesTuneId);
if (idx >= 0) {
allTunes[idx].has_notes = !!fullTune.notes?.length
|| fullTune.instruments.some(e => !!e.notes?.length);
}
} catch (e) {
setStatus(e.message, true);
}
} }
$('btn-notes-close').addEventListener('click', () => notesModal.close()); $('btn-notes-close').addEventListener('click', () => notesModal.close());
@ -860,12 +886,15 @@ $('btn-notes-close').addEventListener('click', () => notesModal.close());
// ── References display modal ──────────────────────────────────── // ── References display modal ────────────────────────────────────
$('btn-refs-modal-close').addEventListener('click', () => refsModal.close()); $('btn-refs-modal-close').addEventListener('click', () => refsModal.close());
function openRefsModal(tuneId) { async function openRefsModal(tuneId) {
const tune = allTunes.find(t => t.id === tuneId); const tune = allTunes.find(t => t.id === tuneId);
$('refs-modal-title').textContent = `References — ${tune?.name ?? ''}`; $('refs-modal-title').textContent = `References — ${tune?.name ?? ''}`;
refsModalList.innerHTML = ''; refsModalList.innerHTML = '<p style="color:var(--muted);font-size:.875rem">Loading…</p>';
refsModal.showModal();
const refs = tune?.references ?? []; try {
const refs = await apiFetch(`/tunes/${tuneId}/references`);
refsModalList.innerHTML = '';
if (refs.length === 0) { if (refs.length === 0) {
refsModalList.innerHTML = '<p style="color:var(--muted);font-size:.875rem">No references.</p>'; refsModalList.innerHTML = '<p style="color:var(--muted);font-size:.875rem">No references.</p>';
} else { } else {
@ -881,8 +910,9 @@ function openRefsModal(tuneId) {
refsModalList.appendChild(item); refsModalList.appendChild(item);
}); });
} }
} catch (e) {
refsModal.showModal(); refsModalList.innerHTML = `<p style="color:#eb5757;font-size:.875rem">${esc(e.message)}</p>`;
}
} }
// ── Call / Learn mode ──────────────────────────────────────────── // ── Call / Learn mode ────────────────────────────────────────────
@ -1086,8 +1116,8 @@ function getBulkRefsFiltered() {
if (search && !(t.name ?? '').toLowerCase().includes(search)) return false; if (search && !(t.name ?? '').toLowerCase().includes(search)) return false;
if (srcId && t.source_id !== parseInt(srcId)) return false; if (srcId && t.source_id !== parseInt(srcId)) return false;
if (instId && !t.instruments.some(e => e.instrument_id === parseInt(instId))) return false; if (instId && !t.instruments.some(e => e.instrument_id === parseInt(instId))) return false;
if (hasRefs === 'no' && t.references.length > 0) return false; if (hasRefs === 'no' && (t.reference_count ?? 0) > 0) return false;
if (hasRefs === 'yes' && t.references.length === 0) return false; if (hasRefs === 'yes' && (t.reference_count ?? 0) === 0) return false;
return true; return true;
}); });
@ -1124,7 +1154,7 @@ function renderBulkRefsList() {
row.dataset.tuneId = tune.id; row.dataset.tuneId = tune.id;
const instruments = tune.instruments.map(e => e.instrument_name).filter(Boolean).join(', '); const instruments = tune.instruments.map(e => e.instrument_name).filter(Boolean).join(', ');
const refCount = tune.references.length; const refCount = tune.reference_count ?? 0;
const refPill = refCount > 0 const refPill = refCount > 0
? `<span class="pill pill-yes">${refCount} ref${refCount !== 1 ? 's' : ''}</span>` ? `<span class="pill pill-yes">${refCount} ref${refCount !== 1 ? 's' : ''}</span>`
: `<span class="pill" style="background:#222">0</span>`; : `<span class="pill" style="background:#222">0</span>`;