mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
fix(now-playing): split artist links and About the Artist tabs (#960)
* fix(now-playing): split artist links and About the Artist tabs OpenSubsonic artists[] now drives per-artist navigation on Now Playing hero and the queue current-track row (matching player bar). About the Artist loads bio for each performer via tabs when a track has multiple artist ids; queue Info uses the primary ref for bio/tour fetch. Reported by zunoz on the Psysonic Discord (v1.47.0-rc.3). * docs: note Now Playing multi-artist fix in CHANGELOG (PR #960)
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { getArtistInfoForServer } from '../api/subsonicArtists';
|
||||
import type { SubsonicArtistInfo, SubsonicOpenArtistRef } from '../api/subsonicTypes';
|
||||
import { makeCache } from '../utils/cache/nowPlayingCache';
|
||||
|
||||
const artistInfoCache = makeCache<SubsonicArtistInfo | null>();
|
||||
|
||||
function cacheKey(serverId: string, artistId: string): string {
|
||||
return `${serverId}:${artistId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches `getArtistInfo` for each ref with an id. Returns `undefined` for ids
|
||||
* still loading, `null` when fetch finished with no info.
|
||||
*/
|
||||
export function useArtistInfoBatch(
|
||||
serverId: string | undefined,
|
||||
refs: SubsonicOpenArtistRef[],
|
||||
similarArtistCount?: number,
|
||||
): Record<string, SubsonicArtistInfo | null | undefined> {
|
||||
const ids = useMemo(
|
||||
() => [...new Set(refs.map(r => r.id?.trim()).filter((id): id is string => Boolean(id)))],
|
||||
[refs],
|
||||
);
|
||||
const idsKey = ids.join('\x1e');
|
||||
|
||||
const [byId, setById] = useState<Record<string, SubsonicArtistInfo | null | undefined>>(() => {
|
||||
if (!serverId || ids.length === 0) return {};
|
||||
const seed: Record<string, SubsonicArtistInfo | null | undefined> = {};
|
||||
for (const id of ids) {
|
||||
const cached = artistInfoCache.get(cacheKey(serverId, id));
|
||||
if (cached !== undefined) seed[id] = cached;
|
||||
}
|
||||
return seed;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!serverId || ids.length === 0) {
|
||||
setById({});
|
||||
return;
|
||||
}
|
||||
|
||||
const next: Record<string, SubsonicArtistInfo | null | undefined> = {};
|
||||
const pending: string[] = [];
|
||||
for (const id of ids) {
|
||||
const cached = artistInfoCache.get(cacheKey(serverId, id));
|
||||
if (cached !== undefined) {
|
||||
next[id] = cached;
|
||||
} else {
|
||||
next[id] = undefined;
|
||||
pending.push(id);
|
||||
}
|
||||
}
|
||||
setById(next);
|
||||
|
||||
if (pending.length === 0) return;
|
||||
|
||||
let cancelled = false;
|
||||
void Promise.all(
|
||||
pending.map(async id => {
|
||||
try {
|
||||
const info = await getArtistInfoForServer(serverId, id, {
|
||||
similarArtistCount: similarArtistCount,
|
||||
});
|
||||
artistInfoCache.set(cacheKey(serverId, id), info ?? null);
|
||||
return [id, info ?? null] as const;
|
||||
} catch {
|
||||
artistInfoCache.set(cacheKey(serverId, id), null);
|
||||
return [id, null] as const;
|
||||
}
|
||||
}),
|
||||
).then(results => {
|
||||
if (cancelled) return;
|
||||
setById(prev => {
|
||||
const merged = { ...prev };
|
||||
for (const [id, info] of results) merged[id] = info;
|
||||
return merged;
|
||||
});
|
||||
});
|
||||
|
||||
return () => { cancelled = true; };
|
||||
}, [serverId, idsKey, similarArtistCount]);
|
||||
|
||||
return byId;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import { prewarmNowPlayingFetchers } from './useNowPlayingFetchers';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { usePlaybackServerId } from './usePlaybackServerId';
|
||||
import { primaryTrackArtistRef } from '../utils/playback/trackArtistRefs';
|
||||
|
||||
const NOW_PLAYING_COVER_CSS_PX = 800;
|
||||
|
||||
@@ -52,11 +53,12 @@ export function useNowPlayingPrewarm(): void {
|
||||
useEffect(() => {
|
||||
if (!currentTrack || !playbackServerId) return;
|
||||
|
||||
const primary = primaryTrackArtistRef(currentTrack);
|
||||
void prewarmNowPlayingFetchers({
|
||||
songId: currentTrack.id,
|
||||
artistId: currentTrack.artistId,
|
||||
artistId: primary.id,
|
||||
albumId: currentTrack.albumId,
|
||||
artistName: currentTrack.artist,
|
||||
artistName: primary.name ?? currentTrack.artist,
|
||||
enableBandsintown,
|
||||
audiomuseNavidromeEnabled,
|
||||
lastfmUsername,
|
||||
@@ -81,6 +83,7 @@ export function useNowPlayingPrewarm(): void {
|
||||
}, [
|
||||
currentTrack?.id,
|
||||
currentTrack?.artistId,
|
||||
currentTrack?.artists,
|
||||
currentTrack?.albumId,
|
||||
currentTrack?.coverArt,
|
||||
currentTrack?.artist,
|
||||
|
||||
Reference in New Issue
Block a user