diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index be255d0..2d5410f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -423,7 +423,7 @@ function App() { } if ("album_info" in metadata.metadata) { const { album_info, track_list } = metadata.metadata; - return ( lyrics.handleDownloadLyrics(spotifyId, name, artists, albumName, album_info.name, position, albumArtist, releaseDate, discNumber, true)} onDownloadCover={(coverUrl, trackName, artistName, albumName, _folderName, _isArtistDiscography, position, trackId, albumArtist, releaseDate, discNumber) => cover.handleDownloadCover(coverUrl, trackName, artistName, albumName, album_info.name, position, trackId, albumArtist, releaseDate, discNumber, true)} onCheckAvailability={availability.checkAvailability} onDownloadAllLyrics={() => lyrics.handleDownloadAllLyrics(track_list, album_info.name, undefined, true)} onDownloadAllCovers={() => cover.handleDownloadAllCovers(track_list, album_info.name, true)} onDownloadAll={() => download.handleDownloadAll(track_list, album_info.name, true)} onDownloadSelected={() => download.handleDownloadSelected(selectedTracks, track_list, album_info.name, true)} onStopDownload={download.handleStopDownload} onOpenFolder={handleOpenFolder} onPageChange={setCurrentListPage} onBack={metadata.resetMetadata} onArtistClick={async (artist) => { + return ( lyrics.handleDownloadLyrics(spotifyId, name, artists, albumName, album_info.name, position, albumArtist, releaseDate, discNumber, true)} onDownloadCover={(coverUrl, trackName, artistName, albumName, _folderName, _isArtistDiscography, position, trackId, albumArtist, releaseDate, discNumber) => cover.handleDownloadCover(coverUrl, trackName, artistName, albumName, album_info.name, position, trackId, albumArtist, releaseDate, discNumber, true)} onCheckAvailability={availability.checkAvailability} onDownloadAllLyrics={() => lyrics.handleDownloadAllLyrics(track_list, album_info.name, undefined, true)} onDownloadAllCovers={() => cover.handleDownloadAllCovers(track_list, album_info.name, true)} onDownloadAll={() => download.handleDownloadAll(track_list, album_info.name, true)} onDownloadSelected={() => download.handleDownloadSelected(selectedTracks, track_list, album_info.name, true)} onStopDownload={download.handleStopDownload} onOpenFolder={handleOpenFolder} onPageChange={setCurrentListPage} onBack={metadata.resetMetadata} onArtistClick={async (artist) => { const pendingArtistUrl = artist.external_urls.replace(/\/$/, "") + "/discography/all"; setSpotifyUrl(pendingArtistUrl); const artistUrl = await metadata.handleArtistClick(artist); @@ -441,7 +441,7 @@ function App() { const { playlist_info, track_list } = metadata.metadata; const settings = getSettings(); const playlistFolderName = buildPlaylistFolderName(playlist_info.owner.name, playlist_info.owner.display_name, settings.playlistOwnerFolderName); - return ( lyrics.handleDownloadLyrics(spotifyId, name, artists, albumName, playlistFolderName, position, albumArtist, releaseDate, discNumber)} onDownloadCover={(coverUrl, trackName, artistName, albumName, _folderName, _isArtistDiscography, position, trackId, albumArtist, releaseDate, discNumber) => cover.handleDownloadCover(coverUrl, trackName, artistName, albumName, playlistFolderName, position, trackId, albumArtist, releaseDate, discNumber)} onCheckAvailability={availability.checkAvailability} onDownloadAllLyrics={() => lyrics.handleDownloadAllLyrics(track_list, playlistFolderName)} onDownloadAllCovers={() => cover.handleDownloadAllCovers(track_list, playlistFolderName)} onDownloadAll={() => download.handleDownloadAll(track_list, playlistFolderName)} onDownloadSelected={() => download.handleDownloadSelected(selectedTracks, track_list, playlistFolderName)} onStopDownload={download.handleStopDownload} onOpenFolder={handleOpenFolder} onPageChange={setCurrentListPage} onBack={metadata.resetMetadata} onAlbumClick={metadata.handleAlbumClick} onArtistClick={async (artist) => { + return ( lyrics.handleDownloadLyrics(spotifyId, name, artists, albumName, playlistFolderName, position, albumArtist, releaseDate, discNumber)} onDownloadCover={(coverUrl, trackName, artistName, albumName, _folderName, _isArtistDiscography, position, trackId, albumArtist, releaseDate, discNumber) => cover.handleDownloadCover(coverUrl, trackName, artistName, albumName, playlistFolderName, position, trackId, albumArtist, releaseDate, discNumber)} onCheckAvailability={availability.checkAvailability} onDownloadAllLyrics={() => lyrics.handleDownloadAllLyrics(track_list, playlistFolderName)} onDownloadAllCovers={() => cover.handleDownloadAllCovers(track_list, playlistFolderName)} onDownloadAll={() => download.handleDownloadAll(track_list, playlistFolderName)} onDownloadSelected={() => download.handleDownloadSelected(selectedTracks, track_list, playlistFolderName)} onStopDownload={download.handleStopDownload} onOpenFolder={handleOpenFolder} onPageChange={setCurrentListPage} onBack={metadata.resetMetadata} onAlbumClick={metadata.handleAlbumClick} onArtistClick={async (artist) => { const pendingArtistUrl = artist.external_urls.replace(/\/$/, "") + "/discography/all"; setSpotifyUrl(pendingArtistUrl); const artistUrl = await metadata.handleArtistClick(artist); @@ -457,7 +457,7 @@ function App() { } if ("artist_info" in metadata.metadata) { const { artist_info, album_list, track_list } = metadata.metadata; - return ( lyrics.handleDownloadLyrics(spotifyId, name, artists, albumName, artist_info.name, position, albumArtist, releaseDate, discNumber)} onDownloadCover={(coverUrl, trackName, artistName, albumName, _folderName, _isArtistDiscography, position, trackId, albumArtist, releaseDate, discNumber) => cover.handleDownloadCover(coverUrl, trackName, artistName, albumName, artist_info.name, position, trackId, albumArtist, releaseDate, discNumber)} onCheckAvailability={availability.checkAvailability} onDownloadAllLyrics={() => lyrics.handleDownloadAllLyrics(track_list, artist_info.name)} onDownloadAllCovers={() => cover.handleDownloadAllCovers(track_list, artist_info.name)} onDownloadAll={() => download.handleDownloadAll(track_list, artist_info.name)} onDownloadSelected={() => download.handleDownloadSelected(selectedTracks, track_list, artist_info.name)} onStopDownload={download.handleStopDownload} onOpenFolder={handleOpenFolder} onAlbumClick={metadata.handleAlbumClick} onBack={metadata.resetMetadata} onArtistClick={async (artist) => { + return ( lyrics.handleDownloadLyrics(spotifyId, name, artists, albumName, artist_info.name, position, albumArtist, releaseDate, discNumber)} onDownloadCover={(coverUrl, trackName, artistName, albumName, _folderName, _isArtistDiscography, position, trackId, albumArtist, releaseDate, discNumber) => cover.handleDownloadCover(coverUrl, trackName, artistName, albumName, artist_info.name, position, trackId, albumArtist, releaseDate, discNumber)} onCheckAvailability={availability.checkAvailability} onDownloadAllLyrics={() => lyrics.handleDownloadAllLyrics(track_list, artist_info.name)} onDownloadAllCovers={() => cover.handleDownloadAllCovers(track_list, artist_info.name)} onDownloadAll={() => download.handleDownloadAll(track_list, artist_info.name)} onDownloadSelected={() => download.handleDownloadSelected(selectedTracks, track_list, artist_info.name)} onStopDownload={download.handleStopDownload} onOpenFolder={handleOpenFolder} onAlbumClick={metadata.handleAlbumClick} onBack={metadata.resetMetadata} onArtistClick={async (artist) => { const pendingArtistUrl = artist.external_urls.replace(/\/$/, "") + "/discography/all"; setSpotifyUrl(pendingArtistUrl); const artistUrl = await metadata.handleArtistClick(artist); diff --git a/frontend/src/components/AlbumInfo.tsx b/frontend/src/components/AlbumInfo.tsx index 11d9196..5b7cc87 100644 --- a/frontend/src/components/AlbumInfo.tsx +++ b/frontend/src/components/AlbumInfo.tsx @@ -53,6 +53,7 @@ interface AlbumInfoProps { downloadingCoverTrack?: string | null; isBulkDownloadingCovers?: boolean; isBulkDownloadingLyrics?: boolean; + isMetadataLoading?: boolean; onSearchChange: (value: string) => void; onSortChange: (value: string) => void; onToggleTrack: (id: string) => void; @@ -76,10 +77,13 @@ interface AlbumInfoProps { onTrackClick?: (track: TrackMetadata) => void; onBack?: () => void; } -export function AlbumInfo({ albumInfo, trackList, searchQuery, sortBy, selectedTracks, downloadedTracks, failedTracks, skippedTracks, downloadingTrack, isDownloading, bulkDownloadType, downloadProgress, currentDownloadInfo, currentPage, itemsPerPage, downloadedLyrics, failedLyrics, skippedLyrics, downloadingLyricsTrack, checkingAvailabilityTrack, availabilityMap, downloadedCovers, failedCovers, skippedCovers, downloadingCoverTrack, isBulkDownloadingCovers, isBulkDownloadingLyrics, onSearchChange, onSortChange, onToggleTrack, onToggleSelectAll, onDownloadTrack, onDownloadLyrics, onDownloadCover, onCheckAvailability, onDownloadAllLyrics, onDownloadAllCovers, onDownloadAll, onDownloadSelected, onStopDownload, onOpenFolder, onPageChange, onArtistClick, onTrackClick, onBack, }: AlbumInfoProps) { +export function AlbumInfo({ albumInfo, trackList, searchQuery, sortBy, selectedTracks, downloadedTracks, failedTracks, skippedTracks, downloadingTrack, isDownloading, bulkDownloadType, downloadProgress, currentDownloadInfo, currentPage, itemsPerPage, downloadedLyrics, failedLyrics, skippedLyrics, downloadingLyricsTrack, checkingAvailabilityTrack, availabilityMap, downloadedCovers, failedCovers, skippedCovers, downloadingCoverTrack, isBulkDownloadingCovers, isBulkDownloadingLyrics, isMetadataLoading = false, onSearchChange, onSortChange, onToggleTrack, onToggleSelectAll, onDownloadTrack, onDownloadLyrics, onDownloadCover, onCheckAvailability, onDownloadAllLyrics, onDownloadAllCovers, onDownloadAll, onDownloadSelected, onStopDownload, onOpenFolder, onPageChange, onArtistClick, onTrackClick, onBack, }: AlbumInfoProps) { const settings = getSettings(); const albumArtistNames = splitArtistNames(albumInfo.artists); const artistSeparator = albumInfo.artists.includes(";") ? "; " : ", "; + const fetchedTrackCount = trackList.length; + const totalTrackCount = albumInfo.total_tracks; + const showStreamingProgress = isMetadataLoading && totalTrackCount > 0 && fetchedTrackCount < totalTrackCount; const clickableAlbumArtists = (() => { const artistsByName = new Map{albumInfo.release_date} - {albumInfo.total_tracks.toLocaleString()} {albumInfo.total_tracks === 1 ? "track" : "tracks"} + {showStreamingProgress + ? `${fetchedTrackCount.toLocaleString()} / ${totalTrackCount.toLocaleString()} tracks` + : `${Math.max(totalTrackCount, fetchedTrackCount).toLocaleString()} ${Math.max(totalTrackCount, fetchedTrackCount) === 1 ? "track" : "tracks"}`} diff --git a/frontend/src/components/ArtistInfo.tsx b/frontend/src/components/ArtistInfo.tsx index 987d5ca..9b77060 100644 --- a/frontend/src/components/ArtistInfo.tsx +++ b/frontend/src/components/ArtistInfo.tsx @@ -66,6 +66,7 @@ interface ArtistInfoProps { downloadingCoverTrack?: string | null; isBulkDownloadingCovers?: boolean; isBulkDownloadingLyrics?: boolean; + isMetadataLoading?: boolean; onSearchChange: (value: string) => void; onSortChange: (value: string) => void; onToggleTrack: (id: string) => void; @@ -94,7 +95,7 @@ interface ArtistInfoProps { onTrackClick?: (track: TrackMetadata) => void; onBack?: () => void; } -export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sortBy, selectedTracks, downloadedTracks, failedTracks, skippedTracks, downloadingTrack, isDownloading, bulkDownloadType, downloadProgress, currentDownloadInfo, currentPage, itemsPerPage, downloadedLyrics, failedLyrics, skippedLyrics, downloadingLyricsTrack, checkingAvailabilityTrack, availabilityMap, downloadedCovers, failedCovers, skippedCovers, downloadingCoverTrack, isBulkDownloadingCovers, isBulkDownloadingLyrics, onSearchChange, onSortChange, onToggleTrack, onToggleSelectAll, onDownloadTrack, onDownloadLyrics, onDownloadCover, onCheckAvailability, onDownloadAllLyrics, onDownloadAllCovers, onDownloadAll, onDownloadSelected, onStopDownload, onOpenFolder, onAlbumClick, onArtistClick, onPageChange, onTrackClick, onBack, }: ArtistInfoProps) { +export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sortBy, selectedTracks, downloadedTracks, failedTracks, skippedTracks, downloadingTrack, isDownloading, bulkDownloadType, downloadProgress, currentDownloadInfo, currentPage, itemsPerPage, downloadedLyrics, failedLyrics, skippedLyrics, downloadingLyricsTrack, checkingAvailabilityTrack, availabilityMap, downloadedCovers, failedCovers, skippedCovers, downloadingCoverTrack, isBulkDownloadingCovers, isBulkDownloadingLyrics, isMetadataLoading = false, onSearchChange, onSortChange, onToggleTrack, onToggleSelectAll, onDownloadTrack, onDownloadLyrics, onDownloadCover, onCheckAvailability, onDownloadAllLyrics, onDownloadAllCovers, onDownloadAll, onDownloadSelected, onStopDownload, onOpenFolder, onAlbumClick, onArtistClick, onPageChange, onTrackClick, onBack, }: ArtistInfoProps) { const [downloadingHeader, setDownloadingHeader] = useState(false); const [downloadingAvatar, setDownloadingAvatar] = useState(false); const [downloadingGalleryIndex, setDownloadingGalleryIndex] = useState(null); @@ -102,6 +103,17 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort const [activeTab, setActiveTab] = useState<"albums" | "tracks" | "gallery">("albums"); const [activeAlbumFilter, setActiveAlbumFilter] = useState("all"); const displayedAlbumCount = artistInfo.total_albums || albumList.length; + const fetchedAlbumCount = albumList.length; + const totalAlbumCount = artistInfo.total_albums || fetchedAlbumCount; + const totalTrackCount = albumList.reduce((sum, album) => sum + (album.total_tracks || 0), 0); + const fetchedTrackCount = trackList.length; + const albumCountLabel = isMetadataLoading && totalAlbumCount > 0 && fetchedAlbumCount < totalAlbumCount + ? `${fetchedAlbumCount.toLocaleString()} / ${totalAlbumCount.toLocaleString()} albums` + : `${displayedAlbumCount.toLocaleString()} ${displayedAlbumCount === 1 ? "album" : "albums"}`; + const resolvedTrackCount = totalTrackCount > 0 ? totalTrackCount : fetchedTrackCount; + const trackCountLabel = isMetadataLoading && totalTrackCount > 0 && fetchedTrackCount < totalTrackCount + ? `${fetchedTrackCount.toLocaleString()} / ${totalTrackCount.toLocaleString()} tracks` + : `${resolvedTrackCount.toLocaleString()} ${resolvedTrackCount === 1 ? "track" : "tracks"}`; const albumFilterCounts = useMemo(() => { const counts = new Map(); counts.set("all", (albumList || []).length); @@ -367,9 +379,9 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort )}
- {displayedAlbumCount.toLocaleString()} {displayedAlbumCount === 1 ? "album" : "albums"} + {albumCountLabel} - {trackList.length.toLocaleString()} {trackList.length === 1 ? "track" : "tracks"} + {trackCountLabel} {artistInfo.genres.length > 0 && (<> {artistInfo.genres.join(", ")} @@ -420,9 +432,9 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort )}
- {displayedAlbumCount.toLocaleString()} {displayedAlbumCount === 1 ? "album" : "albums"} + {albumCountLabel} - {trackList.length.toLocaleString()} {trackList.length === 1 ? "track" : "tracks"} + {trackCountLabel} {artistInfo.genres.length > 0 && (<> {artistInfo.genres.join(", ")} diff --git a/frontend/src/components/PlaylistInfo.tsx b/frontend/src/components/PlaylistInfo.tsx index 1b7609b..02ce2c0 100644 --- a/frontend/src/components/PlaylistInfo.tsx +++ b/frontend/src/components/PlaylistInfo.tsx @@ -59,6 +59,7 @@ interface PlaylistInfoProps { downloadingCoverTrack?: string | null; isBulkDownloadingCovers?: boolean; isBulkDownloadingLyrics?: boolean; + isMetadataLoading?: boolean; onSearchChange: (value: string) => void; onSortChange: (value: string) => void; onToggleTrack: (id: string) => void; @@ -87,11 +88,14 @@ interface PlaylistInfoProps { onTrackClick: (track: TrackMetadata) => void; onBack?: () => void; } -export function PlaylistInfo({ playlistInfo, trackList, searchQuery, sortBy, selectedTracks, downloadedTracks, failedTracks, skippedTracks, downloadingTrack, isDownloading, bulkDownloadType, downloadProgress, currentDownloadInfo, currentPage, itemsPerPage, downloadedLyrics, failedLyrics, skippedLyrics, downloadingLyricsTrack, checkingAvailabilityTrack, availabilityMap, downloadedCovers, failedCovers, skippedCovers, downloadingCoverTrack, isBulkDownloadingCovers, isBulkDownloadingLyrics, onSearchChange, onSortChange, onToggleTrack, onToggleSelectAll, onDownloadTrack, onDownloadLyrics, onDownloadCover, onCheckAvailability, onDownloadAllLyrics, onDownloadAllCovers, onDownloadAll, onDownloadSelected, onStopDownload, onOpenFolder, onPageChange, onAlbumClick, onArtistClick, onTrackClick, onBack, }: PlaylistInfoProps) { +export function PlaylistInfo({ playlistInfo, trackList, searchQuery, sortBy, selectedTracks, downloadedTracks, failedTracks, skippedTracks, downloadingTrack, isDownloading, bulkDownloadType, downloadProgress, currentDownloadInfo, currentPage, itemsPerPage, downloadedLyrics, failedLyrics, skippedLyrics, downloadingLyricsTrack, checkingAvailabilityTrack, availabilityMap, downloadedCovers, failedCovers, skippedCovers, downloadingCoverTrack, isBulkDownloadingCovers, isBulkDownloadingLyrics, isMetadataLoading = false, onSearchChange, onSortChange, onToggleTrack, onToggleSelectAll, onDownloadTrack, onDownloadLyrics, onDownloadCover, onCheckAvailability, onDownloadAllLyrics, onDownloadAllCovers, onDownloadAll, onDownloadSelected, onStopDownload, onOpenFolder, onPageChange, onAlbumClick, onArtistClick, onTrackClick, onBack, }: PlaylistInfoProps) { const settings = getSettings(); const playlistName = playlistInfo.owner.name; const playlistFolderName = buildPlaylistFolderName(playlistName, playlistInfo.owner.display_name, settings.playlistOwnerFolderName); const [downloadingPlaylistCover, setDownloadingPlaylistCover] = useState(false); + const fetchedTrackCount = trackList.length; + const totalTrackCount = playlistInfo.tracks.total; + const showStreamingProgress = isMetadataLoading && totalTrackCount > 0 && fetchedTrackCount < totalTrackCount; const handleDownloadPlaylistCover = async () => { if (!playlistInfo.cover) return; @@ -183,7 +187,9 @@ export function PlaylistInfo({ playlistInfo, trackList, searchQuery, sortBy, sel
- {playlistInfo.tracks.total.toLocaleString()} {playlistInfo.tracks.total === 1 ? "track" : "tracks"} + {showStreamingProgress + ? `${fetchedTrackCount.toLocaleString()} / ${totalTrackCount.toLocaleString()} tracks` + : `${Math.max(totalTrackCount, fetchedTrackCount).toLocaleString()} ${Math.max(totalTrackCount, fetchedTrackCount) === 1 ? "track" : "tracks"}`} {playlistInfo.followers.total.toLocaleString()} {playlistInfo.followers.total === 1 ? "follower" : "followers"}