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:
cucadmuh
2026-06-01 04:20:18 +03:00
committed by GitHub
parent d3e5a6b704
commit ddf10ee01d
42 changed files with 1952 additions and 139 deletions
+1
View File
@@ -9,6 +9,7 @@ export const genres = {
albumsEmpty: 'Keine Alben in diesem Genre gefunden.',
loadMore: 'Mehr laden',
back: 'Zurück',
playTooltip: 'Abspielen (halten zum Mischen)',
shuffle: 'Zufallswiedergabe',
addToQueue: 'Zur Warteschlange',
};
+1
View File
@@ -9,6 +9,7 @@ export const genres = {
albumsEmpty: 'No albums found for this genre.',
loadMore: 'Load more',
back: 'Back',
playTooltip: 'Play (hold to shuffle)',
shuffle: 'Shuffle',
addToQueue: 'Add to queue',
};
+1
View File
@@ -9,6 +9,7 @@ export const genres = {
albumsEmpty: 'No se encontraron álbumes para este género.',
loadMore: 'Cargar más',
back: 'Volver',
playTooltip: 'Reproducir (mantener para aleatorio)',
shuffle: 'Aleatorio',
addToQueue: 'Agregar a la cola',
};
+1
View File
@@ -9,6 +9,7 @@ export const genres = {
albumsEmpty: 'Aucun album trouvé pour ce genre.',
loadMore: 'Charger plus',
back: 'Retour',
playTooltip: 'Lire (maintenir pour mélanger)',
shuffle: 'Aléatoire',
addToQueue: 'Ajouter à la file',
};
+1
View File
@@ -9,6 +9,7 @@ export const genres = {
albumsEmpty: 'Ingen album funnet for denne sjangeren.',
loadMore: 'Last mer',
back: 'Tilbake',
playTooltip: 'Spill av (hold for tilfeldig rekkefølge)',
shuffle: 'Bland',
addToQueue: 'Legg til i kø',
};
+1
View File
@@ -9,6 +9,7 @@ export const genres = {
albumsEmpty: 'Geen albums gevonden voor dit genre.',
loadMore: 'Meer laden',
back: 'Terug',
playTooltip: 'Afspelen (ingedrukt houden om te shufflen)',
shuffle: 'Willekeurig',
addToQueue: 'Aan wachtrij toevoegen',
};
+1
View File
@@ -9,6 +9,7 @@ export const genres = {
albumsEmpty: 'Niciun album găsit pentru acest gen.',
loadMore: 'Încarcă mai mult',
back: 'Înapoi',
playTooltip: 'Redă (ține apăsat pentru amestecare)',
shuffle: 'Amestecă',
addToQueue: 'Adaugă la coadă',
};
+1
View File
@@ -11,6 +11,7 @@ export const genres = {
albumsEmpty: 'В этом жанре альбомов нет.',
loadMore: 'Ещё',
back: 'Назад',
playTooltip: 'Воспроизвести (удерживать для перемешивания)',
shuffle: 'Перемешать',
addToQueue: 'В очередь',
};
+1
View File
@@ -9,6 +9,7 @@ export const genres = {
albumsEmpty: '未找到该流派的专辑。',
loadMore: '加载更多',
back: '返回',
playTooltip: '播放(长按随机播放)',
shuffle: '随机播放',
addToQueue: '添加到队列',
};