v7.1.0
This commit is contained in:
+15
-15
@@ -26,32 +26,32 @@
|
|||||||
"@radix-ui/react-toggle": "^1.1.10",
|
"@radix-ui/react-toggle": "^1.1.10",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.11",
|
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.575.0",
|
||||||
"motion": "^12.26.2",
|
"motion": "^12.34.3",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.2.3",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.3",
|
"react-dom": "^19.2.4",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss": "^4.1.18"
|
"tailwindcss": "^4.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^10.0.1",
|
||||||
"@types/node": "^25.0.8",
|
"@types/node": "^25.3.0",
|
||||||
"@types/react": "^19.2.8",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.2",
|
"@vitejs/plugin-react": "^5.1.4",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^10.0.2",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.26",
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
"globals": "^17.0.0",
|
"globals": "^17.3.0",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"typescript-eslint": "^8.53.0",
|
"typescript-eslint": "^8.56.1",
|
||||||
"vite": "^7.3.1"
|
"vite": "^7.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
9fee02ec6592ede9ade4b36d56bd4d6d
|
3ca7ac3e41fb33a6fc3e30c16b39657b
|
||||||
Generated
+1080
-1208
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,6 @@ import { useState, useMemo } from "react";
|
|||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
interface ArtistInfoProps {
|
interface ArtistInfoProps {
|
||||||
artistInfo: {
|
artistInfo: {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -94,14 +93,12 @@ interface ArtistInfoProps {
|
|||||||
onTrackClick?: (track: TrackMetadata) => void;
|
onTrackClick?: (track: TrackMetadata) => void;
|
||||||
onBack?: () => 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, 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 [downloadingHeader, setDownloadingHeader] = useState(false);
|
||||||
const [downloadingAvatar, setDownloadingAvatar] = useState(false);
|
const [downloadingAvatar, setDownloadingAvatar] = useState(false);
|
||||||
const [downloadingGalleryIndex, setDownloadingGalleryIndex] = useState<number | null>(null);
|
const [downloadingGalleryIndex, setDownloadingGalleryIndex] = useState<number | null>(null);
|
||||||
const [downloadingAllGallery, setDownloadingAllGallery] = useState(false);
|
const [downloadingAllGallery, setDownloadingAllGallery] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState<"albums" | "tracks" | "gallery">("albums");
|
const [activeTab, setActiveTab] = useState<"albums" | "tracks" | "gallery">("albums");
|
||||||
|
|
||||||
const filteredAlbumGroups = useMemo(() => {
|
const filteredAlbumGroups = useMemo(() => {
|
||||||
const albumTypeMap = new Map(albumList.map(a => [a.name, a.album_type]));
|
const albumTypeMap = new Map(albumList.map(a => [a.name, a.album_type]));
|
||||||
const albumGroups = trackList.reduce((acc, track) => {
|
const albumGroups = trackList.reduce((acc, track) => {
|
||||||
@@ -128,7 +125,6 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
|
|||||||
return dateB.localeCompare(dateA);
|
return dateB.localeCompare(dateA);
|
||||||
});
|
});
|
||||||
}, [trackList, albumList]);
|
}, [trackList, albumList]);
|
||||||
|
|
||||||
const handleDownloadHeader = async () => {
|
const handleDownloadHeader = async () => {
|
||||||
if (!artistInfo.header)
|
if (!artistInfo.header)
|
||||||
return;
|
return;
|
||||||
@@ -159,7 +155,6 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
|
|||||||
setDownloadingHeader(false);
|
setDownloadingHeader(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownloadAvatar = async () => {
|
const handleDownloadAvatar = async () => {
|
||||||
if (!artistInfo.images)
|
if (!artistInfo.images)
|
||||||
return;
|
return;
|
||||||
@@ -190,7 +185,6 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
|
|||||||
setDownloadingAvatar(false);
|
setDownloadingAvatar(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownloadGalleryImage = async (imageUrl: string, index: number) => {
|
const handleDownloadGalleryImage = async (imageUrl: string, index: number) => {
|
||||||
setDownloadingGalleryIndex(index);
|
setDownloadingGalleryIndex(index);
|
||||||
try {
|
try {
|
||||||
@@ -220,7 +214,6 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
|
|||||||
setDownloadingGalleryIndex(null);
|
setDownloadingGalleryIndex(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDownloadAllGallery = async () => {
|
const handleDownloadAllGallery = async () => {
|
||||||
if (!artistInfo.gallery || artistInfo.gallery.length === 0)
|
if (!artistInfo.gallery || artistInfo.gallery.length === 0)
|
||||||
return;
|
return;
|
||||||
@@ -277,9 +270,7 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
|
|||||||
setDownloadingAllGallery(false);
|
setDownloadingAllGallery(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasGallery = artistInfo.gallery && artistInfo.gallery.length > 0;
|
const hasGallery = artistInfo.gallery && artistInfo.gallery.length > 0;
|
||||||
|
|
||||||
return (<div className="space-y-6">
|
return (<div className="space-y-6">
|
||||||
<Card className="overflow-hidden p-0 relative">
|
<Card className="overflow-hidden p-0 relative">
|
||||||
{artistInfo.header ? (<>
|
{artistInfo.header ? (<>
|
||||||
@@ -462,42 +453,28 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
|
|||||||
{isDownloading && bulkDownloadType === "all" ? (<Spinner />) : (<Download className="h-4 w-4"/>)}
|
{isDownloading && bulkDownloadType === "all" ? (<Spinner />) : (<Download className="h-4 w-4"/>)}
|
||||||
Download Discography
|
Download Discography
|
||||||
</Button>
|
</Button>
|
||||||
{selectedTracks.length > 0 && (
|
{selectedTracks.length > 0 && (<Button onClick={onDownloadSelected} size="sm" variant="secondary" disabled={isDownloading}>
|
||||||
<Button onClick={onDownloadSelected} size="sm" variant="secondary" disabled={isDownloading}>
|
|
||||||
{isDownloading && bulkDownloadType === "selected" ? (<Spinner />) : (<Download className="h-4 w-4"/>)}
|
{isDownloading && bulkDownloadType === "selected" ? (<Spinner />) : (<Download className="h-4 w-4"/>)}
|
||||||
Download Selected ({selectedTracks.length})
|
Download Selected ({selectedTracks.length})
|
||||||
</Button>
|
</Button>)}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
|
||||||
{albumList.map((album) => {
|
{albumList.map((album) => {
|
||||||
// Filter tracks for this specific album to handle selection
|
|
||||||
const albumTracks = trackList.filter(t => t.album_name === album.name);
|
const albumTracks = trackList.filter(t => t.album_name === album.name);
|
||||||
const tracksWithId = albumTracks.filter(t => t.spotify_id);
|
const tracksWithId = albumTracks.filter(t => t.spotify_id);
|
||||||
// Check if all tracks in this album are currently selected
|
|
||||||
const isSelected = tracksWithId.length > 0 && tracksWithId.every(t => selectedTracks.includes(t.spotify_id!));
|
const isSelected = tracksWithId.length > 0 && tracksWithId.every(t => selectedTracks.includes(t.spotify_id!));
|
||||||
const hasTracks = tracksWithId.length > 0;
|
const hasTracks = tracksWithId.length > 0;
|
||||||
|
|
||||||
return (<div key={album.id} className="group cursor-pointer relative" onClick={() => onAlbumClick({
|
return (<div key={album.id} className="group cursor-pointer relative" onClick={() => onAlbumClick({
|
||||||
id: album.id,
|
id: album.id,
|
||||||
name: album.name,
|
name: album.name,
|
||||||
external_urls: album.external_urls,
|
external_urls: album.external_urls,
|
||||||
})}>
|
})}>
|
||||||
<div className="relative mb-2">
|
<div className="relative mb-2">
|
||||||
{/* Checkbox for Album Selection */}
|
|
||||||
{hasTracks && (
|
{hasTracks && (<div className={`absolute top-2 left-2 z-20 ${isSelected ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'} transition-opacity`} onClick={(e) => e.stopPropagation()}>
|
||||||
<div
|
<Checkbox checked={isSelected} onCheckedChange={() => onToggleSelectAll(albumTracks)} className="bg-black/50 border-white/70 data-[state=checked]:bg-primary data-[state=checked]:border-primary"/>
|
||||||
className={`absolute top-2 left-2 z-20 ${isSelected ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'} transition-opacity`}
|
</div>)}
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
checked={isSelected}
|
|
||||||
onCheckedChange={() => onToggleSelectAll(albumTracks)}
|
|
||||||
className="bg-black/50 border-white/70 data-[state=checked]:bg-primary data-[state=checked]:border-primary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{album.images && (<img src={album.images} alt={album.name} className="w-full aspect-square object-cover rounded-md shadow-md transition-shadow group-hover:shadow-xl"/>)}
|
{album.images && (<img src={album.images} alt={album.name} className="w-full aspect-square object-cover rounded-md shadow-md transition-shadow group-hover:shadow-xl"/>)}
|
||||||
<div className="absolute bottom-2 right-2">
|
<div className="absolute bottom-2 right-2">
|
||||||
<span className="text-[10px] uppercase font-bold px-1.5 py-0.5 rounded bg-black/60 text-white backdrop-blur-[2px]">
|
<span className="text-[10px] uppercase font-bold px-1.5 py-0.5 rounded bg-black/60 text-white backdrop-blur-[2px]">
|
||||||
@@ -599,4 +576,4 @@ export function ArtistInfo({ artistInfo, albumList, trackList, searchQuery, sort
|
|||||||
<TrackList tracks={trackList} searchQuery={searchQuery} sortBy={sortBy} selectedTracks={selectedTracks} downloadedTracks={downloadedTracks} failedTracks={failedTracks} skippedTracks={skippedTracks} downloadingTrack={downloadingTrack} isDownloading={isDownloading} currentPage={currentPage} itemsPerPage={itemsPerPage} showCheckboxes={true} hideAlbumColumn={false} folderName={artistInfo.name} isArtistDiscography={true} downloadedLyrics={downloadedLyrics} failedLyrics={failedLyrics} skippedLyrics={skippedLyrics} downloadingLyricsTrack={downloadingLyricsTrack} checkingAvailabilityTrack={checkingAvailabilityTrack} availabilityMap={availabilityMap} onToggleTrack={onToggleTrack} onToggleSelectAll={onToggleSelectAll} onDownloadTrack={onDownloadTrack} onDownloadLyrics={onDownloadLyrics} onDownloadCover={onDownloadCover} downloadedCovers={downloadedCovers} failedCovers={failedCovers} skippedCovers={skippedCovers} downloadingCoverTrack={downloadingCoverTrack} onCheckAvailability={onCheckAvailability} onPageChange={onPageChange} onAlbumClick={onAlbumClick} onArtistClick={onArtistClick} onTrackClick={onTrackClick}/>
|
<TrackList tracks={trackList} searchQuery={searchQuery} sortBy={sortBy} selectedTracks={selectedTracks} downloadedTracks={downloadedTracks} failedTracks={failedTracks} skippedTracks={skippedTracks} downloadingTrack={downloadingTrack} isDownloading={isDownloading} currentPage={currentPage} itemsPerPage={itemsPerPage} showCheckboxes={true} hideAlbumColumn={false} folderName={artistInfo.name} isArtistDiscography={true} downloadedLyrics={downloadedLyrics} failedLyrics={failedLyrics} skippedLyrics={skippedLyrics} downloadingLyricsTrack={downloadingLyricsTrack} checkingAvailabilityTrack={checkingAvailabilityTrack} availabilityMap={availabilityMap} onToggleTrack={onToggleTrack} onToggleSelectAll={onToggleSelectAll} onDownloadTrack={onDownloadTrack} onDownloadLyrics={onDownloadLyrics} onDownloadCover={onDownloadCover} downloadedCovers={downloadedCovers} failedCovers={failedCovers} skippedCovers={skippedCovers} downloadingCoverTrack={downloadingCoverTrack} onCheckAvailability={onCheckAvailability} onPageChange={onPageChange} onAlbumClick={onAlbumClick} onArtistClick={onArtistClick} onTrackClick={onTrackClick}/>
|
||||||
</div>)}
|
</div>)}
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user