mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 06:25:41 +00:00
4c70408bd6
* 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)
86 lines
2.6 KiB
TypeScript
86 lines
2.6 KiB
TypeScript
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;
|
|
}
|