From 25e7a28b6ac833def3f459d5e01a72af41c19280 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 21:03:48 +0000 Subject: [PATCH] Merge shuffle, repeat list and loop one into one playback mode button --- frontend/app.js | 48 +++++++++++++++++++++++++++++++++++++++++--- frontend/index.html | 9 ++++----- frontend/refresh.css | 15 ++++++++++++++ 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index c9a8f32..0ff11d3 100755 --- a/frontend/app.js +++ b/frontend/app.js @@ -6717,7 +6717,7 @@ function applyShuffle() { queue = queue.slice(0, queueIndex + 1).concat(upcoming); } -function toggleShuffle() { +function toggleShuffle({ quiet = false } = {}) { data.settings.shuffle = !data.settings.shuffle; if (data.settings.shuffle) { applyShuffle(); @@ -6731,7 +6731,7 @@ function toggleShuffle() { persist(); updateLoopRepeatButtons(); renderUpNext(); - toast(data.settings.shuffle ? 'Shuffle on' : 'Shuffle off'); + if (!quiet) toast(data.settings.shuffle ? 'Shuffle on' : 'Shuffle off'); } // ---------- Temporary queue ---------- @@ -6854,10 +6854,51 @@ function toggleRepeat() { updateLoopRepeatButtons(); 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 = [ + '', + '', + '', + '', + '', +]; +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() { 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.shuffleBtn) els.shuffleBtn.classList.toggle('active', !!data.settings.shuffle); + const mb = $('modeBtn'); + if (mb) { + const i = playModeIndex(); + mb.innerHTML = ``; + 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() { if (Party.intercept('prev')) return; @@ -9467,7 +9508,8 @@ document.querySelectorAll('.chip').forEach((c) => { els.playBtn.addEventListener('click', () => Player.toggle()); els.nextBtn.addEventListener('click', playNext); 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.repeatBtn.addEventListener('click', toggleRepeat); els.fsBtn.addEventListener('click', () => { diff --git a/frontend/index.html b/frontend/index.html index 145808f..7da8be8 100755 --- a/frontend/index.html +++ b/frontend/index.html @@ -171,20 +171,19 @@
- + - + +
-
- - +