From 868b7fb10c912ea3b7200d1ffa1f287dce335f23 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sat, 9 May 2026 19:58:44 +0300 Subject: Fix shuffle hotkey reshuffle behavior (q1) --- web/js/app.js | 5 ++-- web/js/shuffle.js | 12 ++++++++++ web/js/tests/shuffle.test.js | 54 ++++++++++++++++++++++++++++++++++++++++++++ web/js/views/media-grid.js | 4 +++- web/sw.js | 2 +- 5 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 web/js/tests/shuffle.test.js diff --git a/web/js/app.js b/web/js/app.js index cfa43f1..9ecd854 100644 --- a/web/js/app.js +++ b/web/js/app.js @@ -20,7 +20,7 @@ import { isImageMode as playerIsImageMode, } from './player.js'; import { initSearch, parseQuery, showSearchHelp } from './search.js'; -import { initShuffle, toggle as toggleShuffle, isOn as isShuffle } from './shuffle.js'; +import { initShuffle, enable as enableShuffle, isOn as isShuffle, revision as shuffleRevision } from './shuffle.js'; import { initThemes } from './themes.js'; import { initNotes } from './notes.js'; import { initAdmin } from './admin.js'; @@ -168,6 +168,7 @@ async function initApp() { initSets({ onLoadMedia: loadMedia }); initMediaGrid({ isShuffle, + shuffleRevision, onSetCleared: updateSetRowsUI, openNotesForSelected, openTagsForElement, @@ -266,7 +267,7 @@ function keyboardHandlers() { closeAllModals(); }, shuffle: () => { - toggleShuffle(); + enableShuffle(); loadMedia(); }, share: () => shareSelected(), diff --git a/web/js/shuffle.js b/web/js/shuffle.js index bef4dad..ccdede7 100644 --- a/web/js/shuffle.js +++ b/web/js/shuffle.js @@ -1,10 +1,12 @@ let shuffleOn = false; +let shuffleRevision = 0; export function initShuffle({ onChange }) { const btn = document.getElementById('shuffle-toggle'); if (!btn) return; btn.addEventListener('click', () => { shuffleOn = !shuffleOn; + shuffleRevision += 1; updateUI(); onChange?.(shuffleOn); }); @@ -12,12 +14,22 @@ export function initShuffle({ onChange }) { export function toggle() { shuffleOn = !shuffleOn; + shuffleRevision += 1; + updateUI(); + return shuffleOn; +} + +export function enable() { + shuffleOn = true; + shuffleRevision += 1; updateUI(); return shuffleOn; } export function isOn() { return shuffleOn; } +export function revision() { return shuffleRevision; } + function updateUI() { const btn = document.getElementById('shuffle-toggle'); if (btn) btn.classList.toggle('active', shuffleOn); diff --git a/web/js/tests/shuffle.test.js b/web/js/tests/shuffle.test.js new file mode 100644 index 0000000..bfbc79a --- /dev/null +++ b/web/js/tests/shuffle.test.js @@ -0,0 +1,54 @@ +import { enable, isOn, revision, toggle } from '../shuffle.js'; + +const failures = []; +const button = { + active: false, + classList: { + toggle(name, enabled) { + if (name === 'active') button.active = enabled; + }, + }, +}; + +globalThis.document = { + getElementById(id) { + return id === 'shuffle-toggle' ? button : null; + }, +}; + +function assert(cond, msg) { + if (!cond) failures.push(msg || 'assertion failed'); +} + +function testEnableTurnsShuffleOn() { + enable(); + assert(isOn(), 'enable should turn shuffle on'); + assert(button.active, 'enable should update the shuffle button active state'); +} + +function testRepeatedEnableKeepsShuffleOnAndAdvancesRevision() { + const before = revision(); + enable(); + assert(isOn(), 'repeated enable should keep shuffle on'); + assert(revision() > before, 'repeated enable should advance revision for a fresh random load'); +} + +function testToggleCanStillTurnShuffleOff() { + toggle(); + assert(!isOn(), 'toggle should still turn shuffle off for the toolbar button'); + assert(!button.active, 'toggle should update the shuffle button inactive state'); +} + +console.log('Running shuffle tests...'); +testEnableTurnsShuffleOn(); +testRepeatedEnableKeepsShuffleOnAndAdvancesRevision(); +testToggleCanStillTurnShuffleOff(); + +if (failures.length) { + console.error('FAILURES:'); + failures.forEach((m) => console.error(' - ' + m)); + process.exit(1); +} else { + console.log('All shuffle tests passed.'); + process.exit(0); +} diff --git a/web/js/views/media-grid.js b/web/js/views/media-grid.js index 4625eba..e82ae98 100644 --- a/web/js/views/media-grid.js +++ b/web/js/views/media-grid.js @@ -92,7 +92,8 @@ export async function loadMedia() { resultCount.textContent = resultText(total, page); } else { breadcrumb?.classList.add('hidden'); - const sort = callbacks.isShuffle?.() ? 'random' : (state.filters.sort || 'name'); + const shuffle = callbacks.isShuffle?.(); + const sort = shuffle ? 'random' : (state.filters.sort || 'name'); const params = { set_id: setIds ? '' : String(singleSetId || state.selectedSetId || ''), set_ids: setIds, @@ -105,6 +106,7 @@ export async function loadMedia() { filesize_min: state.filters.minFileSize ? String(parseInt(state.filters.minFileSize, 10) * 1024 * 1024) : '', filesize_max: state.filters.maxFileSize ? String(parseInt(state.filters.maxFileSize, 10) * 1024 * 1024) : '', sort, + shuffle_revision: shuffle ? String(callbacks.shuffleRevision?.() || 0) : '', limit: '1000', }; syncMediaPage(`grid:${JSON.stringify(params)}`); diff --git a/web/sw.js b/web/sw.js index 503ce8e..9707748 100644 --- a/web/sw.js +++ b/web/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'kiss-v18'; +const CACHE = 'kiss-v19'; const ASSETS = [ '/', '/index.html', -- cgit v1.2.3