.refine artist fetch all tracks

This commit is contained in:
afkarxyz
2026-03-25 13:43:14 +07:00
parent dd67b54ea9
commit d8722c58dc
7 changed files with 279 additions and 61 deletions
+7 -5
View File
@@ -21,6 +21,7 @@ interface ArtistInfoProps {
header?: string;
gallery?: string[];
followers: number;
total_albums?: number;
genres: string[];
biography?: string;
verified?: boolean;
@@ -99,6 +100,7 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
const [downloadingGalleryIndex, setDownloadingGalleryIndex] = useState<number | null>(null);
const [downloadingAllGallery, setDownloadingAllGallery] = useState(false);
const [activeTab, setActiveTab] = useState<"albums" | "tracks" | "gallery">("albums");
const displayedAlbumCount = artistInfo.total_albums || albumList.length;
const filteredAlbumGroups = useMemo(() => {
const albumTypeMap = new Map(albumList.map(a => [a.name, a.album_type]));
const albumGroups = trackList.reduce((acc, track) => {
@@ -330,9 +332,9 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
</>)}
</div>
<div className="flex items-center gap-2 text-sm flex-wrap text-white/90">
<span>{albumList.length} {albumList.length === 1 ? "album" : "albums"}</span>
<span>{displayedAlbumCount.toLocaleString()} {displayedAlbumCount === 1 ? "album" : "albums"}</span>
<span></span>
<span>{trackList.length} {trackList.length === 1 ? "track" : "tracks"}</span>
<span>{trackList.length.toLocaleString()} {trackList.length === 1 ? "track" : "tracks"}</span>
{artistInfo.genres.length > 0 && (<>
<span></span>
<span>{artistInfo.genres.join(", ")}</span>
@@ -383,9 +385,9 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
</>)}
</div>
<div className="flex items-center gap-2 text-sm flex-wrap">
<span>{albumList.length} {albumList.length === 1 ? "album" : "albums"}</span>
<span>{displayedAlbumCount.toLocaleString()} {displayedAlbumCount === 1 ? "album" : "albums"}</span>
<span></span>
<span>{trackList.length} {trackList.length === 1 ? "track" : "tracks"}</span>
<span>{trackList.length.toLocaleString()} {trackList.length === 1 ? "track" : "tracks"}</span>
{artistInfo.genres.length > 0 && (<>
<span></span>
<span>{artistInfo.genres.join(", ")}</span>
@@ -412,7 +414,7 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
{activeTab === "gallery" && hasGallery && (<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-2xl font-bold">Gallery ({artistInfo.gallery!.length})</h3>
<h3 className="text-2xl font-bold">Gallery ({artistInfo.gallery!.length.toLocaleString()})</h3>
<Tooltip>
<TooltipTrigger asChild>
<Button onClick={handleDownloadAllGallery} size="sm" variant="outline" disabled={downloadingAllGallery}>
+72 -1
View File
@@ -1,13 +1,17 @@
import { useState } from "react";
import { useEffect, useRef, useState } from "react";
import { getSettings } from "@/lib/settings";
import { fetchSpotifyMetadata } from "@/lib/api";
import { toastWithSound as toast } from "@/lib/toast-with-sound";
import { logger } from "@/lib/logger";
import { AddFetchHistory } from "../../wailsjs/go/main/App";
import { EventsOff, EventsOn } from "../../wailsjs/runtime/runtime";
import type { SpotifyMetadataResponse } from "@/types/api";
export function useMetadata() {
const [loading, setLoading] = useState(false);
const [metadata, setMetadata] = useState<SpotifyMetadataResponse | null>(null);
const loadingToastId = useRef<string | number | null>(null);
const fetchedCount = useRef(0);
const currentName = useRef("");
const [showApiModal, setShowApiModal] = useState(false);
const [showAlbumDialog, setShowAlbumDialog] = useState(false);
const [selectedAlbum, setSelectedAlbum] = useState<{
@@ -16,6 +20,73 @@ export function useMetadata() {
external_urls: string;
} | null>(null);
const [pendingArtistName, setPendingArtistName] = useState<string | null>(null);
useEffect(() => {
if (loading) {
fetchedCount.current = 0;
currentName.current = "";
loadingToastId.current = toast.silentInfo("fetching metadata...", {
duration: Infinity,
description: "please wait while we retrieve the information"
});
return;
}
if (loadingToastId.current) {
toast.dismiss(loadingToastId.current);
loadingToastId.current = null;
}
}, [loading]);
useEffect(() => {
const handler = (data: any) => {
if (!data) {
return;
}
if (Array.isArray(data)) {
fetchedCount.current += data.length;
if (loadingToastId.current && currentName.current) {
toast.silentInfo(`fetching tracks for ${currentName.current.toLowerCase()}...`, {
id: loadingToastId.current,
description: `${fetchedCount.current.toLocaleString()} tracks fetched`
});
}
}
else {
const baseInfo = data;
const name = "artist_info" in baseInfo ? baseInfo.artist_info.name :
"album_info" in baseInfo ? baseInfo.album_info.name :
"playlist_info" in baseInfo ? (baseInfo.playlist_info.name || baseInfo.playlist_info.owner.name) : "";
if (name) {
currentName.current = name;
if (loadingToastId.current) {
toast.silentInfo(`fetching tracks for ${name.toLowerCase()}...`, {
id: loadingToastId.current,
description: `${fetchedCount.current.toLocaleString()} tracks fetched`
});
}
}
}
setMetadata(prev => {
if (Array.isArray(data)) {
if (!prev || !("track_list" in prev)) {
return prev;
}
return {
...prev,
track_list: [...prev.track_list, ...data]
};
}
if (prev && "track_list" in prev && prev.track_list.length > 0) {
return prev;
}
const baseInfo = data;
if (!("track_list" in baseInfo)) {
baseInfo.track_list = [];
}
return baseInfo;
});
};
EventsOn("metadata-stream", handler);
return () => EventsOff("metadata-stream");
}, []);
const getUrlType = (url: string): string => {
if (url.includes("/track/"))
return "track";
+13 -5
View File
@@ -5,41 +5,49 @@ import { getSettings } from "./settings";
const toastStyle = {
className: "font-mono lowercase",
};
type ToastData = Parameters<typeof toast.success>[1];
const isSfxEnabled = () => getSettings().sfxEnabled;
export const toastWithSound = {
success: (message: string, data?: any) => {
success: (message: string, data?: ToastData) => {
const msg = message.toLowerCase();
logger.success(msg);
if (isSfxEnabled())
playSuccessSound();
return toast.success(msg, { ...toastStyle, ...data });
},
error: (message: string, data?: any) => {
error: (message: string, data?: ToastData) => {
const msg = message.toLowerCase();
logger.error(msg);
if (isSfxEnabled())
playErrorSound();
return toast.error(msg, { ...toastStyle, ...data });
},
warning: (message: string, data?: any) => {
warning: (message: string, data?: ToastData) => {
const msg = message.toLowerCase();
logger.warning(msg);
if (isSfxEnabled())
playWarningSound();
return toast.warning(msg, { ...toastStyle, ...data });
},
info: (message: string, data?: any) => {
info: (message: string, data?: ToastData) => {
const msg = message.toLowerCase();
logger.info(msg);
if (isSfxEnabled())
playInfoSound();
return toast.info(msg, { ...toastStyle, ...data });
},
message: (message: string, data?: any) => {
message: (message: string, data?: ToastData) => {
const msg = message.toLowerCase();
logger.info(msg);
if (isSfxEnabled())
playInfoSound();
return toast(msg, { ...toastStyle, ...data });
},
silentInfo: (message: string, data?: ToastData) => {
const msg = message.toLowerCase();
logger.info(msg);
return toast.info(msg, { ...toastStyle, ...data });
},
dismiss: (id?: string | number) => toast.dismiss(id),
toast: toast,
};