refactor(artist-detail): G.84 — extract helpers + suggestion cover + 2 data hooks + play utilities (cluster) (#651)

Five-cut cluster opening the ArtistDetail refactor. 944 → 631 LOC
(−313).

artistDetailHelpers — formatDuration (M:SS) + sanitizeHtml (strip
script/style/iframe/etc tags + onXxx + javascript: / data: hrefs).

ArtistSuggestionTrackCover — tiny CachedImage wrapper for the
32×32 cover thumbnail in the suggestions tracklist.

useArtistDetailData — owns the page's three primary fetch effects:
getArtist + getTopSongs on artist id change, getArtistInfo on id +
audiomuseNavidromeEnabled change, and the background "Also Featured
On" search that derives albums from search results not in the
artist's own album set. Exposes artist / setArtist / albums /
topSongs / info / featuredAlbums / loading flags + isStarred for
the star button.

useArtistSimilarArtists — owns the two parallel similar-artist
effects (Last.fm primary path when AudioMuse is off; Last.fm
fallback when AudioMuse is on but returned nothing) plus the
audiomuse-positive-result reset. Returns { similarArtists,
similarLoading }.

runArtistDetailPlay — three play orchestrators that share the
fetchAllTracks helper: runArtistDetailPlayAll, runArtistDetailShuffle,
runArtistDetailStartRadio (with the no-radio fallback alert). All
take deps objects so the page just delegates state setters.

ArtistDetail drops the now-unused direct search / getArtistInfo /
getTopSongs / getSimilarSongs2 / getArtist / lastfmGetSimilarArtists
imports. Pure code move otherwise.
This commit is contained in:
Frank Stellmacher
2026-05-13 17:07:53 +02:00
committed by GitHub
parent c207f748da
commit ba0bf8aa9d
6 changed files with 374 additions and 259 deletions
@@ -0,0 +1,17 @@
import React, { useMemo } from 'react';
import { buildCoverArtUrl, coverArtCacheKey } from '../../api/subsonicStreamUrl';
import CachedImage from '../CachedImage';
export default function ArtistSuggestionTrackCover({ coverArt, album }: { coverArt: string; album: string }) {
const src = useMemo(() => buildCoverArtUrl(coverArt, 64), [coverArt]);
const cacheKey = useMemo(() => coverArtCacheKey(coverArt, 64), [coverArt]);
return (
<CachedImage
src={src}
cacheKey={cacheKey}
alt={album}
style={{ width: '32px', height: '32px', borderRadius: '4px', objectFit: 'cover', flexShrink: 0 }}
onError={(e) => { (e.currentTarget as HTMLImageElement).style.display = 'none'; }}
/>
);
}