Add deck tabs, uniform action buttons, lyric reports in the player panel and a song list in the lyrics studio
This commit is contained in:
@@ -118,6 +118,13 @@
|
||||
.step { padding: 14px; border-radius: 16px; background: rgba(255,255,255,.035); border: 1px solid var(--line); }
|
||||
.step b { display: grid; place-items: center; width: 26px; height: 26px; border-radius: 50%; background: var(--grad); color: #fff; font-size: 13px; margin-bottom: 8px; }
|
||||
.step h3 { margin: 0 0 2px; font-size: 14px; } .step p { margin: 0; font-size: 12.5px; color: var(--text-dim); }
|
||||
.songs { display: grid; gap: 6px; margin-top: 14px; max-height: 46vh; overflow-y: auto; }
|
||||
.song-row { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; padding: 10px 12px; border-radius: 14px; border: 1px solid var(--line); background: rgba(255,255,255,.03); }
|
||||
.song-row:hover { border-color: var(--a1); }
|
||||
.song-row b { display: block; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.song-row small { color: var(--text-dim); font-size: 12px; }
|
||||
.song-row .st { flex: none; font: 700 11px var(--ui); padding: 3px 9px; border-radius: 99px; border: 1px solid var(--line-2); color: var(--text-dim); }
|
||||
.song-row .st.has { color: var(--ok); border-color: rgba(74,222,128,.4); }
|
||||
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
|
||||
.chip { height: 34px; padding: 0 13px; border-radius: 99px; border: 1px solid var(--line-2); background: rgba(255,255,255,.04); font-size: 13px; font-weight: 600; color: var(--text-2); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.chip:hover { color: var(--text); border-color: var(--a1); }
|
||||
@@ -284,6 +291,10 @@
|
||||
</form>
|
||||
<div id="loadMsg" class="msg"></div>
|
||||
<div id="recentChips" class="chips"></div>
|
||||
<div class="row" style="margin-top:16px"><h2 style="margin:0;font-size:14px">Songs on the server</h2><span class="grow"></span>
|
||||
<input class="field" id="songFilter" type="search" placeholder="Filter…" style="flex:0 1 200px;height:36px" /></div>
|
||||
<p class="muted" style="margin:4px 0 0;font-size:12.5px">Downloaded videos and uploads. Songs that already have lyrics can be corrected here too.</p>
|
||||
<div id="songList" class="songs"><div class="muted" style="padding:10px">Loading…</div></div>
|
||||
<div class="steps">
|
||||
<div class="step"><b>1</b><h3>Open a song</h3><p>Paste a link or pick one from your library.</p></div>
|
||||
<div class="step"><b>2</b><h3>Play & tap</h3><p>Switch to Tap-sync and hit the big button on each line as it is sung.</p></div>
|
||||
@@ -513,6 +524,7 @@
|
||||
if (!$('tabsTop').children.length) buildTabs();
|
||||
renderDocs();
|
||||
loadRecent();
|
||||
loadSongs();
|
||||
refreshBadge();
|
||||
const want = videoIdFrom(new URLSearchParams(location.search).get('v') || '');
|
||||
const hash = location.hash.replace('#', '');
|
||||
@@ -656,6 +668,25 @@
|
||||
const j = await api('/api/uploads?limit=10');
|
||||
$('recentChips').innerHTML = (j.uploads || []).map((u) => `<button type="button" class="chip" data-open="${esc(u.id)}">♪ ${esc(u.title)}</button>`).join('');
|
||||
}
|
||||
let SONGS = [];
|
||||
async function loadSongs() {
|
||||
const [m, u] = await Promise.all([api('/api/admin/media'), api('/api/uploads?limit=200')]);
|
||||
const seen = new Set();
|
||||
SONGS = [];
|
||||
for (const r of (m.media || [])) { seen.add(r.id); SONGS.push({ id: r.id, title: r.title || r.id, sub: r.channel, lines: r.lyricsLines || 0 }); }
|
||||
for (const r of (u.uploads || [])) if (!seen.has(r.id)) SONGS.push({ id: r.id, title: r.title, sub: r.artist || 'Upload', lines: -1 });
|
||||
paintSongs();
|
||||
}
|
||||
function paintSongs() {
|
||||
const q = $('songFilter').value.trim().toLowerCase();
|
||||
const rows = SONGS.filter((r) => !q || (r.title + ' ' + (r.sub || '')).toLowerCase().includes(q));
|
||||
$('songList').innerHTML = rows.length ? rows.map((r) => `<button type="button" class="song-row" data-open="${esc(r.id)}">
|
||||
<span style="min-width:0;flex:1"><b>${esc(r.title)}</b><small>${esc(r.sub || '')}</small></span>
|
||||
<span class="st ${r.lines > 0 ? 'has' : ''}">${r.lines > 0 ? `${r.lines} lines` : r.lines === 0 ? 'no lyrics' : 'open'}</span></button>`).join('')
|
||||
: '<div class="muted" style="padding:10px">Nothing matches — paste a link above to open any YouTube song.</div>';
|
||||
}
|
||||
$('songFilter').addEventListener('input', paintSongs);
|
||||
$('songList').addEventListener('click', (e) => { const b = e.target.closest('[data-open]'); if (b) leOpen(b.dataset.open); });
|
||||
$('recentChips').addEventListener('click', (e) => { const b = e.target.closest('[data-open]'); if (b) leOpen(b.dataset.open); });
|
||||
|
||||
/* ---------------- p2p ---------------- */
|
||||
|
||||
Reference in New Issue
Block a user