This commit is contained in:
afkarxyz
2026-03-11 03:19:59 +07:00
parent d495a9851c
commit b3273b7602
42 changed files with 1807 additions and 1655 deletions
+79 -1
View File
@@ -6,6 +6,12 @@ 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 type { TrackMetadata, TrackAvailability } from "@/types/api";
interface PlaylistInfoProps {
playlistInfo: {
@@ -81,6 +87,66 @@ interface PlaylistInfoProps {
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) {
const settings = getSettings();
const [downloadingPlaylistCover, setDownloadingPlaylistCover] = useState(false);
const handleDownloadPlaylistCover = async () => {
if (!playlistInfo.cover)
return;
setDownloadingPlaylistCover(true);
try {
const os = settings.operatingSystem;
let outputDir = settings.downloadPath;
const playlistName = playlistInfo.owner.name;
const placeholder = "__SLASH_PLACEHOLDER__";
const templateData: TemplateData = {
artist: "",
album: "",
album_artist: "",
title: playlistName.replace(/\//g, placeholder),
playlist: playlistName.replace(/\//g, placeholder),
};
if (settings.createPlaylistFolder && playlistName) {
outputDir = joinPath(os, outputDir, sanitizePath(playlistName.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("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 (<div className="space-y-6">
<Card className="relative">
{onBack && (<div className="absolute top-4 right-4 z-10">
@@ -90,7 +156,19 @@ export function PlaylistInfo({ playlistInfo, trackList, searchQuery, sortBy, sel
</div>)}
<CardContent className="px-6">
<div className="flex gap-6 items-start">
{playlistInfo.cover && (<img src={playlistInfo.cover} alt={playlistInfo.owner.name} className="w-48 h-48 rounded-md shadow-lg object-cover"/>)}
{playlistInfo.cover && (<div className="relative group shrink-0 w-48 h-48">
<img src={playlistInfo.cover} alt={playlistInfo.owner.name} className="w-48 h-48 rounded-md shadow-lg object-cover"/>
<div className="absolute inset-0 flex items-center justify-center bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity rounded-md">
<Tooltip>
<TooltipTrigger asChild>
<Button variant="secondary" size="icon" className="h-9 w-9 shadow-lg" onClick={handleDownloadPlaylistCover} disabled={downloadingPlaylistCover}>
{downloadingPlaylistCover ? <Spinner /> : <ImageDown className="h-4 w-4"/>}
</Button>
</TooltipTrigger>
<TooltipContent><p>Download Playlist Cover</p></TooltipContent>
</Tooltip>
</div>
</div>)}
<div className="flex-1 space-y-4">
<div className="space-y-2">
<p className="text-sm font-medium">Playlist</p>