summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-09 19:58:44 +0300
committerPaul Buetow <paul@buetow.org>2026-05-09 19:58:44 +0300
commit868b7fb10c912ea3b7200d1ffa1f287dce335f23 (patch)
treea524d91e484bf4c814a269815594b3ebae66bed4
parent8c32cd117abe79f4ee6cff3a950ffc35c95faeff (diff)
Fix shuffle hotkey reshuffle behavior (q1)
-rw-r--r--web/js/app.js5
-rw-r--r--web/js/shuffle.js12
-rw-r--r--web/js/tests/shuffle.test.js54
-rw-r--r--web/js/views/media-grid.js4
-rw-r--r--web/sw.js2
5 files changed, 73 insertions, 4 deletions
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',