fix(playlist): stop the detail page reloading on Play/Shuffle/Enqueue (#1201)

* fix(playlist): stop the detail page reloading on Play/Shuffle/Enqueue

* docs(changelog): playlist play no longer reloads the page
This commit is contained in:
Psychotoxical
2026-06-27 15:24:28 +02:00
committed by GitHub
parent 7e6a2100e5
commit 6d4d82d6a3
6 changed files with 71 additions and 19 deletions
+1 -3
View File
@@ -1,7 +1,6 @@
import { getPlaylist } from '../../api/subsonicPlaylists';
import { songToTrack } from '../playback/songToTrack';
import { usePlayerStore } from '../../store/playerStore';
import { usePlaylistStore } from '../../store/playlistStore';
import { playPlaylistAll } from './playlistBulkPlayActions';
/**
@@ -16,6 +15,5 @@ export async function playPlaylistById(id: string): Promise<void> {
const { songs } = await getPlaylist(id);
const tracks = songs.map(songToTrack);
const { playTrack, enqueue } = usePlayerStore.getState();
const { touchPlaylist } = usePlaylistStore.getState();
playPlaylistAll({ songsLength: tracks.length, id, tracks, touchPlaylist, playTrack, enqueue });
playPlaylistAll({ songsLength: tracks.length, id, tracks, playTrack, enqueue });
}
@@ -0,0 +1,49 @@
import { describe, expect, it, vi } from 'vitest';
import { enqueuePlaylistAll, playPlaylistAll, shufflePlaylistAll } from './playlistBulkPlayActions';
import type { Track } from '../../store/playerStoreTypes';
// Only id/queue identity matters for these actions.
const tracks = [{ id: 'a' }, { id: 'b' }, { id: 'c' }] as unknown as Track[];
describe('playlistBulkPlayActions', () => {
it('playPlaylistAll starts the first track with the full queue', () => {
const playTrack = vi.fn();
const enqueue = vi.fn();
playPlaylistAll({ songsLength: tracks.length, id: 'p1', tracks, playTrack, enqueue });
expect(playTrack).toHaveBeenCalledWith(tracks[0], tracks);
expect(enqueue).not.toHaveBeenCalled();
});
it('enqueuePlaylistAll appends every track without starting playback', () => {
const playTrack = vi.fn();
const enqueue = vi.fn();
enqueuePlaylistAll({ songsLength: tracks.length, id: 'p1', tracks, playTrack, enqueue });
expect(enqueue).toHaveBeenCalledWith(tracks);
expect(playTrack).not.toHaveBeenCalled();
});
it('shufflePlaylistAll plays a track from the playlist with the full queue', () => {
const playTrack = vi.fn();
shufflePlaylistAll({ songsLength: tracks.length, id: 'p1', tracks, playTrack, enqueue: vi.fn() });
expect(playTrack).toHaveBeenCalledTimes(1);
const [first, queue] = playTrack.mock.calls[0];
expect(tracks).toContain(first);
expect(queue).toHaveLength(tracks.length);
});
it('no-ops on an empty playlist', () => {
const playTrack = vi.fn();
const enqueue = vi.fn();
playPlaylistAll({ songsLength: 0, id: 'p1', tracks: [], playTrack, enqueue });
shufflePlaylistAll({ songsLength: 0, id: 'p1', tracks: [], playTrack, enqueue });
enqueuePlaylistAll({ songsLength: 0, id: 'p1', tracks: [], playTrack, enqueue });
expect(playTrack).not.toHaveBeenCalled();
expect(enqueue).not.toHaveBeenCalled();
});
it('no-ops without a playlist id', () => {
const playTrack = vi.fn();
playPlaylistAll({ songsLength: tracks.length, id: undefined, tracks, playTrack, enqueue: vi.fn() });
expect(playTrack).not.toHaveBeenCalled();
});
});
@@ -1,25 +1,26 @@
import type { Track } from '../../store/playerStoreTypes';
// No `touchPlaylist` here: playing/shuffling/enqueuing does not modify the
// playlist. Touching it bumps `lastModified`, which is the playlist detail
// page's load-effect trigger, so it would re-fetch and flash the whole
// container on every Play click. Real mutations (add/remove/save) still touch.
export interface BulkPlayDeps {
songsLength: number;
id: string | undefined;
tracks: Track[];
touchPlaylist: (id: string) => void;
playTrack: (track: Track, queue: Track[]) => void;
enqueue: (tracks: Track[]) => void;
}
export function playPlaylistAll(deps: BulkPlayDeps): void {
const { songsLength, id, tracks, touchPlaylist, playTrack } = deps;
const { songsLength, id, tracks, playTrack } = deps;
if (!songsLength || !id) return;
touchPlaylist(id);
playTrack(tracks[0], tracks);
}
export function shufflePlaylistAll(deps: BulkPlayDeps): void {
const { songsLength, id, tracks, touchPlaylist, playTrack } = deps;
const { songsLength, id, tracks, playTrack } = deps;
if (!songsLength || !id) return;
touchPlaylist(id);
const shuffled = [...tracks];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
@@ -29,8 +30,7 @@ export function shufflePlaylistAll(deps: BulkPlayDeps): void {
}
export function enqueuePlaylistAll(deps: BulkPlayDeps): void {
const { songsLength, id, tracks, touchPlaylist, enqueue } = deps;
const { songsLength, id, tracks, enqueue } = deps;
if (!songsLength || !id) return;
touchPlaylist(id);
enqueue(tracks);
}