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