import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Download, FolderOpen, ImageDown, FileText, XCircle } from "lucide-react"; import { Spinner } from "@/components/ui/spinner"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { SearchAndSort } from "./SearchAndSort"; import { TrackList } from "./TrackList"; import { DownloadProgress } from "./DownloadProgress"; import { getSettings } from "@/lib/settings"; import { downloadCover } from "@/lib/api"; import { useState } from "react"; import { toastWithSound as toast } from "@/lib/toast-with-sound"; import { joinPath, sanitizePath } from "@/lib/utils"; import { parseTemplate, type TemplateData } from "@/lib/settings"; import { buildPlaylistFolderName } from "@/lib/playlist"; import type { TrackMetadata, TrackAvailability } from "@/types/api"; interface PlaylistInfoProps { playlistInfo: { owner: { name: string; display_name: string; images: string; }; tracks: { total: number; }; followers: { total: number; }; cover?: string; description?: string; }; trackList: TrackMetadata[]; searchQuery: string; sortBy: string; selectedTracks: string[]; downloadedTracks: Set; failedTracks: Set; skippedTracks: Set; downloadingTrack: string | null; isDownloading: boolean; bulkDownloadType: "all" | "selected" | null; downloadProgress: number; downloadRemainingCount: number; currentDownloadInfo: { name: string; artists: string; } | null; currentPage: number; itemsPerPage: number; downloadedLyrics?: Set; failedLyrics?: Set; skippedLyrics?: Set; downloadingLyricsTrack?: string | null; checkingAvailabilityTrack?: string | null; availabilityMap?: Map; downloadedCovers?: Set; failedCovers?: Set; skippedCovers?: Set; downloadingCoverTrack?: string | null; isBulkDownloadingCovers?: boolean; isBulkDownloadingLyrics?: boolean; isMetadataLoading?: boolean; onSearchChange: (value: string) => void; onSortChange: (value: string) => void; onToggleTrack: (id: string) => void; onToggleSelectAll: (tracks: TrackMetadata[]) => void; onDownloadTrack: (id: string, name: string, artists: string, albumName: string, spotifyId?: string, folderName?: string, durationMs?: number, position?: number, albumArtist?: string, releaseDate?: string, coverUrl?: string, spotifyTrackNumber?: number, spotifyDiscNumber?: number, spotifyTotalTracks?: number, spotifyTotalDiscs?: number, copyright?: string, publisher?: string) => void; onDownloadLyrics?: (spotifyId: string, name: string, artists: string, albumName: string, folderName?: string, isArtistDiscography?: boolean, position?: number, albumArtist?: string, releaseDate?: string, discNumber?: number) => void; onDownloadCover?: (coverUrl: string, trackName: string, artistName: string, albumName: string, folderName?: string, isArtistDiscography?: boolean, position?: number, trackId?: string, albumArtist?: string, releaseDate?: string, discNumber?: number) => void; onCheckAvailability?: (spotifyId: string) => void; onDownloadAllLyrics?: () => void; onDownloadAllCovers?: () => void; onDownloadAll: () => void; onDownloadSelected: () => void; onStopDownload: () => void; onOpenFolder: () => void; onPageChange: (page: number) => void; onAlbumClick: (album: { id: string; name: string; external_urls: string; }) => void; onArtistClick: (artist: { id: string; name: string; external_urls: string; }) => void; onTrackClick: (track: TrackMetadata) => void; onBack?: () => void; } export function PlaylistInfo({ playlistInfo, trackList, searchQuery, sortBy, selectedTracks, downloadedTracks, failedTracks, skippedTracks, downloadingTrack, isDownloading, bulkDownloadType, downloadProgress, downloadRemainingCount, 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; setDownloadingPlaylistCover(true); try { const os = settings.operatingSystem; let outputDir = settings.downloadPath; const placeholder = "__SLASH_PLACEHOLDER__"; const templateData: TemplateData = { artist: "", album: "", album_artist: "", title: playlistName.replace(/\//g, placeholder), playlist: playlistFolderName.replace(/\//g, placeholder), }; if (settings.createPlaylistFolder && playlistFolderName) { outputDir = joinPath(os, outputDir, sanitizePath(playlistFolderName.replace(/\//g, " "), os)); } if (settings.folderTemplate) { const folderPath = parseTemplate(settings.folderTemplate, templateData); if (folderPath) { const parts = folderPath.split("/").filter((p: string) => p.trim()); for (const part of parts) { outputDir = joinPath(os, outputDir, sanitizePath(part.replace(new RegExp(placeholder, "g"), " "), os)); } } } const response = await downloadCover({ cover_url: playlistInfo.cover, track_name: playlistName, artist_name: "", album_name: "", album_artist: "", release_date: "", output_dir: outputDir, filename_format: "title", track_number: false, position: 0, disc_number: 0, }); if (response.success) { if (response.already_exists) toast.info("Cover already exists"); else toast.success("Separate playlist cover downloaded"); } else { toast.error(response.error || "Failed to download cover"); } } catch (err) { toast.error(err instanceof Error ? err.message : "Failed to download cover"); } finally { setDownloadingPlaylistCover(false); } }; return (
{onBack && (
)}
{playlistInfo.cover && (
{playlistName}

Download Separate Playlist Cover

)}

Playlist

{playlistName}

{playlistInfo.description && (

{playlistInfo.description}

)}
{playlistInfo.owner.images && ({playlistInfo.owner.display_name})} {playlistInfo.owner.display_name}
{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"}
{selectedTracks.length > 0 && ()} {onDownloadAllLyrics && (

Download All Lyrics

)} {onDownloadAllCovers && (

Download All Separate Covers

)} {downloadedTracks.size > 0 && (

Open Folder

)}
{isDownloading && ()}
); }