fix: load playlist image on frontend
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
fastapi==0.116.1
|
fastapi==0.116.1
|
||||||
uvicorn[standard]==0.35.0
|
uvicorn[standard]==0.35.0
|
||||||
celery==5.5.3
|
celery==5.5.3
|
||||||
deezspot-spotizerr==3.1.4
|
deezspot-spotizerr==3.1.5
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
bcrypt==4.2.1
|
bcrypt==4.2.1
|
||||||
PyJWT==2.10.1
|
PyJWT==2.10.1
|
||||||
|
|||||||
@@ -135,6 +135,16 @@ export const Album = () => {
|
|||||||
};
|
};
|
||||||
}, [loadMore]);
|
}, [loadMore]);
|
||||||
|
|
||||||
|
// Auto progressive loading regardless of scroll
|
||||||
|
useEffect(() => {
|
||||||
|
if (!album) return;
|
||||||
|
if (!hasMore || isLoadingMore) return;
|
||||||
|
const t = setTimeout(() => {
|
||||||
|
loadMore();
|
||||||
|
}, 300);
|
||||||
|
return () => clearTimeout(t);
|
||||||
|
}, [album, hasMore, isLoadingMore, loadMore]);
|
||||||
|
|
||||||
const handleDownloadTrack = (track: LibrespotTrackType) => {
|
const handleDownloadTrack = (track: LibrespotTrackType) => {
|
||||||
if (!track.id) return;
|
if (!track.id) return;
|
||||||
toast.info(`Adding ${track.name} to queue...`);
|
toast.info(`Adding ${track.name} to queue...`);
|
||||||
|
|||||||
@@ -303,6 +303,16 @@ export const Artist = () => {
|
|||||||
return () => observer.disconnect();
|
return () => observer.disconnect();
|
||||||
}, [fetchMoreAlbums, hasMore]);
|
}, [fetchMoreAlbums, hasMore]);
|
||||||
|
|
||||||
|
// Auto progressive loading regardless of scroll
|
||||||
|
useEffect(() => {
|
||||||
|
if (!artist) return;
|
||||||
|
if (!hasMore || loading || loadingMore) return;
|
||||||
|
const t = setTimeout(() => {
|
||||||
|
fetchMoreAlbums();
|
||||||
|
}, 350);
|
||||||
|
return () => clearTimeout(t);
|
||||||
|
}, [artist, hasMore, loading, loadingMore, fetchMoreAlbums]);
|
||||||
|
|
||||||
// --- existing handlers (unchanged) ---
|
// --- existing handlers (unchanged) ---
|
||||||
const handleDownloadTrack = (track: LibrespotTrackType) => {
|
const handleDownloadTrack = (track: LibrespotTrackType) => {
|
||||||
if (!track.id) return;
|
if (!track.id) return;
|
||||||
|
|||||||
@@ -153,6 +153,16 @@ export const Playlist = () => {
|
|||||||
}
|
}
|
||||||
}, [playlistMetadata, items.length, totalTracks, loadMoreTracks]);
|
}, [playlistMetadata, items.length, totalTracks, loadMoreTracks]);
|
||||||
|
|
||||||
|
// Auto progressive loading regardless of scroll
|
||||||
|
useEffect(() => {
|
||||||
|
if (!playlistMetadata) return;
|
||||||
|
if (!hasMoreTracks || loadingTracks) return;
|
||||||
|
const t = setTimeout(() => {
|
||||||
|
loadMoreTracks();
|
||||||
|
}, 300);
|
||||||
|
return () => clearTimeout(t);
|
||||||
|
}, [playlistMetadata, hasMoreTracks, loadingTracks, loadMoreTracks]);
|
||||||
|
|
||||||
const handleDownloadTrack = (track: LibrespotTrackType) => {
|
const handleDownloadTrack = (track: LibrespotTrackType) => {
|
||||||
if (!track?.id) return;
|
if (!track?.id) return;
|
||||||
addItem({ spotifyId: track.id, type: "track", name: track.name });
|
addItem({ spotifyId: track.id, type: "track", name: track.name });
|
||||||
@@ -227,11 +237,40 @@ export const Playlist = () => {
|
|||||||
{/* Playlist Header - Mobile Optimized */}
|
{/* Playlist Header - Mobile Optimized */}
|
||||||
<div className="bg-surface dark:bg-surface-dark border border-border dark:border-border-dark rounded-xl p-4 md:p-6 shadow-sm">
|
<div className="bg-surface dark:bg-surface-dark border border-border dark:border-border-dark rounded-xl p-4 md:p-6 shadow-sm">
|
||||||
<div className="flex flex-col items-center gap-4 md:gap-6">
|
<div className="flex flex-col items-center gap-4 md:gap-6">
|
||||||
<img
|
{playlistMetadata.picture ? (
|
||||||
src={playlistMetadata.images?.at(0)?.url || "/placeholder.jpg"}
|
<img
|
||||||
alt={playlistMetadata.name}
|
src={playlistMetadata.picture}
|
||||||
className="w-32 h-32 sm:w-40 sm:h-40 md:w-48 md:h-48 object-cover rounded-lg shadow-lg mx-auto"
|
alt={playlistMetadata.name}
|
||||||
/>
|
className="w-32 h-32 sm:w-40 sm:h-40 md:w-48 md:h-48 object-cover rounded-lg shadow-lg mx-auto"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="w-32 h-32 sm:w-40 sm:h-40 md:w-48 md:h-48 rounded-lg shadow-lg mx-auto overflow-hidden bg-surface-muted dark:bg-surface-muted-dark grid grid-cols-2 grid-rows-2"
|
||||||
|
>
|
||||||
|
{(Array.from(
|
||||||
|
new Map(
|
||||||
|
filteredItems
|
||||||
|
.map(({ track }) => (track as any)?.album?.images?.at(-1)?.url)
|
||||||
|
.filter((u) => !!u)
|
||||||
|
.map((u) => [u, u] as const)
|
||||||
|
).values()
|
||||||
|
) as string[]).slice(0, 4).map((url, i) => (
|
||||||
|
<img
|
||||||
|
key={`${url}-${i}`}
|
||||||
|
src={url}
|
||||||
|
alt={`Cover ${i + 1}`}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{filteredItems.length === 0 && (
|
||||||
|
<img
|
||||||
|
src="/placeholder.jpg"
|
||||||
|
alt={playlistMetadata.name}
|
||||||
|
className="col-span-2 row-span-2 w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex-grow space-y-2 text-center">
|
<div className="flex-grow space-y-2 text-center">
|
||||||
<h1 className="text-2xl md:text-3xl font-bold text-content-primary dark:text-content-primary-dark leading-tight">{playlistMetadata.name}</h1>
|
<h1 className="text-2xl md:text-3xl font-bold text-content-primary dark:text-content-primary-dark leading-tight">{playlistMetadata.name}</h1>
|
||||||
{playlistMetadata.description && (
|
{playlistMetadata.description && (
|
||||||
|
|||||||
@@ -142,6 +142,7 @@ export interface LibrespotPlaylistType {
|
|||||||
snapshot_id: string;
|
snapshot_id: string;
|
||||||
tracks: LibrespotPlaylistTracksPageType;
|
tracks: LibrespotPlaylistTracksPageType;
|
||||||
type: "playlist";
|
type: "playlist";
|
||||||
|
picture?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type guards
|
// Type guards
|
||||||
|
|||||||
Reference in New Issue
Block a user