feat(album): show all genres in album details, linkable to genre pages (#1186)

* feat(album): show all genres in album details, linkable to genre pages

The album header listed only a single genre. It now renders every genre
from the album's OpenSubsonic genres[] (falling back to splitting the
legacy genre string), each linking to its genre page via the existing
genre route.

* docs(changelog): multiple genres in album details (#1186)

* refactor(genres): extract genreColor into a shared util

* feat(album): union album and track genres behind a +N cursor menu

* feat(album): read album detail from the library index when ready

* docs(changelog): expand the album genres entry

* feat(album): genre pill row with keyboard-navigable popover

* test(offline): cover index-first resolveAlbum path with mocked libraryIsReady

* refactor(styles): move album-detail genre styles out of cover-lightbox.css
This commit is contained in:
Psychotoxical
2026-06-25 13:16:24 +02:00
committed by GitHub
parent 4aa6427727
commit 8b89596fcf
22 changed files with 517 additions and 24 deletions
+10 -5
View File
@@ -17,6 +17,7 @@ import {
offlineLocalBrowseEnabled,
} from '../utils/offline/offlineLocalBrowse';
import { readDetailServerId } from '../utils/navigation/detailServerScope';
import { libraryIsReady } from '../utils/library/libraryReady';
import {
shouldAttemptSubsonicForActiveServer,
shouldAttemptSubsonicForServer,
@@ -96,8 +97,6 @@ export function useAlbumDetailData(id: string | undefined): UseAlbumDetailDataRe
}
};
const libraryFirst = favoritesOfflineEnabled && !!detailServerId;
void (async () => {
if (offlineBrowseActive && detailServerId) {
const local = await resolveAlbum(detailServerId, id);
@@ -115,7 +114,13 @@ export function useAlbumDetailData(id: string | undefined): UseAlbumDetailDataRe
return;
}
if (libraryFirst && detailServerId) {
// Index-first when the local SQLite index is ready, not only when the
// favorites-offline toggle is on — album detail then opens from SQLite
// (and offline) with the same genres genre browse derives.
const indexReady = !!detailServerId && await libraryIsReady(detailServerId);
const canLoadLocal = (favoritesOfflineEnabled || indexReady) && !!detailServerId;
if (canLoadLocal && detailServerId) {
try {
const local = await resolveAlbum(detailServerId, id);
if (local) {
@@ -131,7 +136,7 @@ export function useAlbumDetailData(id: string | undefined): UseAlbumDetailDataRe
: shouldAttemptSubsonicForActiveServer();
if (!detailNetworkAllowed) {
if (favoritesOfflineEnabled && detailServerId) {
if (canLoadLocal && detailServerId) {
try {
const local = await resolveAlbum(detailServerId, id);
if (local) {
@@ -159,7 +164,7 @@ export function useAlbumDetailData(id: string | undefined): UseAlbumDetailDataRe
applyAlbumPayload(data);
await loadRelatedAlbums(detailServerId, data.album.artistId, false, false);
} catch {
if (favoritesOfflineEnabled && detailServerId) {
if (canLoadLocal && detailServerId) {
try {
const local = await loadAlbumFromLibraryIndex(detailServerId, id);
if (local) {