mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 14:55:43 +00:00
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:
@@ -357,6 +357,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
* On Windows, the "update available" notice now waits until a release is a couple of days old, so it no longer points to a version that WinGet has not finished publishing yet. macOS and Linux are unaffected.
|
||||
|
||||
### Playlist no longer reloads when you press Play
|
||||
|
||||
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1201](https://github.com/Psychotoxical/psysonic/pull/1201)**
|
||||
|
||||
* Pressing Play, Shuffle or Add to queue on a playlist no longer reloads the whole page with a spinner — it just starts playback. Editing the playlist (adding or removing songs) still refreshes the list as before.
|
||||
|
||||
## [1.48.1] - 2026-06-15
|
||||
|
||||
## Fixed
|
||||
|
||||
@@ -6,7 +6,6 @@ export interface PlaylistBulkPlayCallbacksDeps {
|
||||
songsLength: number;
|
||||
id: string | undefined;
|
||||
tracks: Track[];
|
||||
touchPlaylist: (id: string) => void;
|
||||
playTrack: (track: Track, queue: Track[]) => void;
|
||||
enqueue: (tracks: Track[]) => void;
|
||||
}
|
||||
@@ -18,21 +17,21 @@ export interface PlaylistBulkPlayCallbacks {
|
||||
}
|
||||
|
||||
export function usePlaylistBulkPlayCallbacks(deps: PlaylistBulkPlayCallbacksDeps): PlaylistBulkPlayCallbacks {
|
||||
const { songsLength, id, tracks, touchPlaylist, playTrack, enqueue } = deps;
|
||||
const { songsLength, id, tracks, playTrack, enqueue } = deps;
|
||||
|
||||
const handlePlayAll = useCallback(
|
||||
() => playPlaylistAll({ songsLength, id, tracks, touchPlaylist, playTrack, enqueue }),
|
||||
[songsLength, id, tracks, touchPlaylist, playTrack, enqueue],
|
||||
() => playPlaylistAll({ songsLength, id, tracks, playTrack, enqueue }),
|
||||
[songsLength, id, tracks, playTrack, enqueue],
|
||||
);
|
||||
|
||||
const handleShuffleAll = useCallback(
|
||||
() => shufflePlaylistAll({ songsLength, id, tracks, touchPlaylist, playTrack, enqueue }),
|
||||
[songsLength, id, tracks, touchPlaylist, playTrack, enqueue],
|
||||
() => shufflePlaylistAll({ songsLength, id, tracks, playTrack, enqueue }),
|
||||
[songsLength, id, tracks, playTrack, enqueue],
|
||||
);
|
||||
|
||||
const handleEnqueueAll = useCallback(
|
||||
() => enqueuePlaylistAll({ songsLength, id, tracks, touchPlaylist, playTrack, enqueue }),
|
||||
[songsLength, id, tracks, touchPlaylist, playTrack, enqueue],
|
||||
() => enqueuePlaylistAll({ songsLength, id, tracks, playTrack, enqueue }),
|
||||
[songsLength, id, tracks, playTrack, enqueue],
|
||||
);
|
||||
|
||||
return { handlePlayAll, handleShuffleAll, handleEnqueueAll };
|
||||
|
||||
@@ -223,7 +223,7 @@ export default function PlaylistDetail() {
|
||||
|
||||
// ── Playback actions (encapsulated like AlbumHeader) ─────────
|
||||
const { handlePlayAll, handleShuffleAll, handleEnqueueAll } = usePlaylistBulkPlayCallbacks({
|
||||
songsLength: songs.length, id, tracks, touchPlaylist, playTrack, enqueue,
|
||||
songsLength: songs.length, id, tracks, playTrack, enqueue,
|
||||
});
|
||||
|
||||
// ── Render ────────────────────────────────────────────────────
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user