Merge shuffle, repeat list and loop one into one playback mode button

This commit is contained in:
Claude
2026-10-01 21:03:48 +00:00
parent f9cb7edeb2
commit 25e7a28b6a
3 changed files with 64 additions and 8 deletions

View File

@@ -6717,7 +6717,7 @@ function applyShuffle() {
queue = queue.slice(0, queueIndex + 1).concat(upcoming); queue = queue.slice(0, queueIndex + 1).concat(upcoming);
} }
function toggleShuffle() { function toggleShuffle({ quiet = false } = {}) {
data.settings.shuffle = !data.settings.shuffle; data.settings.shuffle = !data.settings.shuffle;
if (data.settings.shuffle) { if (data.settings.shuffle) {
applyShuffle(); applyShuffle();
@@ -6731,7 +6731,7 @@ function toggleShuffle() {
persist(); persist();
updateLoopRepeatButtons(); updateLoopRepeatButtons();
renderUpNext(); renderUpNext();
toast(data.settings.shuffle ? 'Shuffle on' : 'Shuffle off'); if (!quiet) toast(data.settings.shuffle ? 'Shuffle on' : 'Shuffle off');
} }
// ---------- Temporary queue ---------- // ---------- Temporary queue ----------
@@ -6854,10 +6854,51 @@ function toggleRepeat() {
updateLoopRepeatButtons(); updateLoopRepeatButtons();
toast(data.settings.repeatMode === 'all' ? 'Repeating list' : 'Repeat off'); toast(data.settings.repeatMode === 'all' ? 'Repeating list' : 'Repeat off');
} }
// One button for shuffle / repeat list / loop one. Cycle:
// off → repeat list → shuffle → shuffle + repeat → loop this video → off.
const PLAY_MODES = [
{ name: 'In order', sub: 'Plays the list once, then stops' },
{ name: 'Repeat list', sub: 'Starts over when the list ends' },
{ name: 'Shuffle', sub: 'Random order' },
{ name: 'Shuffle + repeat', sub: 'Random order, forever' },
{ name: 'Loop this video', sub: 'Plays the same video again and again' },
];
const MODE_ICONS = [
'<path d="M17 2l4 4-4 4M3 12v-2a4 4 0 0 1 4-4h14M7 22l-4-4 4-4M21 12v2a4 4 0 0 1-4 4H3"/>',
'<path d="M17 2l4 4-4 4M3 12v-2a4 4 0 0 1 4-4h14M7 22l-4-4 4-4M21 12v2a4 4 0 0 1-4 4H3"/>',
'<path d="M16 3h5v5M4 20 21 3M21 16v5h-5M15 15l6 6M4 4l5 5"/>',
'<path d="M16 3h5v5M4 20 21 3M21 16v5h-5M15 15l6 6M4 4l5 5"/>',
'<path d="M17 2l4 4-4 4M3 12v-2a4 4 0 0 1 4-4h14M7 22l-4-4 4-4M21 12v2a4 4 0 0 1-4 4H3M11 10.2l1.6-.9v5.4"/>',
];
function playModeIndex() {
const st = data.settings;
if (st.loopOne) return 4;
const rep = st.repeatMode === 'all';
return st.shuffle ? (rep ? 3 : 2) : (rep ? 1 : 0);
}
function cyclePlayMode() {
const next = (playModeIndex() + 1) % PLAY_MODES.length;
const wantShuffle = next === 2 || next === 3;
data.settings.loopOne = next === 4;
data.settings.repeatMode = next === 1 || next === 3 ? 'all' : 'off';
if (!!data.settings.shuffle !== wantShuffle) toggleShuffle({ quiet: true });
persist();
updateLoopRepeatButtons();
toast(PLAY_MODES[next].name, { duration: 1800 });
}
function updateLoopRepeatButtons() { function updateLoopRepeatButtons() {
if (els.loopBtn) els.loopBtn.classList.toggle('active', !!data.settings.loopOne); if (els.loopBtn) els.loopBtn.classList.toggle('active', !!data.settings.loopOne);
if (els.repeatBtn) els.repeatBtn.classList.toggle('active', data.settings.repeatMode === 'all'); if (els.repeatBtn) els.repeatBtn.classList.toggle('active', data.settings.repeatMode === 'all');
if (els.shuffleBtn) els.shuffleBtn.classList.toggle('active', !!data.settings.shuffle); if (els.shuffleBtn) els.shuffleBtn.classList.toggle('active', !!data.settings.shuffle);
const mb = $('modeBtn');
if (mb) {
const i = playModeIndex();
mb.innerHTML = `<svg viewBox="0 0 24 24" aria-hidden="true">${MODE_ICONS[i]}</svg>`;
mb.classList.toggle('active', i > 0);
mb.dataset.badge = i === 3 ? '∞' : '';
mb.title = `${PLAY_MODES[i].name} — ${PLAY_MODES[i].sub}. Tap to change.`;
mb.setAttribute('aria-label', 'Playback mode: ' + PLAY_MODES[i].name);
}
} }
function playPrev() { function playPrev() {
if (Party.intercept('prev')) return; if (Party.intercept('prev')) return;
@@ -9467,7 +9508,8 @@ document.querySelectorAll('.chip').forEach((c) => {
els.playBtn.addEventListener('click', () => Player.toggle()); els.playBtn.addEventListener('click', () => Player.toggle());
els.nextBtn.addEventListener('click', playNext); els.nextBtn.addEventListener('click', playNext);
els.prevBtn.addEventListener('click', playPrev); els.prevBtn.addEventListener('click', playPrev);
els.shuffleBtn.addEventListener('click', toggleShuffle); els.shuffleBtn.addEventListener('click', () => toggleShuffle());
if ($('modeBtn')) $('modeBtn').addEventListener('click', cyclePlayMode);
els.loopBtn.addEventListener('click', toggleLoopOne); els.loopBtn.addEventListener('click', toggleLoopOne);
els.repeatBtn.addEventListener('click', toggleRepeat); els.repeatBtn.addEventListener('click', toggleRepeat);
els.fsBtn.addEventListener('click', () => { els.fsBtn.addEventListener('click', () => {

View File

@@ -171,20 +171,19 @@
</div> </div>
<button id="waveLoud" class="wave-loud hidden" type="button">⚡ Jump to the loudest part</button> <button id="waveLoud" class="wave-loud hidden" type="button">⚡ Jump to the loudest part</button>
<div class="deck-main"> <div class="deck-main">
<button id="shuffleBtn" class="ctrl" title="Shuffle upcoming tracks (S)" aria-label="Shuffle"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M16 3h5v5M4 20 21 3M21 16v5h-5M15 15l6 6M4 4l5 5"/></svg></button> <button id="modeBtn" class="ctrl" type="button" title="Playback mode" aria-label="Playback mode"></button>
<button id="prevBtn" class="ctrl" title="Previous" aria-label="Previous"><svg viewBox="0 0 24 24" aria-hidden="true" class="fill"><rect x="5" y="5" width="2.8" height="14" rx="1.2"/><path d="M19 5.5v13L9.5 12z"/></svg></button> <button id="prevBtn" class="ctrl" title="Previous" aria-label="Previous"><svg viewBox="0 0 24 24" aria-hidden="true" class="fill"><rect x="5" y="5" width="2.8" height="14" rx="1.2"/><path d="M19 5.5v13L9.5 12z"/></svg></button>
<button id="playBtn" class="ctrl play" title="Play/Pause" aria-label="Play or pause"><svg viewBox="0 0 24 24" aria-hidden="true" class="fill"><path d="M9 5.5v13l10-6.5z"/></svg></button> <button id="playBtn" class="ctrl play" title="Play/Pause" aria-label="Play or pause"><svg viewBox="0 0 24 24" aria-hidden="true" class="fill"><path d="M9 5.5v13l10-6.5z"/></svg></button>
<button id="nextBtn" class="ctrl" title="Next" aria-label="Next"><svg viewBox="0 0 24 24" aria-hidden="true" class="fill"><rect x="16.2" y="5" width="2.8" height="14" rx="1.2"/><path d="M5 5.5v13l9.5-6.5z"/></svg></button> <button id="nextBtn" class="ctrl" title="Next" aria-label="Next"><svg viewBox="0 0 24 24" aria-hidden="true" class="fill"><rect x="16.2" y="5" width="2.8" height="14" rx="1.2"/><path d="M5 5.5v13l9.5-6.5z"/></svg></button>
<button id="repeatBtn" class="ctrl" title="Repeat list when finished (R)" aria-label="Repeat list"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M17 2l4 4-4 4M3 12v-2a4 4 0 0 1 4-4h14M7 22l-4-4 4-4M21 12v2a4 4 0 0 1-4 4H3"/></svg></button> <button id="muteBtn" class="ctrl" title="Mute" aria-label="Mute">🔊</button>
<div id="dkModeHost" hidden><button id="shuffleBtn" class="ctrl" title="Shuffle upcoming tracks (S)" aria-label="Shuffle"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M16 3h5v5M4 20 21 3M21 16v5h-5M15 15l6 6M4 4l5 5"/></svg></button><button id="repeatBtn" class="ctrl" title="Repeat list when finished (R)" aria-label="Repeat list"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M17 2l4 4-4 4M3 12v-2a4 4 0 0 1 4-4h14M7 22l-4-4 4-4M21 12v2a4 4 0 0 1-4 4H3"/></svg></button><button id="loopBtn" class="ctrl" title="Loop this video (L)"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M17 2l4 4-4 4M3 12v-2a4 4 0 0 1 4-4h14M7 22l-4-4 4-4M21 12v2a4 4 0 0 1-4 4H3M11 10.2l1.6-.9v5.4"/></svg></button></div>
</div> </div>
<div class="deck-vol vol"> <div class="deck-vol vol">
<button id="muteBtn" class="ctrl" title="Mute" aria-label="Mute">🔊</button>
<input id="volume" type="range" min="0" max="1" step="0.01" value="1" aria-label="Volume" /> <input id="volume" type="range" min="0" max="1" step="0.01" value="1" aria-label="Volume" />
</div> </div>
<div class="dk-quick"> <div class="dk-quick">
<button id="loopBtn" class="ctrl" title="Loop this video (L)"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M17 2l4 4-4 4M3 12v-2a4 4 0 0 1 4-4h14M7 22l-4-4 4-4M21 12v2a4 4 0 0 1-4 4H3M11 10.2l1.6-.9v5.4"/></svg></button>
<button id="fsBtn" class="ctrl" title="Fullscreen">⛶</button> <button id="fsBtn" class="ctrl" title="Fullscreen">⛶</button>
<button id="ctlMoreBtn" class="dk-more" type="button" aria-haspopup="dialog"><span>⋯ More controls</span><small>Speed · Quality · A-B · Sleep</small></button> <button id="ctlMoreBtn" class="dk-more" type="button" aria-haspopup="dialog"><span>⋯ More controls</span><small>Speed · Quality · A-B · Sleep · EQ</small></button>
</div> </div>
<div id="dkMoreHost" hidden> <div id="dkMoreHost" hidden>
<button id="abABtn" class="ctrl ab-ctrl" title="Set A-B loop start [A]">A</button> <button id="abABtn" class="ctrl ab-ctrl" title="Set A-B loop start [A]">A</button>

View File

@@ -471,3 +471,18 @@
#controls .deck-main .ctrl.play svg { width: 34px; height: 34px; } #controls .deck-main .ctrl.play svg { width: 34px; height: 34px; }
#controls .deck-main .ctrl { display: grid; place-items: center; } #controls .deck-main .ctrl { display: grid; place-items: center; }
#controls .ctrl.active svg { filter: drop-shadow(0 0 6px var(--accent-glow)); } #controls .ctrl.active svg { filter: drop-shadow(0 0 6px var(--accent-glow)); }
/* One playback-mode button + mute in the transport row */
#controls .deck-main { grid-template-columns: 1fr 1fr 1.5fr 1fr 1fr; }
#controls #dkModeHost { display: none; }
#controls #modeBtn { position: relative; }
#controls #modeBtn:not(.active) { color: var(--text-dim); }
#controls #modeBtn[data-badge]:not([data-badge=""])::after {
content: attr(data-badge); position: absolute; right: 4px; top: 4px;
font: 800 12px/1 var(--ui); color: var(--accent-bright);
}
#controls .deck-main #muteBtn { font-size: 20px; }
#controls .dk-quick .ctrl { display: none; }
#controls .dk-quick #fsBtn { display: grid; }
#controls .deck-main #modeBtn { order: 1; }
#controls .deck-main #muteBtn { order: 5; }