From 4476d39d39329b8a8c543ba3777f4d293a4e7503 Mon Sep 17 00:00:00 2001 From: Xoconoch Date: Thu, 28 Aug 2025 07:16:05 -0600 Subject: [PATCH] fix: artist frontend rendering --- log.txt | 0 requirements.txt | 2 +- spotizerr-ui/src/routes/artist.tsx | 126 ++++++++++++++++++++-------- spotizerr-ui/src/types/librespot.ts | 5 ++ 4 files changed, 95 insertions(+), 38 deletions(-) create mode 100644 log.txt diff --git a/log.txt b/log.txt new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt index 44feb72..40d5860 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ fastapi==0.116.1 uvicorn[standard]==0.35.0 celery==5.5.3 -deezspot-spotizerr==3.1.0 +deezspot-spotizerr==3.1.2 httpx==0.28.1 bcrypt==4.2.1 PyJWT==2.10.1 diff --git a/spotizerr-ui/src/routes/artist.tsx b/spotizerr-ui/src/routes/artist.tsx index 5fa2ecb..072ab12 100644 --- a/spotizerr-ui/src/routes/artist.tsx +++ b/spotizerr-ui/src/routes/artist.tsx @@ -15,6 +15,7 @@ type ArtistInfoResponse = LibrespotArtistType & { top_track?: Array<{ country: string; track: string[] }>; album_group?: string[]; single_group?: string[]; + compilation_group?: string[]; appears_on_group?: string[]; }; @@ -23,6 +24,7 @@ export const Artist = () => { const [artist, setArtist] = useState(null); const [artistAlbums, setArtistAlbums] = useState([]); const [artistSingles, setArtistSingles] = useState([]); + const [artistCompilations, setArtistCompilations] = useState([]); const [artistAppearsOn, setArtistAppearsOn] = useState([]); const [topTracks, setTopTracks] = useState([]); const [bannerUrl, setBannerUrl] = useState(null); @@ -38,6 +40,7 @@ export const Artist = () => { const ALBUM_BATCH = 12; const [albumOffset, setAlbumOffset] = useState(0); const [singleOffset, setSingleOffset] = useState(0); + const [compOffset, setCompOffset] = useState(0); const [appearsOffset, setAppearsOffset] = useState(0); const [loading, setLoading] = useState(false); const [loadingMore, setLoadingMore] = useState(false); @@ -81,9 +84,11 @@ export const Artist = () => { setError(null); setArtistAlbums([]); setArtistSingles([]); + setArtistCompilations([]); setArtistAppearsOn([]); setAlbumOffset(0); setSingleOffset(0); + setCompOffset(0); setAppearsOffset(0); setHasMore(true); setBannerUrl(null); // reset hero; will lazy-load below @@ -123,46 +128,61 @@ export const Artist = () => { if (!cancelled) setTopTracks([]); } - // Progressive album loading: album -> single -> appears_on + // Progressive album loading: album -> single -> compilation -> appears_on const albumIds = data.album_group ?? []; const singleIds = data.single_group ?? []; + const compIds = data.compilation_group ?? []; const appearsIds = data.appears_on_group ?? []; // Determine initial number based on screen size: 4 on small screens const isSmallScreen = typeof window !== "undefined" && !window.matchMedia("(min-width: 640px)").matches; const initialTarget = isSmallScreen ? 4 : ALBUM_BATCH; - // Load initial batch from albumIds, then if needed from singles, then appears - const initialBatch: LibrespotAlbumType[] = []; - let aOff = 0, sOff = 0, apOff = 0; - if (albumIds.length > 0) { - const take = albumIds.slice(0, initialTarget); - initialBatch.push(...await fetchAlbumsByIds(take)); + // Load initial sets from each group in order until initialTarget reached + let aOff = 0, sOff = 0, cOff = 0, apOff = 0; + let loaded = 0; + let aList: LibrespotAlbumType[] = []; + let sList: LibrespotAlbumType[] = []; + let cList: LibrespotAlbumType[] = []; + let apList: LibrespotAlbumType[] = []; + + if (albumIds.length > 0 && loaded < initialTarget) { + const take = albumIds.slice(0, initialTarget - loaded); + aList = await fetchAlbumsByIds(take); aOff = take.length; + loaded += aList.length; } - if (initialBatch.length < initialTarget && singleIds.length > 0) { - const remaining = initialTarget - initialBatch.length; - const take = singleIds.slice(0, remaining); - initialBatch.push(...await fetchAlbumsByIds(take)); + if (singleIds.length > 0 && loaded < initialTarget) { + const take = singleIds.slice(0, initialTarget - loaded); + sList = await fetchAlbumsByIds(take); sOff = take.length; + loaded += sList.length; } - if (initialBatch.length < initialTarget && appearsIds.length > 0) { - const remaining = initialTarget - initialBatch.length; - const take = appearsIds.slice(0, remaining); - initialBatch.push(...await fetchAlbumsByIds(take)); + if (compIds.length > 0 && loaded < initialTarget) { + const take = compIds.slice(0, initialTarget - loaded); + cList = await fetchAlbumsByIds(take); + cOff = take.length; + loaded += cList.length; + } + if (appearsIds.length > 0 && loaded < initialTarget) { + const take = appearsIds.slice(0, initialTarget - loaded); + apList = await fetchAlbumsByIds(take); apOff = take.length; + loaded += apList.length; } if (!cancelled) { - setArtistAlbums(initialBatch.filter(a => a.album_type === "album")); - setArtistSingles(initialBatch.filter(a => a.album_type === "single")); - setArtistAppearsOn([]); // placeholder; appears_on grouping not explicitly typed + setArtistAlbums(aList); + setArtistSingles(sList); + setArtistCompilations(cList); + setArtistAppearsOn(apList); // Store offsets for next loads setAlbumOffset(aOff); setSingleOffset(sOff); + setCompOffset(cOff); setAppearsOffset(apOff); // Determine if more remain - setHasMore((albumIds.length > aOff) || (singleIds.length > sOff) || (appearsIds.length > apOff)); + setHasMore((albumIds.length > aOff) || (singleIds.length > sOff) || (compIds.length > cOff) || (appearsIds.length > apOff)); } } else { setError("Could not load artist data."); @@ -201,34 +221,54 @@ export const Artist = () => { try { const albumIds = artist.album_group ?? []; const singleIds = artist.single_group ?? []; + const compIds = artist.compilation_group ?? []; const appearsIds = artist.appears_on_group ?? []; - const nextBatch: LibrespotAlbumType[] = []; - let aOff = albumOffset, sOff = singleOffset, apOff = appearsOffset; - if (aOff < albumIds.length) { - const take = albumIds.slice(aOff, aOff + ALBUM_BATCH - nextBatch.length); - nextBatch.push(...await fetchAlbumsByIds(take)); + const nextA: LibrespotAlbumType[] = []; + const nextS: LibrespotAlbumType[] = []; + const nextC: LibrespotAlbumType[] = []; + const nextAp: LibrespotAlbumType[] = []; + + let aOff = albumOffset, sOff = singleOffset, cOff = compOffset, apOff = appearsOffset; + + const totalLoaded = () => nextA.length + nextS.length + nextC.length + nextAp.length; + + if (aOff < albumIds.length && totalLoaded() < ALBUM_BATCH) { + const remaining = ALBUM_BATCH - totalLoaded(); + const take = albumIds.slice(aOff, aOff + remaining); + nextA.push(...await fetchAlbumsByIds(take)); aOff += take.length; } - if (nextBatch.length < ALBUM_BATCH && sOff < singleIds.length) { - const remaining = ALBUM_BATCH - nextBatch.length; + if (sOff < singleIds.length && totalLoaded() < ALBUM_BATCH) { + const remaining = ALBUM_BATCH - totalLoaded(); const take = singleIds.slice(sOff, sOff + remaining); - nextBatch.push(...await fetchAlbumsByIds(take)); + nextS.push(...await fetchAlbumsByIds(take)); sOff += take.length; } - if (nextBatch.length < ALBUM_BATCH && apOff < appearsIds.length) { - const remaining = ALBUM_BATCH - nextBatch.length; + if (cOff < compIds.length && totalLoaded() < ALBUM_BATCH) { + const remaining = ALBUM_BATCH - totalLoaded(); + const take = compIds.slice(cOff, cOff + remaining); + nextC.push(...await fetchAlbumsByIds(take)); + cOff += take.length; + } + if (apOff < appearsIds.length && totalLoaded() < ALBUM_BATCH) { + const remaining = ALBUM_BATCH - totalLoaded(); const take = appearsIds.slice(apOff, apOff + remaining); - nextBatch.push(...await fetchAlbumsByIds(take)); + nextAp.push(...await fetchAlbumsByIds(take)); apOff += take.length; } - setArtistAlbums((cur) => cur.concat(nextBatch.filter(a => a.album_type === "album"))); - setArtistSingles((cur) => cur.concat(nextBatch.filter(a => a.album_type === "single"))); - setAppearsOffset(apOff); + setArtistAlbums((cur) => cur.concat(nextA)); + setArtistSingles((cur) => cur.concat(nextS)); + setArtistCompilations((cur) => cur.concat(nextC)); + setArtistAppearsOn((cur) => cur.concat(nextAp)); + setAlbumOffset(aOff); setSingleOffset(sOff); - setHasMore((albumIds.length > aOff) || (singleIds.length > sOff) || (appearsIds.length > apOff)); + setCompOffset(cOff); + setAppearsOffset(apOff); + + setHasMore((albumIds.length > aOff) || (singleIds.length > sOff) || (compIds.length > cOff) || (appearsIds.length > apOff)); } catch (err) { console.error("Failed to load more albums", err); toast.error("Failed to load more albums"); @@ -236,7 +276,7 @@ export const Artist = () => { } finally { setLoadingMore(false); } - }, [artistId, loadingMore, loading, hasMore, artist, albumOffset, singleOffset, appearsOffset, fetchAlbumsByIds]); + }, [artistId, loadingMore, loading, hasMore, artist, albumOffset, singleOffset, compOffset, appearsOffset, fetchAlbumsByIds]); // IntersectionObserver to trigger fetchMoreAlbums when sentinel is visible useEffect(() => { @@ -474,6 +514,18 @@ export const Artist = () => { )} + {/* Compilations */} + {artistCompilations.length > 0 && ( +
+

Compilations

+
+ {artistCompilations.map((album) => ( + handleDownloadAlbum(album)} /> + ))} +
+
+ )} + {/* Appears On */} {artistAppearsOn.length > 0 && (
@@ -494,9 +546,9 @@ export const Artist = () => { {hasMore && !loadingMore && ( )}
diff --git a/spotizerr-ui/src/types/librespot.ts b/spotizerr-ui/src/types/librespot.ts index 66f4c61..a38864f 100644 --- a/spotizerr-ui/src/types/librespot.ts +++ b/spotizerr-ui/src/types/librespot.ts @@ -29,6 +29,11 @@ export interface LibrespotArtistType { popularity?: number; type?: "artist"; uri?: string; + // Album groups: arrays of album IDs + album_group?: string[]; + single_group?: string[]; + compilation_group?: string[]; + appears_on_group?: string[]; } export interface LibrespotCopyright {