diff --git a/frontend/admin.html b/frontend/admin.html
index a8cabc4..1911239 100644
--- a/frontend/admin.html
+++ b/frontend/admin.html
@@ -6,6 +6,7 @@
- ${esc(r.title)} ${esc(r.sub || '')}
- ${r.lines > 0 ? `${r.lines} lines` : r.lines === 0 ? 'no lyrics' : 'open'}
+ const has = SONGS.filter((r) => r.lines > 0).length, need = SONGS.filter((r) => r.lines === 0).length, known = has + need;
+ const counts = { all: SONGS.length, need, has };
+ document.querySelectorAll('[data-n]').forEach((el) => { el.textContent = counts[el.dataset.n].toLocaleString(); });
+ document.querySelectorAll('[data-view]').forEach((b) => b.setAttribute('aria-pressed', String(b.dataset.view === songView)));
+ $('songCount').textContent = SONGS.length ? `${SONGS.length.toLocaleString()} songs` : '';
+ $('coverage').hidden = !known;
+ if (known) {
+ $('covFill').style.width = `${(has / known) * 100}%`;
+ $('covText').innerHTML = `
${has.toLocaleString()} of ${known.toLocaleString()} downloaded songs have lyrics${need ? ` ·
${need.toLocaleString()} still need them` : ' · all done'}`;
+ }
+ const rows = SONGS.filter((r) => (songView === 'all' || (songView === 'has' ? r.lines > 0 : r.lines === 0))
+ && (!q || (r.title + ' ' + (r.sub || '')).toLowerCase().includes(q)));
+ const shown = rows.slice(0, songLimit);
+ $('songList').innerHTML = shown.length ? shown.map((r) => `
+ ${SONG_ART}
+ ${esc(r.title)} ${esc(r.sub || '')}
+ ${r.lines > 0 ? `${r.lines} lines` : r.lines === 0 ? 'no lyrics' : 'open'} ${songDur(r.dur) ? `${songDur(r.dur)} ` : ''}
${r.lines > 0 ? 'Edit lyrics' : 'Add lyrics'}
Whisper
${r.sourceUrl ? `
Re-download from source Source Open source page ` : ''}
`).join('')
- : '
Nothing matches — paste a link above to open any YouTube song.
';
+ : `
${SONGS.length ? 'Nothing matches. Paste a link above to open any YouTube song.' : 'No songs on the server yet. Paste a link above to open any YouTube song.'}
`;
+ const more = rows.length - shown.length;
+ $('songMore').classList.toggle('hidden', more <= 0);
+ $('songMore').textContent = `Show ${Math.min(more, 30)} more of ${more.toLocaleString()}`;
paintWhisper();
}
- $('songFilter').addEventListener('input', paintSongs);
+ $('songMore').addEventListener('click', () => { songLimit += 30; paintSongs(); });
+ document.querySelector('.lib-filter').addEventListener('click', (e) => {
+ const b = e.target.closest('[data-view]'); if (!b) return;
+ songView = b.dataset.view; songLimit = 30; paintSongs();
+ });
+ $('songFilter').addEventListener('input', () => { songLimit = 30; paintSongs(); });
$('songList').addEventListener('click', (e) => {
const whisper = e.target.closest('[data-whisper]'); if (whisper) { startWhisper(whisper.dataset.whisper); return; }
const source = e.target.closest('[data-redownload]'); if (source) { startSourceRedownload(source); return; }