Files
Psychotoxical-psysonic/src/features/artist/store/artistBrowseSessionStore.ts
T
Psychotoxical 17ea96dcdb test(offline): mock useOfflineBrowseReloadToken submodule in useSongBrowseList
The offline move collapsed a single-module mock into a `@/features/offline`
barrel mock, which shadowed the real `useOfflineBrowseContext` the test relied
on. Mock the reload-token submodule directly so the barrel re-exports the stub
while the sibling context hook stays live.
2026-06-30 01:26:46 +02:00

92 lines
3.0 KiB
TypeScript

import { create } from 'zustand';
import { ALL_SENTINEL } from '../utils/componentHelpers/artistsHelpers';
export type ArtistBrowseViewMode = 'grid' | 'list';
/** Browse state restored when returning to Artists via back from artist detail. */
export interface ArtistBrowseReturnState {
filter: string;
letterFilter: string;
starredOnly: boolean;
viewMode: ArtistBrowseViewMode;
showArtistImages: boolean;
scrollTop?: number;
visibleCount?: number;
}
export const DEFAULT_ARTIST_BROWSE_RETURN_STATE: ArtistBrowseReturnState = {
filter: '',
letterFilter: ALL_SENTINEL,
starredOnly: false,
viewMode: 'grid',
showArtistImages: true,
};
interface ArtistBrowseSessionStore {
returnStashByServer: Record<string, ArtistBrowseReturnState>;
stashReturnState: (serverId: string, state: ArtistBrowseReturnState) => void;
clearReturnStash: (serverId: string) => void;
peekReturnStash: (serverId: string) => ArtistBrowseReturnState | null;
}
export const useArtistBrowseSessionStore = create<ArtistBrowseSessionStore>((set, get) => ({
returnStashByServer: {},
stashReturnState: (serverId, state) => {
if (!serverId) return;
set((s) => ({
returnStashByServer: {
...s.returnStashByServer,
[serverId]: {
filter: state.filter,
letterFilter: state.letterFilter,
starredOnly: state.starredOnly,
viewMode: state.viewMode,
showArtistImages: state.showArtistImages,
...(typeof state.scrollTop === 'number' ? { scrollTop: state.scrollTop } : {}),
...(typeof state.visibleCount === 'number' ? { visibleCount: state.visibleCount } : {}),
},
},
}));
},
clearReturnStash: (serverId) => {
if (!serverId) return;
const next = { ...get().returnStashByServer };
delete next[serverId];
set({ returnStashByServer: next });
},
peekReturnStash: (serverId) => {
if (!serverId) return null;
const stash = get().returnStashByServer[serverId];
if (!stash) return null;
return {
filter: stash.filter,
letterFilter: stash.letterFilter,
starredOnly: stash.starredOnly,
viewMode: stash.viewMode,
showArtistImages: stash.showArtistImages,
...(typeof stash.scrollTop === 'number' ? { scrollTop: stash.scrollTop } : {}),
...(typeof stash.visibleCount === 'number' ? { visibleCount: stash.visibleCount } : {}),
};
},
}));
export function peekArtistBrowseScrollRestore(
serverId: string,
): { scrollTop: number; visibleCount: number } | null {
const stash = useArtistBrowseSessionStore.getState().peekReturnStash(serverId);
if (!stash) return null;
if (typeof stash.scrollTop !== 'number' || typeof stash.visibleCount !== 'number') return null;
return {
scrollTop: Math.max(0, stash.scrollTop),
visibleCount: Math.max(0, stash.visibleCount),
};
}
/** True when pathname is the Artists browse route (`/artists`). */
export function isArtistsBrowsePath(pathname: string): boolean {
return pathname === '/artists';
}