Music: on wide screens the Queue button docks an Up next panel down the right side that stays open while browsing
build-container / build (push) Successful in 3m14s
build-container / build (push) Successful in 3m14s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1293ff0673
commit
bbe32bae83
2 files changed
+35
-2
No files matched your search
+24
-2
@@ -136,7 +136,28 @@ function restorePlayer() {
|
|||||||
pl.a.addEventListener('loadedmetadata', () => { if (st.pos) pl.a.currentTime = st.pos; }, { once: true });
|
pl.a.addEventListener('loadedmetadata', () => { if (st.pos) pl.a.currentTime = st.pos; }, { once: true });
|
||||||
renderMini(); syncUI();
|
renderMini(); syncUI();
|
||||||
}
|
}
|
||||||
|
// On a wide screen the queue docks down the right side of the page and stays open while you browse; the Queue button turns it on and off.
|
||||||
|
const qWide = () => typeof matchMedia === 'function' && matchMedia('(min-width: 1100px)').matches;
|
||||||
|
function qDock() {
|
||||||
|
if (!qWide() && !document.body?.classList.contains('qdock')) return; // (narrow screens use the sheet)
|
||||||
|
let el = document.getElementById('qdock'), on = store.get('q_dock', false) && qWide() && pl.q.length && !$('#player').hidden;
|
||||||
|
if (!on) { if (el) el.remove(); document.body.classList.remove('qdock'); return; }
|
||||||
|
if (!el) { el = document.createElement('aside'); el.id = 'qdock'; el.setAttribute('aria-label', 'Up next'); $('#main').appendChild(el); }
|
||||||
|
document.body.classList.add('qdock'); el.style.bottom = ($('#player').offsetHeight || 0) + 'px';
|
||||||
|
const keep = el.querySelector('.qlist')?.scrollTop || 0;
|
||||||
|
el.innerHTML = `<div class="qhead"><b>Up next</b><small>${pl.q.length} tracks${pl.radio ? ' · radio' : ''}</small><button class="ibtn" id="qdx" aria-label="Hide the queue">${icon('x')}</button></div>
|
||||||
|
<div class="qlist">${pl.q.map((t, i) => `<div class="qrow ${i === pl.i ? 'now' : ''}" data-i="${i}"><button class="qmain" data-j="${i}"><span class="num">${i === pl.i ? '▶' : i + 1}</span><span class="grow"><b>${esc(t.title)}</b><small>${esc(t.artist)}</small></span></button>${i === pl.i ? '' : `<button class="ibtn" data-x="${i}" aria-label="Remove" title="Remove">${icon('x')}</button>`}</div>`).join('')}</div>
|
||||||
|
<div class="qfoot"><button class="btn ghost" id="qdc">Clear up next</button><button class="btn ghost" id="qds">Save as playlist</button></div>`;
|
||||||
|
const list = el.querySelector('.qlist'); list.scrollTop = keep || Math.max(0, (list.querySelector('.now')?.offsetTop || 0) - 60);
|
||||||
|
el.querySelector('#qdx').onclick = () => { store.set('q_dock', false); qDock(); };
|
||||||
|
el.querySelectorAll('[data-j]').forEach(b => b.onclick = () => { pl.i = +b.dataset.j; loadCur(0); });
|
||||||
|
el.querySelectorAll('[data-x]').forEach(b => b.onclick = () => { const k = +b.dataset.x; pl.q.splice(k, 1); if (k < pl.i) pl.i--; saveState(); tcKick(); updateNow(); });
|
||||||
|
el.querySelector('#qdc').onclick = () => { pl.q.length = pl.i + 1; if (pl.orig) pl.orig = null, pl.shuffle = false; saveState(); tcKick(); updateNow(); };
|
||||||
|
el.querySelector('#qds').onclick = () => addToPlaylist(pl.q.slice(), 'Queue ' + new Date().toLocaleDateString([], { month: 'short', day: 'numeric' }));
|
||||||
|
}
|
||||||
|
if (typeof matchMedia === 'function') matchMedia('(min-width: 1100px)').addEventListener('change', () => qDock());
|
||||||
function openQueue() {
|
function openQueue() {
|
||||||
|
if (qWide()) { store.set('q_dock', !store.get('q_dock', false)); return qDock(); }
|
||||||
const upcoming = pl.q.map((t, i) => ({ t, i }));
|
const upcoming = pl.q.map((t, i) => ({ t, i }));
|
||||||
const el = openSheet('Up next', pl.q.length + ' tracks' + (pl.radio ? ' · library radio is on' : ''), `<div class="list" style="background:var(--panel2);border:0;max-height:55dvh;overflow:auto">${upcoming.map(({ t, i }) => `
|
const el = openSheet('Up next', pl.q.length + ' tracks' + (pl.radio ? ' · library radio is on' : ''), `<div class="list" style="background:var(--panel2);border:0;max-height:55dvh;overflow:auto">${upcoming.map(({ t, i }) => `
|
||||||
<div class="row it ${i === pl.i ? 'playing' : ''}"><button class="rmain" data-j="${i}"><span class="num">${i === pl.i ? '▶' : i + 1}</span><span class="grow"><b>${esc(t.title)}</b><small>${esc(t.artist)}</small></span></button>
|
<div class="row it ${i === pl.i ? 'playing' : ''}"><button class="rmain" data-j="${i}"><span class="num">${i === pl.i ? '▶' : i + 1}</span><span class="grow"><b>${esc(t.title)}</b><small>${esc(t.artist)}</small></span></button>
|
||||||
@@ -204,8 +225,8 @@ function markPlaying() {
|
|||||||
}
|
}
|
||||||
function renderMini() {
|
function renderMini() {
|
||||||
if (typeof videoBarOn === 'function' && videoBarOn()) return; // the bottom bar is showing the video's controls
|
if (typeof videoBarOn === 'function' && videoBarOn()) return; // the bottom bar is showing the video's controls
|
||||||
const t = cur(), el = $('#player'); if (!t) return (el.hidden = true);
|
const t = cur(), el = $('#player'); if (!t) { el.hidden = true; return qDock(); }
|
||||||
el.hidden = false;
|
el.hidden = false; setTimeout(qDock);
|
||||||
el.innerHTML = `<div class="mini"><div class="bar2" id="mseek" role="slider" aria-label="Seek"><u id="mbuf"></u><i id="mp"></i></div><img class="art" alt="" src="${px.art(t.thumb, 100)}">
|
el.innerHTML = `<div class="mini"><div class="bar2" id="mseek" role="slider" aria-label="Seek"><u id="mbuf"></u><i id="mp"></i></div><img class="art" alt="" src="${px.art(t.thumb, 100)}">
|
||||||
<button class="meta" id="mo"><b>${esc(t.title)}</b><small>${esc(t.artist)}</small></button><span class="mtime" id="mtm"></span>
|
<button class="meta" id="mo"><b>${esc(t.title)}</b><small>${esc(t.artist)}</small></button><span class="mtime" id="mtm"></span>
|
||||||
<button class="ibtn wide-only" id="ml" aria-label="Lyrics" title="Lyrics">${icon('lyrics')}</button><button class="ibtn wide-only" id="mv" aria-label="Visuals" title="Visuals">${icon('visuals')}</button><button class="ibtn wide-only" id="mq" aria-label="Queue" title="Queue">${icon('queue')}</button><button class="ibtn wide-only" id="mz" aria-label="Sleep timer" title="Sleep timer">${icon('moon')}</button><button class="ibtn wide-only" id="ms" aria-label="Shuffle">${icon('shuffle')}</button><button class="ibtn wide-only" id="mr" aria-label="Repeat">${icon('repeat')}</button>
|
<button class="ibtn wide-only" id="ml" aria-label="Lyrics" title="Lyrics">${icon('lyrics')}</button><button class="ibtn wide-only" id="mv" aria-label="Visuals" title="Visuals">${icon('visuals')}</button><button class="ibtn wide-only" id="mq" aria-label="Queue" title="Queue">${icon('queue')}</button><button class="ibtn wide-only" id="mz" aria-label="Sleep timer" title="Sleep timer">${icon('moon')}</button><button class="ibtn wide-only" id="ms" aria-label="Shuffle">${icon('shuffle')}</button><button class="ibtn wide-only" id="mr" aria-label="Repeat">${icon('repeat')}</button>
|
||||||
@@ -287,6 +308,7 @@ function renderNpCard() {
|
|||||||
}
|
}
|
||||||
// The Now Playing sheet stays open while tracks change, so refresh what it shows.
|
// The Now Playing sheet stays open while tracks change, so refresh what it shows.
|
||||||
function updateNow() {
|
function updateNow() {
|
||||||
|
qDock();
|
||||||
const t = cur(), sk = $('#npk'); if (!t || !sk) return;
|
const t = cur(), sk = $('#npk'); if (!t || !sk) return;
|
||||||
$('#npti').textContent = t.title; $('#npart').src = px.art(t.thumb, 600);
|
$('#npti').textContent = t.title; $('#npart').src = px.art(t.thumb, 600);
|
||||||
const q = $('#npq span'); if (q) q.textContent = 'Queue · ' + (pl.q.length - pl.i - 1);
|
const q = $('#npq span'); if (q) q.textContent = 'Queue · ' + (pl.q.length - pl.i - 1);
|
||||||
|
|||||||
@@ -1177,3 +1177,14 @@ body.vfullwin:has(.vstage.vhide) .vstage.vfull { bottom: 0; } body.vfullwin:has(
|
|||||||
.mini .bar2:active i::after { transform: scale(1.35); }
|
.mini .bar2:active i::after { transform: scale(1.35); }
|
||||||
.mini .bar2::before { top: -14px; bottom: -12px; }
|
.mini .bar2::before { top: -14px; bottom: -12px; }
|
||||||
.mini .bar2[hidden] i::after { display: none; }
|
.mini .bar2[hidden] i::after { display: none; }
|
||||||
|
|
||||||
|
/* the queue docked down the right side on wide screens */
|
||||||
|
#qdock { position: fixed; right: 0; top: 0; width: 340px; display: flex; flex-direction: column; background: var(--panel); border-left: 1px solid var(--line); z-index: 40; }
|
||||||
|
body.qdock #view { padding-right: 340px; }
|
||||||
|
#qdock .qhead { display: flex; align-items: center; gap: 8px; padding: 14px 8px 10px 16px; } #qdock .qhead b { font-size: 16px; } #qdock .qhead small { color: var(--muted); flex: 1; }
|
||||||
|
#qdock .qlist { flex: 1; overflow-y: auto; padding: 0 8px; }
|
||||||
|
#qdock .qrow { display: flex; align-items: center; border-radius: 10px; } #qdock .qrow:hover { background: var(--panel2); } #qdock .qrow.now { background: var(--panel2); }
|
||||||
|
#qdock .qmain { flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px; padding: 8px 6px; background: none; border: 0; color: inherit; font: inherit; text-align: left; cursor: pointer; }
|
||||||
|
#qdock .num { width: 22px; text-align: center; color: var(--muted); font-size: 13px; flex: none; } #qdock .qrow.now .num, #qdock .qrow.now b { color: var(--accent); }
|
||||||
|
#qdock .grow { min-width: 0; } #qdock b, #qdock small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #qdock .qmain small { color: var(--muted); font-size: 12px; }
|
||||||
|
#qdock .qfoot { display: flex; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--line); } #qdock .qfoot .btn { flex: 1; justify-content: center; }
|
||||||
Reference in new issue
Block a user