mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
45e0e1206f
* fix(playback): pin queue streams, cover art, and library links to queue server When the active server changes while a queue from another server is playing, keep streams and UI on queueServerId; switch back for artist/album links and queue or player-bar context menus. * fix(playback): switch to queue server when opening Now Playing Ensure active server matches queueServerId before Subsonic fetches on the Now Playing page, mobile player route, and queue info panel; scope caches by server id. * docs(credits): mention Now Playing in PR #717 contribution line * fix(playback): route scrobble and queue sync to queue server Address PR review: apiForServer for scrobble/now-playing/savePlayQueue, clear queueServerId on server removal, mini-player queueServerId sync, block cross-server enqueue with toast, and regression tests.
103 lines
2.9 KiB
TypeScript
103 lines
2.9 KiB
TypeScript
import {
|
|
persistQueueVisibility,
|
|
} from './queueVisibilityStorage';
|
|
import type { PlayerState } from './playerStoreTypes';
|
|
import {
|
|
ensurePlaybackServerActive,
|
|
playbackServerDiffersFromActive,
|
|
} from '../utils/playback/playbackServer';
|
|
|
|
type SetState = (
|
|
partial: Partial<PlayerState> | ((state: PlayerState) => Partial<PlayerState>),
|
|
) => void;
|
|
|
|
/**
|
|
* Pure-UI state setters: no audio engine / network side effects.
|
|
* Add new actions here only if they fit that contract.
|
|
*/
|
|
export function createUiStateActions(set: SetState): Pick<
|
|
PlayerState,
|
|
| 'setStarredOverride'
|
|
| 'setUserRatingOverride'
|
|
| 'openContextMenu'
|
|
| 'closeContextMenu'
|
|
| 'openSongInfo'
|
|
| 'closeSongInfo'
|
|
| 'toggleQueue'
|
|
| 'setQueueVisible'
|
|
| 'toggleFullscreen'
|
|
| 'toggleRepeat'
|
|
> {
|
|
return {
|
|
setStarredOverride: (id, starred) =>
|
|
set(s => ({ starredOverrides: { ...s.starredOverrides, [id]: starred } })),
|
|
|
|
setUserRatingOverride: (id, rating) =>
|
|
set(s => {
|
|
const nextOverrides = { ...s.userRatingOverrides };
|
|
if (rating === 0) delete nextOverrides[id];
|
|
else nextOverrides[id] = rating;
|
|
return {
|
|
userRatingOverrides: nextOverrides,
|
|
queue: s.queue.map(t => (t.id === id ? { ...t, userRating: rating } : t)),
|
|
currentTrack:
|
|
s.currentTrack?.id === id ? { ...s.currentTrack, userRating: rating } : s.currentTrack,
|
|
};
|
|
}),
|
|
|
|
openContextMenu: (x, y, item, type, queueIndex, playlistId, playlistSongIndex, shareKindOverride, pinToPlaybackServer) => {
|
|
const pin = pinToPlaybackServer ?? type === 'queue-item';
|
|
const open = () =>
|
|
set({
|
|
contextMenu: {
|
|
isOpen: true,
|
|
x,
|
|
y,
|
|
item,
|
|
type,
|
|
queueIndex,
|
|
playlistId,
|
|
playlistSongIndex,
|
|
shareKindOverride,
|
|
pinToPlaybackServer: pin,
|
|
},
|
|
});
|
|
if (pin && playbackServerDiffersFromActive()) {
|
|
void ensurePlaybackServerActive().then(ok => {
|
|
if (ok) open();
|
|
});
|
|
return;
|
|
}
|
|
open();
|
|
},
|
|
|
|
closeContextMenu: () =>
|
|
set(state => ({
|
|
contextMenu: { ...state.contextMenu, isOpen: false },
|
|
})),
|
|
|
|
openSongInfo: (songId) => set({ songInfoModal: { isOpen: true, songId } }),
|
|
closeSongInfo: () => set({ songInfoModal: { isOpen: false, songId: null } }),
|
|
|
|
toggleQueue: () =>
|
|
set(state => {
|
|
const next = !state.isQueueVisible;
|
|
persistQueueVisibility(next);
|
|
return { isQueueVisible: next };
|
|
}),
|
|
|
|
setQueueVisible: (v: boolean) => {
|
|
persistQueueVisibility(v);
|
|
set({ isQueueVisible: v });
|
|
},
|
|
|
|
toggleFullscreen: () => set(state => ({ isFullscreenOpen: !state.isFullscreenOpen })),
|
|
|
|
toggleRepeat: () =>
|
|
set(state => {
|
|
const modes = ['off', 'all', 'one'] as const;
|
|
return { repeatMode: modes[(modes.indexOf(state.repeatMode) + 1) % modes.length] };
|
|
}),
|
|
};
|
|
}
|