mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
ddf10ee01d
* feat(genres): genre detail browse via local index with aligned counts Move genre detail albums/play/shuffle onto the local library index with Albums-style in-page scroll, session restore, and genre-scoped stash. Unify genre album totals between the cloud and detail pages via library_get_genre_album_counts, and fix grouped browse totals to count distinct albums rather than matching tracks. * perf(genres): local genre browse with scoped counts cache Add dedicated Rust genre album pagination and indexes, slice-mode grid loading, library-filter-aware counts, and a long-lived in-memory catalog cache invalidated on sync so genre pages avoid repeated full-library SQL. * fix(genres): restore scroll after album back; play hold-to-shuffle Pin restore display count in refs so clearing the return stash no longer reloads the genre grid mid-restore. Load the first SQL page only (60 rows), use long-press on Play for shuffle, and add genre play tooltips. * fix(genres): fall back to Subsonic byGenre when local index unavailable Genre detail album grid now matches All Albums: try library_list_albums_by_genre first, then getAlbumsByGenre when the index is off, not ready, or errors. * docs: add CHANGELOG and credits for PR #937
80 lines
2.8 KiB
TypeScript
80 lines
2.8 KiB
TypeScript
import { useEffect, useRef, type RefObject } from 'react';
|
|
import { useLocation, useNavigationType } from 'react-router-dom';
|
|
import { GENRE_DETAIL_INPAGE_SCROLL_VIEWPORT_ID, readInpageScrollTop } from '../constants/appScroll';
|
|
import {
|
|
DEFAULT_ALBUM_BROWSE_RETURN_FILTERS,
|
|
albumBrowseSortForServer,
|
|
clearGenreDetailReturnStash,
|
|
genreDetailGenreFromPath,
|
|
isAlbumDetailPath,
|
|
isGenreDetailPath,
|
|
peekGenreDetailScrollRestore,
|
|
stashGenreDetailReturnFilters,
|
|
useAlbumBrowseSessionStore,
|
|
} from '../store/albumBrowseSessionStore';
|
|
import { shouldRestoreAlbumBrowseSession } from '../utils/navigation/albumDetailNavigation';
|
|
import type { AlbumBrowseScrollSnapshot } from './useAlbumBrowseFilters';
|
|
|
|
/** Genre detail: locked genre filter + leave/restore session (same contract as All Albums). */
|
|
export function useGenreDetailBrowse(
|
|
serverId: string,
|
|
genreName: string,
|
|
scrollSnapshotRef?: RefObject<AlbumBrowseScrollSnapshot>,
|
|
) {
|
|
const navigationType = useNavigationType();
|
|
const location = useLocation();
|
|
const sort = useAlbumBrowseSessionStore(s => albumBrowseSortForServer(s.sortByServer, serverId));
|
|
const restoredFromStashRef = useRef(false);
|
|
const restoreKeyRef = useRef('');
|
|
const restoreDisplayCountRef = useRef<number | undefined>(undefined);
|
|
const restoreKey = `${serverId}:${genreName}`;
|
|
if (restoreKeyRef.current !== restoreKey) {
|
|
restoreKeyRef.current = restoreKey;
|
|
restoreDisplayCountRef.current = peekGenreDetailScrollRestore(serverId, genreName)?.displayCount;
|
|
}
|
|
|
|
useEffect(() => {
|
|
restoredFromStashRef.current = false;
|
|
}, [serverId, genreName]);
|
|
|
|
useEffect(() => {
|
|
if (!serverId || !genreName) return;
|
|
|
|
if (shouldRestoreAlbumBrowseSession(navigationType, location.state)) {
|
|
restoredFromStashRef.current = true;
|
|
return;
|
|
}
|
|
|
|
if (restoredFromStashRef.current) return;
|
|
|
|
clearGenreDetailReturnStash(serverId, genreName);
|
|
}, [serverId, genreName, navigationType, location.state]);
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
if (!serverId || !genreName) return;
|
|
const path = window.location.pathname;
|
|
if (isAlbumDetailPath(path)) {
|
|
const snapshot = scrollSnapshotRef?.current;
|
|
const scrollTop = Math.max(
|
|
readInpageScrollTop(GENRE_DETAIL_INPAGE_SCROLL_VIEWPORT_ID),
|
|
snapshot?.scrollTop ?? 0,
|
|
);
|
|
stashGenreDetailReturnFilters(serverId, genreName, {
|
|
...DEFAULT_ALBUM_BROWSE_RETURN_FILTERS,
|
|
selectedGenres: [genreName],
|
|
scrollTop,
|
|
displayCount: snapshot?.displayCount,
|
|
});
|
|
} else if (!isGenreDetailPath(path) || genreDetailGenreFromPath(path) !== genreName) {
|
|
clearGenreDetailReturnStash(serverId, genreName);
|
|
}
|
|
};
|
|
}, [serverId, genreName, scrollSnapshotRef]);
|
|
|
|
return {
|
|
sort,
|
|
restoreDisplayCount: restoreDisplayCountRef.current,
|
|
};
|
|
}
|