v6.1
This commit is contained in:
@@ -40,7 +40,7 @@ function App() {
|
||||
const [hasUpdate, setHasUpdate] = useState(false);
|
||||
|
||||
const ITEMS_PER_PAGE = 50;
|
||||
const CURRENT_VERSION = "6.0";
|
||||
const CURRENT_VERSION = "6.1";
|
||||
|
||||
const download = useDownload();
|
||||
const metadata = useMetadata();
|
||||
@@ -144,6 +144,7 @@ function App() {
|
||||
isDownloading={download.isDownloading}
|
||||
downloadingTrack={download.downloadingTrack}
|
||||
isDownloaded={download.downloadedTracks.has(track.isrc)}
|
||||
isFailed={download.failedTracks.has(track.isrc)}
|
||||
onDownload={download.handleDownloadTrack}
|
||||
onOpenFolder={handleOpenFolder}
|
||||
/>
|
||||
@@ -160,6 +161,7 @@ function App() {
|
||||
sortBy={sortBy}
|
||||
selectedTracks={selectedTracks}
|
||||
downloadedTracks={download.downloadedTracks}
|
||||
failedTracks={download.failedTracks}
|
||||
downloadingTrack={download.downloadingTrack}
|
||||
isDownloading={download.isDownloading}
|
||||
bulkDownloadType={download.bulkDownloadType}
|
||||
@@ -193,6 +195,7 @@ function App() {
|
||||
sortBy={sortBy}
|
||||
selectedTracks={selectedTracks}
|
||||
downloadedTracks={download.downloadedTracks}
|
||||
failedTracks={download.failedTracks}
|
||||
downloadingTrack={download.downloadingTrack}
|
||||
isDownloading={download.isDownloading}
|
||||
bulkDownloadType={download.bulkDownloadType}
|
||||
@@ -231,6 +234,7 @@ function App() {
|
||||
sortBy={sortBy}
|
||||
selectedTracks={selectedTracks}
|
||||
downloadedTracks={download.downloadedTracks}
|
||||
failedTracks={download.failedTracks}
|
||||
downloadingTrack={download.downloadingTrack}
|
||||
isDownloading={download.isDownloading}
|
||||
bulkDownloadType={download.bulkDownloadType}
|
||||
|
||||
@@ -20,6 +20,7 @@ interface AlbumInfoProps {
|
||||
sortBy: string;
|
||||
selectedTracks: string[];
|
||||
downloadedTracks: Set<string>;
|
||||
failedTracks: Set<string>;
|
||||
downloadingTrack: string | null;
|
||||
isDownloading: boolean;
|
||||
bulkDownloadType: "all" | "selected" | null;
|
||||
@@ -46,6 +47,7 @@ export function AlbumInfo({
|
||||
sortBy,
|
||||
selectedTracks,
|
||||
downloadedTracks,
|
||||
failedTracks,
|
||||
downloadingTrack,
|
||||
isDownloading,
|
||||
bulkDownloadType,
|
||||
@@ -145,6 +147,7 @@ export function AlbumInfo({
|
||||
sortBy={sortBy}
|
||||
selectedTracks={selectedTracks}
|
||||
downloadedTracks={downloadedTracks}
|
||||
failedTracks={failedTracks}
|
||||
downloadingTrack={downloadingTrack}
|
||||
isDownloading={isDownloading}
|
||||
currentPage={currentPage}
|
||||
|
||||
@@ -27,6 +27,7 @@ interface ArtistInfoProps {
|
||||
sortBy: string;
|
||||
selectedTracks: string[];
|
||||
downloadedTracks: Set<string>;
|
||||
failedTracks: Set<string>;
|
||||
downloadingTrack: string | null;
|
||||
isDownloading: boolean;
|
||||
bulkDownloadType: "all" | "selected" | null;
|
||||
@@ -55,6 +56,7 @@ export function ArtistInfo({
|
||||
sortBy,
|
||||
selectedTracks,
|
||||
downloadedTracks,
|
||||
failedTracks,
|
||||
downloadingTrack,
|
||||
isDownloading,
|
||||
bulkDownloadType,
|
||||
@@ -197,6 +199,7 @@ export function ArtistInfo({
|
||||
sortBy={sortBy}
|
||||
selectedTracks={selectedTracks}
|
||||
downloadedTracks={downloadedTracks}
|
||||
failedTracks={failedTracks}
|
||||
downloadingTrack={downloadingTrack}
|
||||
isDownloading={isDownloading}
|
||||
currentPage={currentPage}
|
||||
|
||||
@@ -26,6 +26,7 @@ interface PlaylistInfoProps {
|
||||
sortBy: string;
|
||||
selectedTracks: string[];
|
||||
downloadedTracks: Set<string>;
|
||||
failedTracks: Set<string>;
|
||||
downloadingTrack: string | null;
|
||||
isDownloading: boolean;
|
||||
bulkDownloadType: "all" | "selected" | null;
|
||||
@@ -52,6 +53,7 @@ export function PlaylistInfo({
|
||||
sortBy,
|
||||
selectedTracks,
|
||||
downloadedTracks,
|
||||
failedTracks,
|
||||
downloadingTrack,
|
||||
isDownloading,
|
||||
bulkDownloadType,
|
||||
@@ -151,6 +153,7 @@ export function PlaylistInfo({
|
||||
sortBy={sortBy}
|
||||
selectedTracks={selectedTracks}
|
||||
downloadedTracks={downloadedTracks}
|
||||
failedTracks={failedTracks}
|
||||
downloadingTrack={downloadingTrack}
|
||||
isDownloading={isDownloading}
|
||||
currentPage={currentPage}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { InputWithContext } from "@/components/ui/input-with-context";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Search, Info, XCircle } from "lucide-react";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
@@ -19,56 +18,52 @@ interface SearchBarProps {
|
||||
|
||||
export function SearchBar({ url, loading, onUrlChange, onFetch }: SearchBarProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="px-6 py-6 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="spotify-url">Spotify URL</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-4 w-4 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Supports track, album, playlist, and artist URLs</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="relative flex-1">
|
||||
<InputWithContext
|
||||
id="spotify-url"
|
||||
placeholder="https://open.spotify.com/..."
|
||||
value={url}
|
||||
onChange={(e) => onUrlChange(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && onFetch()}
|
||||
className="pr-8"
|
||||
/>
|
||||
{url && (
|
||||
<button
|
||||
type="button"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
|
||||
onClick={() => onUrlChange("")}
|
||||
>
|
||||
<XCircle className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Button onClick={onFetch} disabled={loading}>
|
||||
{loading ? (
|
||||
<>
|
||||
<Spinner />
|
||||
Fetching...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Search className="h-4 w-4" />
|
||||
Fetch
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="spotify-url">Spotify URL</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-4 w-4 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>Supports track, album, playlist, and artist URLs</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="relative flex-1">
|
||||
<InputWithContext
|
||||
id="spotify-url"
|
||||
placeholder="https://open.spotify.com/..."
|
||||
value={url}
|
||||
onChange={(e) => onUrlChange(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && onFetch()}
|
||||
className="pr-8"
|
||||
/>
|
||||
{url && (
|
||||
<button
|
||||
type="button"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
|
||||
onClick={() => onUrlChange("")}
|
||||
>
|
||||
<XCircle className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Button onClick={onFetch} disabled={loading}>
|
||||
{loading ? (
|
||||
<>
|
||||
<Spinner />
|
||||
Fetching...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Search className="h-4 w-4" />
|
||||
Fetch
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export function TitleBar() {
|
||||
/>
|
||||
|
||||
{/* Window control buttons */}
|
||||
<div className="absolute top-4 left-4 z-50 flex gap-2">
|
||||
<div className="fixed top-4 left-4 z-50 flex gap-2">
|
||||
<button
|
||||
onClick={handleClose}
|
||||
onMouseEnter={() => setHoveredButton("close")}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Download, FolderOpen } from "lucide-react";
|
||||
import { Download, FolderOpen, CheckCircle, XCircle } from "lucide-react";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import type { TrackMetadata } from "@/types/api";
|
||||
|
||||
@@ -9,6 +9,7 @@ interface TrackInfoProps {
|
||||
isDownloading: boolean;
|
||||
downloadingTrack: string | null;
|
||||
isDownloaded: boolean;
|
||||
isFailed: boolean;
|
||||
onDownload: (isrc: string, name: string, artists: string, albumName?: string, spotifyId?: string) => void;
|
||||
onOpenFolder: () => void;
|
||||
}
|
||||
@@ -18,6 +19,7 @@ export function TrackInfo({
|
||||
isDownloading,
|
||||
downloadingTrack,
|
||||
isDownloaded,
|
||||
isFailed,
|
||||
onDownload,
|
||||
onOpenFolder,
|
||||
}: TrackInfoProps) {
|
||||
@@ -34,7 +36,15 @@ export function TrackInfo({
|
||||
)}
|
||||
<div className="flex-1 space-y-4 min-w-0">
|
||||
<div className="space-y-1">
|
||||
<h1 className="text-3xl font-bold wrap-break-word">{track.name}</h1>
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-3xl font-bold wrap-break-word">{track.name}</h1>
|
||||
{isDownloaded && (
|
||||
<CheckCircle className="h-6 w-6 text-green-500 shrink-0" />
|
||||
)}
|
||||
{isFailed && (
|
||||
<XCircle className="h-6 w-6 text-red-500 shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-lg text-muted-foreground">{track.artists}</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Download, CheckCircle } from "lucide-react";
|
||||
import { Download, CheckCircle, XCircle } from "lucide-react";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import {
|
||||
Pagination,
|
||||
@@ -18,6 +18,7 @@ interface TrackListProps {
|
||||
sortBy: string;
|
||||
selectedTracks: string[];
|
||||
downloadedTracks: Set<string>;
|
||||
failedTracks: Set<string>;
|
||||
downloadingTrack: string | null;
|
||||
isDownloading: boolean;
|
||||
currentPage: number;
|
||||
@@ -36,6 +37,7 @@ export function TrackList({
|
||||
sortBy,
|
||||
selectedTracks,
|
||||
downloadedTracks,
|
||||
failedTracks,
|
||||
downloadingTrack,
|
||||
isDownloading,
|
||||
currentPage,
|
||||
@@ -165,6 +167,9 @@ export function TrackList({
|
||||
{downloadedTracks.has(track.isrc) && (
|
||||
<CheckCircle className="h-4 w-4 text-green-500 shrink-0" />
|
||||
)}
|
||||
{failedTracks.has(track.isrc) && (
|
||||
<XCircle className="h-4 w-4 text-red-500 shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{track.artists}
|
||||
|
||||
@@ -11,6 +11,7 @@ export function useDownload() {
|
||||
const [downloadingTrack, setDownloadingTrack] = useState<string | null>(null);
|
||||
const [bulkDownloadType, setBulkDownloadType] = useState<"all" | "selected" | null>(null);
|
||||
const [downloadedTracks, setDownloadedTracks] = useState<Set<string>>(new Set());
|
||||
const [failedTracks, setFailedTracks] = useState<Set<string>>(new Set());
|
||||
const [currentDownloadInfo, setCurrentDownloadInfo] = useState<{
|
||||
name: string;
|
||||
artists: string;
|
||||
@@ -57,50 +58,100 @@ export function useDownload() {
|
||||
}
|
||||
|
||||
if (service === "auto") {
|
||||
// Try Tidal first
|
||||
try {
|
||||
const tidalResponse = await downloadTrack({
|
||||
isrc,
|
||||
service: "tidal",
|
||||
query,
|
||||
track_name: trackName,
|
||||
artist_name: artistName,
|
||||
album_name: albumName,
|
||||
output_dir: outputDir,
|
||||
filename_format: settings.filenameFormat,
|
||||
track_number: settings.trackNumber,
|
||||
position,
|
||||
use_album_track_number: useAlbumTrackNumber,
|
||||
});
|
||||
|
||||
if (tidalResponse.success) {
|
||||
return tidalResponse;
|
||||
// Get all streaming URLs once from song.link API
|
||||
let streamingURLs: any = null;
|
||||
if (spotifyId) {
|
||||
try {
|
||||
const { GetStreamingURLs } = await import("../../wailsjs/go/main/App");
|
||||
const urlsJson = await GetStreamingURLs(spotifyId);
|
||||
streamingURLs = JSON.parse(urlsJson);
|
||||
} catch (err) {
|
||||
console.error("Failed to get streaming URLs:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Try Tidal first
|
||||
if (streamingURLs?.tidal_url) {
|
||||
try {
|
||||
const tidalResponse = await downloadTrack({
|
||||
isrc,
|
||||
service: "tidal",
|
||||
query,
|
||||
track_name: trackName,
|
||||
artist_name: artistName,
|
||||
album_name: albumName,
|
||||
output_dir: outputDir,
|
||||
filename_format: settings.filenameFormat,
|
||||
track_number: settings.trackNumber,
|
||||
position,
|
||||
use_album_track_number: useAlbumTrackNumber,
|
||||
spotify_id: spotifyId,
|
||||
service_url: streamingURLs.tidal_url,
|
||||
});
|
||||
|
||||
if (tidalResponse.success) {
|
||||
return tidalResponse;
|
||||
}
|
||||
console.log("Tidal failed, trying Deezer...");
|
||||
} catch (tidalErr) {
|
||||
console.log("Tidal error:", tidalErr);
|
||||
}
|
||||
} catch (tidalErr) {
|
||||
// Tidal failed, continue to Deezer
|
||||
}
|
||||
|
||||
// Try Deezer second
|
||||
try {
|
||||
const deezerResponse = await downloadTrack({
|
||||
isrc,
|
||||
service: "deezer",
|
||||
query,
|
||||
track_name: trackName,
|
||||
artist_name: artistName,
|
||||
album_name: albumName,
|
||||
output_dir: outputDir,
|
||||
filename_format: settings.filenameFormat,
|
||||
track_number: settings.trackNumber,
|
||||
position,
|
||||
use_album_track_number: useAlbumTrackNumber,
|
||||
});
|
||||
if (streamingURLs?.deezer_url) {
|
||||
try {
|
||||
const deezerResponse = await downloadTrack({
|
||||
isrc,
|
||||
service: "deezer",
|
||||
query,
|
||||
track_name: trackName,
|
||||
artist_name: artistName,
|
||||
album_name: albumName,
|
||||
output_dir: outputDir,
|
||||
filename_format: settings.filenameFormat,
|
||||
track_number: settings.trackNumber,
|
||||
position,
|
||||
use_album_track_number: useAlbumTrackNumber,
|
||||
spotify_id: spotifyId,
|
||||
service_url: streamingURLs.deezer_url,
|
||||
});
|
||||
|
||||
if (deezerResponse.success) {
|
||||
return deezerResponse;
|
||||
if (deezerResponse.success) {
|
||||
return deezerResponse;
|
||||
}
|
||||
console.log("Deezer failed, trying Amazon...");
|
||||
} catch (deezerErr) {
|
||||
console.log("Deezer error:", deezerErr);
|
||||
}
|
||||
}
|
||||
|
||||
// Try Amazon third
|
||||
if (streamingURLs?.amazon_url) {
|
||||
try {
|
||||
const amazonResponse = await downloadTrack({
|
||||
isrc,
|
||||
service: "amazon",
|
||||
query,
|
||||
track_name: trackName,
|
||||
artist_name: artistName,
|
||||
album_name: albumName,
|
||||
output_dir: outputDir,
|
||||
filename_format: settings.filenameFormat,
|
||||
track_number: settings.trackNumber,
|
||||
position,
|
||||
use_album_track_number: useAlbumTrackNumber,
|
||||
spotify_id: spotifyId,
|
||||
service_url: streamingURLs.amazon_url,
|
||||
});
|
||||
|
||||
if (amazonResponse.success) {
|
||||
return amazonResponse;
|
||||
}
|
||||
console.log("Amazon failed, trying Qobuz...");
|
||||
} catch (amazonErr) {
|
||||
console.log("Amazon error:", amazonErr);
|
||||
}
|
||||
} catch (deezerErr) {
|
||||
// Deezer failed, continue to Qobuz
|
||||
}
|
||||
|
||||
// Try Qobuz as last fallback
|
||||
@@ -187,6 +238,7 @@ export function useDownload() {
|
||||
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
let skippedCount = 0;
|
||||
const total = selectedTracks.length;
|
||||
|
||||
for (let i = 0; i < selectedTracks.length; i++) {
|
||||
@@ -221,13 +273,25 @@ export function useDownload() {
|
||||
);
|
||||
|
||||
if (response.success) {
|
||||
successCount++;
|
||||
if (response.already_exists) {
|
||||
skippedCount++;
|
||||
console.log(`Skipped: ${track?.name} - ${track?.artists} (already exists)`);
|
||||
} else {
|
||||
successCount++;
|
||||
}
|
||||
setDownloadedTracks((prev) => new Set(prev).add(isrc));
|
||||
setFailedTracks((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(isrc); // Remove from failed if it was there
|
||||
return newSet;
|
||||
});
|
||||
} else {
|
||||
errorCount++;
|
||||
setFailedTracks((prev) => new Set(prev).add(isrc));
|
||||
}
|
||||
} catch (err) {
|
||||
errorCount++;
|
||||
setFailedTracks((prev) => new Set(prev).add(isrc));
|
||||
}
|
||||
|
||||
setDownloadProgress(Math.round(((i + 1) / total) * 100));
|
||||
@@ -239,10 +303,22 @@ export function useDownload() {
|
||||
setBulkDownloadType(null);
|
||||
shouldStopDownloadRef.current = false;
|
||||
|
||||
if (errorCount === 0) {
|
||||
// Build summary message
|
||||
if (errorCount === 0 && skippedCount === 0) {
|
||||
toast.success(`Downloaded ${successCount} tracks successfully`);
|
||||
} else if (errorCount === 0 && successCount === 0) {
|
||||
// All skipped
|
||||
toast.info(`${skippedCount} tracks already exist`);
|
||||
} else if (errorCount === 0) {
|
||||
// Mix of downloaded and skipped
|
||||
toast.info(`${successCount} downloaded, ${skippedCount} skipped`);
|
||||
} else {
|
||||
toast.warning(`Downloaded ${successCount} tracks, ${errorCount} failed`);
|
||||
// Has errors
|
||||
const parts = [];
|
||||
if (successCount > 0) parts.push(`${successCount} downloaded`);
|
||||
if (skippedCount > 0) parts.push(`${skippedCount} skipped`);
|
||||
parts.push(`${errorCount} failed`);
|
||||
toast.warning(parts.join(", "));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -265,6 +341,7 @@ export function useDownload() {
|
||||
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
let skippedCount = 0;
|
||||
const total = tracksWithIsrc.length;
|
||||
|
||||
for (let i = 0; i < tracksWithIsrc.length; i++) {
|
||||
@@ -294,13 +371,25 @@ export function useDownload() {
|
||||
);
|
||||
|
||||
if (response.success) {
|
||||
successCount++;
|
||||
if (response.already_exists) {
|
||||
skippedCount++;
|
||||
console.log(`Skipped: ${track.name} - ${track.artists} (already exists)`);
|
||||
} else {
|
||||
successCount++;
|
||||
}
|
||||
setDownloadedTracks((prev) => new Set(prev).add(track.isrc));
|
||||
setFailedTracks((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
newSet.delete(track.isrc); // Remove from failed if it was there
|
||||
return newSet;
|
||||
});
|
||||
} else {
|
||||
errorCount++;
|
||||
setFailedTracks((prev) => new Set(prev).add(track.isrc));
|
||||
}
|
||||
} catch (err) {
|
||||
errorCount++;
|
||||
setFailedTracks((prev) => new Set(prev).add(track.isrc));
|
||||
}
|
||||
|
||||
setDownloadProgress(Math.round(((i + 1) / total) * 100));
|
||||
@@ -312,10 +401,22 @@ export function useDownload() {
|
||||
setBulkDownloadType(null);
|
||||
shouldStopDownloadRef.current = false;
|
||||
|
||||
if (errorCount === 0) {
|
||||
// Build summary message
|
||||
if (errorCount === 0 && skippedCount === 0) {
|
||||
toast.success(`Downloaded ${successCount} tracks successfully`);
|
||||
} else if (errorCount === 0 && successCount === 0) {
|
||||
// All skipped
|
||||
toast.info(`${skippedCount} tracks already exist`);
|
||||
} else if (errorCount === 0) {
|
||||
// Mix of downloaded and skipped
|
||||
toast.info(`${successCount} downloaded, ${skippedCount} skipped`);
|
||||
} else {
|
||||
toast.warning(`Downloaded ${successCount} tracks, ${errorCount} failed`);
|
||||
// Has errors
|
||||
const parts = [];
|
||||
if (successCount > 0) parts.push(`${successCount} downloaded`);
|
||||
if (skippedCount > 0) parts.push(`${skippedCount} skipped`);
|
||||
parts.push(`${errorCount} failed`);
|
||||
toast.warning(parts.join(", "));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -326,6 +427,7 @@ export function useDownload() {
|
||||
|
||||
const resetDownloadedTracks = () => {
|
||||
setDownloadedTracks(new Set());
|
||||
setFailedTracks(new Set());
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -334,6 +436,7 @@ export function useDownload() {
|
||||
downloadingTrack,
|
||||
bulkDownloadType,
|
||||
downloadedTracks,
|
||||
failedTracks,
|
||||
currentDownloadInfo,
|
||||
handleDownloadTrack,
|
||||
handleDownloadSelected,
|
||||
|
||||
@@ -112,6 +112,7 @@ export interface DownloadRequest {
|
||||
position?: number;
|
||||
use_album_track_number?: boolean;
|
||||
spotify_id?: string;
|
||||
service_url?: string;
|
||||
}
|
||||
|
||||
export interface DownloadResponse {
|
||||
|
||||
Reference in New Issue
Block a user