mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +00:00
feat(genres): local index genre browse with Subsonic fallback (#937)
* 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
This commit is contained in:
@@ -706,6 +706,12 @@ export type CatalogYearBounds = {
|
||||
maxYear: number | null;
|
||||
};
|
||||
|
||||
export type GenreAlbumCountRow = {
|
||||
value: string;
|
||||
albumCount: number;
|
||||
songCount: number;
|
||||
};
|
||||
|
||||
export function libraryGetCatalogYearBounds(args: { serverId: string }): Promise<CatalogYearBounds> {
|
||||
const indexKey = serverIndexKeyForId(args.serverId);
|
||||
return invoke<CatalogYearBounds>('library_get_catalog_year_bounds', {
|
||||
@@ -713,6 +719,58 @@ export function libraryGetCatalogYearBounds(args: { serverId: string }): Promise
|
||||
});
|
||||
}
|
||||
|
||||
export function libraryGetGenreAlbumCounts(args: {
|
||||
serverId: string;
|
||||
libraryScope?: string;
|
||||
}): Promise<GenreAlbumCountRow[]> {
|
||||
const indexKey = serverIndexKeyForId(args.serverId);
|
||||
return invoke<GenreAlbumCountRow[]>('library_get_genre_album_counts', {
|
||||
serverId: indexKey,
|
||||
libraryScope: args.libraryScope,
|
||||
});
|
||||
}
|
||||
|
||||
export type LibraryGenreAlbumsRequest = {
|
||||
serverId: string;
|
||||
genre: string;
|
||||
libraryScope?: string | null;
|
||||
sort?: LibrarySortClause[];
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
includeTotal?: boolean;
|
||||
};
|
||||
|
||||
export type LibraryGenreAlbumsResponse = {
|
||||
albums: LibraryAlbumDto[];
|
||||
hasMore: boolean;
|
||||
total?: number | null;
|
||||
source: 'local';
|
||||
};
|
||||
|
||||
/** Paginated albums for one genre from the local track index. */
|
||||
export function libraryListAlbumsByGenre(
|
||||
request: LibraryGenreAlbumsRequest,
|
||||
): Promise<LibraryGenreAlbumsResponse> {
|
||||
const indexKey = serverIndexKeyForId(request.serverId);
|
||||
return invoke<LibraryGenreAlbumsResponse>('library_list_albums_by_genre', {
|
||||
request: {
|
||||
serverId: indexKey,
|
||||
genre: request.genre,
|
||||
libraryScope: request.libraryScope ?? undefined,
|
||||
sort: request.sort ?? [],
|
||||
limit: request.limit ?? 50,
|
||||
offset: request.offset ?? 0,
|
||||
includeTotal: request.includeTotal ?? false,
|
||||
},
|
||||
}).then(response => ({
|
||||
...response,
|
||||
albums: response.albums.map(album => ({
|
||||
...album,
|
||||
serverId: mapServerIdFromIndexKey(album.serverId, request.serverId),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
export type PlaySessionRecentDay = {
|
||||
date: string;
|
||||
totalListenedSec: number;
|
||||
|
||||
Reference in New Issue
Block a user