v6.6
This commit is contained in:
+201
-147
@@ -11,13 +11,14 @@ import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Search, X } from "lucide-react";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { getSettings, applyThemeMode } from "@/lib/settings";
|
||||
import { getSettings, applyThemeMode, applyFont } from "@/lib/settings";
|
||||
import { applyTheme } from "@/lib/themes";
|
||||
import { OpenFolder } from "../wailsjs/go/main/App";
|
||||
import { toastWithSound as toast } from "@/lib/toast-with-sound";
|
||||
|
||||
// Components
|
||||
import { TitleBar } from "@/components/TitleBar";
|
||||
import { Sidebar, type PageType } from "@/components/Sidebar";
|
||||
import { Header } from "@/components/Header";
|
||||
import { SearchBar } from "@/components/SearchBar";
|
||||
import { TrackInfo } from "@/components/TrackInfo";
|
||||
@@ -27,43 +28,48 @@ import { ArtistInfo } from "@/components/ArtistInfo";
|
||||
import { DownloadQueue } from "@/components/DownloadQueue";
|
||||
import { DownloadProgressToast } from "@/components/DownloadProgressToast";
|
||||
import { AudioAnalysisPage } from "@/components/AudioAnalysisPage";
|
||||
import { SettingsPage } from "@/components/SettingsPage";
|
||||
import { DebugLoggerPage } from "@/components/DebugLoggerPage";
|
||||
import type { HistoryItem } from "@/components/FetchHistory";
|
||||
|
||||
// Hooks
|
||||
import { useDownload } from "@/hooks/useDownload";
|
||||
import { useMetadata } from "@/hooks/useMetadata";
|
||||
import { useLyrics } from "@/hooks/useLyrics";
|
||||
import { useCover } from "@/hooks/useCover";
|
||||
import { useAvailability } from "@/hooks/useAvailability";
|
||||
import { useDownloadQueueDialog } from "@/hooks/useDownloadQueueDialog";
|
||||
|
||||
const HISTORY_KEY = "spotiflac_fetch_history";
|
||||
const MAX_HISTORY = 5;
|
||||
|
||||
type PageType = "main" | "audio-analysis";
|
||||
|
||||
function App() {
|
||||
const [currentPageView, setCurrentPageView] = useState<PageType>("main");
|
||||
const [currentPage, setCurrentPage] = useState<PageType>("main");
|
||||
const [spotifyUrl, setSpotifyUrl] = useState("");
|
||||
const [selectedTracks, setSelectedTracks] = useState<string[]>([]);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [sortBy, setSortBy] = useState<string>("default");
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [currentListPage, setCurrentListPage] = useState(1);
|
||||
const [hasUpdate, setHasUpdate] = useState(false);
|
||||
const [releaseDate, setReleaseDate] = useState<string | null>(null);
|
||||
const [fetchHistory, setFetchHistory] = useState<HistoryItem[]>([]);
|
||||
|
||||
const ITEMS_PER_PAGE = 50;
|
||||
const CURRENT_VERSION = "6.5";
|
||||
const CURRENT_VERSION = "6.6";
|
||||
|
||||
const download = useDownload();
|
||||
const metadata = useMetadata();
|
||||
const lyrics = useLyrics();
|
||||
const cover = useCover();
|
||||
const availability = useAvailability();
|
||||
const downloadQueue = useDownloadQueueDialog();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const settings = getSettings();
|
||||
applyThemeMode(settings.themeMode);
|
||||
applyTheme(settings.theme);
|
||||
applyFont(settings.fontFamily);
|
||||
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const handleChange = () => {
|
||||
@@ -88,9 +94,10 @@ function App() {
|
||||
setSearchQuery("");
|
||||
download.resetDownloadedTracks();
|
||||
lyrics.resetLyricsState();
|
||||
cover.resetCoverState();
|
||||
availability.clearAvailability();
|
||||
setSortBy("default");
|
||||
setCurrentPage(1);
|
||||
setCurrentListPage(1);
|
||||
}, [metadata.metadata]);
|
||||
|
||||
const checkForUpdates = async () => {
|
||||
@@ -99,9 +106,12 @@ function App() {
|
||||
"https://api.github.com/repos/afkarxyz/SpotiFLAC/releases/latest"
|
||||
);
|
||||
const data = await response.json();
|
||||
// tag_name format: "v6.1" -> extract "6.1"
|
||||
const latestVersion = data.tag_name?.replace(/^v/, "") || "";
|
||||
|
||||
if (data.published_at) {
|
||||
setReleaseDate(data.published_at);
|
||||
}
|
||||
|
||||
if (latestVersion && latestVersion > CURRENT_VERSION) {
|
||||
setHasUpdate(true);
|
||||
}
|
||||
@@ -166,7 +176,6 @@ function App() {
|
||||
}
|
||||
};
|
||||
|
||||
// Add to history when metadata is successfully fetched
|
||||
useEffect(() => {
|
||||
if (!metadata.metadata || !spotifyUrl) return;
|
||||
|
||||
@@ -217,7 +226,7 @@ function App() {
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
setSearchQuery(value);
|
||||
setCurrentPage(1);
|
||||
setCurrentListPage(1);
|
||||
};
|
||||
|
||||
const toggleTrackSelection = (isrc: string) => {
|
||||
@@ -250,6 +259,7 @@ function App() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const renderMetadata = () => {
|
||||
if (!metadata.metadata) return null;
|
||||
|
||||
@@ -269,9 +279,11 @@ function App() {
|
||||
skippedLyrics={lyrics.skippedLyrics.has(track.spotify_id || "")}
|
||||
checkingAvailability={availability.checkingTrackId === track.spotify_id}
|
||||
availability={availability.getAvailability(track.spotify_id || "")}
|
||||
downloadingCover={cover.downloadingCover}
|
||||
onDownload={download.handleDownloadTrack}
|
||||
onDownloadLyrics={lyrics.handleDownloadLyrics}
|
||||
onCheckAvailability={availability.checkAvailability}
|
||||
onDownloadCover={cover.handleDownloadCover}
|
||||
onOpenFolder={handleOpenFolder}
|
||||
/>
|
||||
);
|
||||
@@ -294,7 +306,7 @@ function App() {
|
||||
bulkDownloadType={download.bulkDownloadType}
|
||||
downloadProgress={download.downloadProgress}
|
||||
currentDownloadInfo={download.currentDownloadInfo}
|
||||
currentPage={currentPage}
|
||||
currentPage={currentListPage}
|
||||
itemsPerPage={ITEMS_PER_PAGE}
|
||||
downloadedLyrics={lyrics.downloadedLyrics}
|
||||
failedLyrics={lyrics.failedLyrics}
|
||||
@@ -302,6 +314,11 @@ function App() {
|
||||
downloadingLyricsTrack={lyrics.downloadingLyricsTrack}
|
||||
checkingAvailabilityTrack={availability.checkingTrackId}
|
||||
availabilityMap={availability.availabilityMap}
|
||||
downloadedCovers={cover.downloadedCovers}
|
||||
failedCovers={cover.failedCovers}
|
||||
skippedCovers={cover.skippedCovers}
|
||||
downloadingCoverTrack={cover.downloadingCoverTrack}
|
||||
isBulkDownloadingCovers={cover.isBulkDownloadingCovers}
|
||||
onSearchChange={handleSearchChange}
|
||||
onSortChange={setSortBy}
|
||||
onToggleTrack={toggleTrackSelection}
|
||||
@@ -310,14 +327,18 @@ function App() {
|
||||
onDownloadLyrics={(spotifyId, name, artists, albumName, _folderName, _isArtistDiscography, position) =>
|
||||
lyrics.handleDownloadLyrics(spotifyId, name, artists, albumName, album_info.name, false, position)
|
||||
}
|
||||
onDownloadCover={(coverUrl, trackName, artistName, albumName, _folderName, _isArtistDiscography, position, trackId) =>
|
||||
cover.handleDownloadCover(coverUrl, trackName, artistName, albumName, album_info.name, false, position, trackId)
|
||||
}
|
||||
onCheckAvailability={availability.checkAvailability}
|
||||
onDownloadAllCovers={() => cover.handleDownloadAllCovers(track_list, album_info.name)}
|
||||
onDownloadAll={() => download.handleDownloadAll(track_list, album_info.name)}
|
||||
onDownloadSelected={() =>
|
||||
download.handleDownloadSelected(selectedTracks, track_list, album_info.name)
|
||||
}
|
||||
onStopDownload={download.handleStopDownload}
|
||||
onOpenFolder={handleOpenFolder}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageChange={setCurrentListPage}
|
||||
onArtistClick={async (artist) => {
|
||||
const artistUrl = await metadata.handleArtistClick(artist);
|
||||
if (artistUrl) {
|
||||
@@ -351,7 +372,7 @@ function App() {
|
||||
bulkDownloadType={download.bulkDownloadType}
|
||||
downloadProgress={download.downloadProgress}
|
||||
currentDownloadInfo={download.currentDownloadInfo}
|
||||
currentPage={currentPage}
|
||||
currentPage={currentListPage}
|
||||
itemsPerPage={ITEMS_PER_PAGE}
|
||||
downloadedLyrics={lyrics.downloadedLyrics}
|
||||
failedLyrics={lyrics.failedLyrics}
|
||||
@@ -359,6 +380,11 @@ function App() {
|
||||
downloadingLyricsTrack={lyrics.downloadingLyricsTrack}
|
||||
checkingAvailabilityTrack={availability.checkingTrackId}
|
||||
availabilityMap={availability.availabilityMap}
|
||||
downloadedCovers={cover.downloadedCovers}
|
||||
failedCovers={cover.failedCovers}
|
||||
skippedCovers={cover.skippedCovers}
|
||||
downloadingCoverTrack={cover.downloadingCoverTrack}
|
||||
isBulkDownloadingCovers={cover.isBulkDownloadingCovers}
|
||||
onSearchChange={handleSearchChange}
|
||||
onSortChange={setSortBy}
|
||||
onToggleTrack={toggleTrackSelection}
|
||||
@@ -367,7 +393,11 @@ function App() {
|
||||
onDownloadLyrics={(spotifyId, name, artists, albumName, _folderName, _isArtistDiscography, position) =>
|
||||
lyrics.handleDownloadLyrics(spotifyId, name, artists, albumName, playlist_info.owner.name, false, position)
|
||||
}
|
||||
onDownloadCover={(coverUrl, trackName, artistName, albumName, _folderName, _isArtistDiscography, position, trackId) =>
|
||||
cover.handleDownloadCover(coverUrl, trackName, artistName, albumName, playlist_info.owner.name, false, position, trackId)
|
||||
}
|
||||
onCheckAvailability={availability.checkAvailability}
|
||||
onDownloadAllCovers={() => cover.handleDownloadAllCovers(track_list, playlist_info.owner.name)}
|
||||
onDownloadAll={() => download.handleDownloadAll(track_list, playlist_info.owner.name)}
|
||||
onDownloadSelected={() =>
|
||||
download.handleDownloadSelected(
|
||||
@@ -378,7 +408,7 @@ function App() {
|
||||
}
|
||||
onStopDownload={download.handleStopDownload}
|
||||
onOpenFolder={handleOpenFolder}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageChange={setCurrentListPage}
|
||||
onAlbumClick={metadata.handleAlbumClick}
|
||||
onArtistClick={async (artist) => {
|
||||
const artistUrl = await metadata.handleArtistClick(artist);
|
||||
@@ -414,7 +444,7 @@ function App() {
|
||||
bulkDownloadType={download.bulkDownloadType}
|
||||
downloadProgress={download.downloadProgress}
|
||||
currentDownloadInfo={download.currentDownloadInfo}
|
||||
currentPage={currentPage}
|
||||
currentPage={currentListPage}
|
||||
itemsPerPage={ITEMS_PER_PAGE}
|
||||
downloadedLyrics={lyrics.downloadedLyrics}
|
||||
failedLyrics={lyrics.failedLyrics}
|
||||
@@ -422,6 +452,11 @@ function App() {
|
||||
downloadingLyricsTrack={lyrics.downloadingLyricsTrack}
|
||||
checkingAvailabilityTrack={availability.checkingTrackId}
|
||||
availabilityMap={availability.availabilityMap}
|
||||
downloadedCovers={cover.downloadedCovers}
|
||||
failedCovers={cover.failedCovers}
|
||||
skippedCovers={cover.skippedCovers}
|
||||
downloadingCoverTrack={cover.downloadingCoverTrack}
|
||||
isBulkDownloadingCovers={cover.isBulkDownloadingCovers}
|
||||
onSearchChange={handleSearchChange}
|
||||
onSortChange={setSortBy}
|
||||
onToggleTrack={toggleTrackSelection}
|
||||
@@ -430,7 +465,11 @@ function App() {
|
||||
onDownloadLyrics={(spotifyId, name, artists, albumName, _folderName, isArtistDiscography, position) =>
|
||||
lyrics.handleDownloadLyrics(spotifyId, name, artists, albumName, artist_info.name, isArtistDiscography, position)
|
||||
}
|
||||
onDownloadCover={(coverUrl, trackName, artistName, albumName, _folderName, isArtistDiscography, position, trackId) =>
|
||||
cover.handleDownloadCover(coverUrl, trackName, artistName, albumName, artist_info.name, isArtistDiscography, position, trackId)
|
||||
}
|
||||
onCheckAvailability={availability.checkAvailability}
|
||||
onDownloadAllCovers={() => cover.handleDownloadAllCovers(track_list, artist_info.name, true)}
|
||||
onDownloadAll={() => download.handleDownloadAll(track_list, artist_info.name, true)}
|
||||
onDownloadSelected={() =>
|
||||
download.handleDownloadSelected(selectedTracks, track_list, artist_info.name, true)
|
||||
@@ -444,7 +483,7 @@ function App() {
|
||||
setSpotifyUrl(artistUrl);
|
||||
}
|
||||
}}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageChange={setCurrentListPage}
|
||||
onTrackClick={async (track) => {
|
||||
if (track.external_urls) {
|
||||
setSpotifyUrl(track.external_urls);
|
||||
@@ -458,144 +497,159 @@ function App() {
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
const renderPage = () => {
|
||||
switch (currentPage) {
|
||||
case "settings":
|
||||
return <SettingsPage />;
|
||||
case "debug":
|
||||
return <DebugLoggerPage />;
|
||||
case "audio-analysis":
|
||||
return <AudioAnalysisPage />;
|
||||
default:
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
version={CURRENT_VERSION}
|
||||
hasUpdate={hasUpdate}
|
||||
releaseDate={releaseDate}
|
||||
/>
|
||||
|
||||
{/* Timeout Dialog */}
|
||||
<Dialog
|
||||
open={metadata.showTimeoutDialog}
|
||||
onOpenChange={metadata.setShowTimeoutDialog}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[425px] p-6 [&>button]:hidden">
|
||||
<div className="absolute right-4 top-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 opacity-70 hover:opacity-100"
|
||||
onClick={() => metadata.setShowTimeoutDialog(false)}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<DialogTitle className="text-sm font-medium">Fetch Artist</DialogTitle>
|
||||
<DialogDescription>
|
||||
Set timeout for fetching metadata. Longer timeout is recommended for artists
|
||||
with large discography.
|
||||
</DialogDescription>
|
||||
{metadata.pendingArtistName && (
|
||||
<div className="py-2">
|
||||
<p className="font-medium bg-muted/50 rounded-md px-3 py-2">{metadata.pendingArtistName}</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="timeout">Timeout (seconds)</Label>
|
||||
<Input
|
||||
id="timeout"
|
||||
type="number"
|
||||
min="10"
|
||||
max="600"
|
||||
value={metadata.timeoutValue}
|
||||
onChange={(e) => metadata.setTimeoutValue(Number(e.target.value))}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Default: 60 seconds. For large discographies, try 300-600 seconds (5-10
|
||||
minutes).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => metadata.setShowTimeoutDialog(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={metadata.handleConfirmFetch}>
|
||||
<Search className="h-4 w-4" />
|
||||
Fetch
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Album Fetch Dialog */}
|
||||
<Dialog open={metadata.showAlbumDialog} onOpenChange={metadata.setShowAlbumDialog}>
|
||||
<DialogContent className="sm:max-w-[425px] p-6 [&>button]:hidden">
|
||||
<div className="absolute right-4 top-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 opacity-70 hover:opacity-100"
|
||||
onClick={() => metadata.setShowAlbumDialog(false)}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<DialogTitle className="text-sm font-medium">Fetch Album</DialogTitle>
|
||||
<DialogDescription>
|
||||
Do you want to fetch metadata for this album?
|
||||
</DialogDescription>
|
||||
{metadata.selectedAlbum && (
|
||||
<div className="py-2">
|
||||
<p className="font-medium bg-muted/50 rounded-md px-3 py-2">{metadata.selectedAlbum.name}</p>
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => metadata.setShowAlbumDialog(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={async () => {
|
||||
const albumUrl = await metadata.handleConfirmAlbumFetch();
|
||||
if (albumUrl) {
|
||||
setSpotifyUrl(albumUrl);
|
||||
}
|
||||
}}>
|
||||
<Search className="h-4 w-4" />
|
||||
Fetch Album
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<SearchBar
|
||||
url={spotifyUrl}
|
||||
loading={metadata.loading}
|
||||
onUrlChange={setSpotifyUrl}
|
||||
onFetch={handleFetchMetadata}
|
||||
history={fetchHistory}
|
||||
onHistorySelect={handleHistorySelect}
|
||||
onHistoryRemove={removeFromHistory}
|
||||
hasResult={!!metadata.metadata}
|
||||
/>
|
||||
|
||||
{metadata.metadata && renderMetadata()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className="min-h-screen bg-background flex flex-col">
|
||||
<TitleBar />
|
||||
<div className="flex-1 p-4 md:p-8">
|
||||
<Sidebar currentPage={currentPage} onPageChange={setCurrentPage} />
|
||||
|
||||
{/* Main content area with sidebar offset */}
|
||||
<div className="flex-1 ml-14 mt-10 p-4 md:p-8">
|
||||
<div className="max-w-4xl mx-auto space-y-6">
|
||||
{currentPageView === "audio-analysis" ? (
|
||||
<AudioAnalysisPage onBack={() => setCurrentPageView("main")} />
|
||||
) : (
|
||||
<>
|
||||
<Header
|
||||
version={CURRENT_VERSION}
|
||||
hasUpdate={hasUpdate}
|
||||
onOpenAudioAnalysis={() => setCurrentPageView("audio-analysis")}
|
||||
/>
|
||||
|
||||
{/* Download Progress Toast - Bottom Left */}
|
||||
<DownloadProgressToast onClick={downloadQueue.openQueue} />
|
||||
|
||||
{/* Download Queue Dialog */}
|
||||
<DownloadQueue
|
||||
isOpen={downloadQueue.isOpen}
|
||||
onClose={downloadQueue.closeQueue}
|
||||
/>
|
||||
|
||||
{/* Timeout Dialog */}
|
||||
<Dialog
|
||||
open={metadata.showTimeoutDialog}
|
||||
onOpenChange={metadata.setShowTimeoutDialog}
|
||||
>
|
||||
<DialogContent className="sm:max-w-[425px] p-6 [&>button]:hidden">
|
||||
<div className="absolute right-4 top-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 opacity-70 hover:opacity-100"
|
||||
onClick={() => metadata.setShowTimeoutDialog(false)}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<DialogTitle className="text-sm font-medium">Fetch Artist</DialogTitle>
|
||||
<DialogDescription>
|
||||
Set timeout for fetching metadata. Longer timeout is recommended for artists
|
||||
with large discography.
|
||||
</DialogDescription>
|
||||
{metadata.pendingArtistName && (
|
||||
<div className="py-2">
|
||||
<p className="font-medium bg-muted/50 rounded-md px-3 py-2">{metadata.pendingArtistName}</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="timeout">Timeout (seconds)</Label>
|
||||
<Input
|
||||
id="timeout"
|
||||
type="number"
|
||||
min="10"
|
||||
max="600"
|
||||
value={metadata.timeoutValue}
|
||||
onChange={(e) => metadata.setTimeoutValue(Number(e.target.value))}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Default: 60 seconds. For large discographies, try 300-600 seconds (5-10
|
||||
minutes).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => metadata.setShowTimeoutDialog(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={metadata.handleConfirmFetch}>
|
||||
<Search className="h-4 w-4" />
|
||||
Fetch
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Album Fetch Dialog */}
|
||||
<Dialog open={metadata.showAlbumDialog} onOpenChange={metadata.setShowAlbumDialog}>
|
||||
<DialogContent className="sm:max-w-[425px] p-6 [&>button]:hidden">
|
||||
<div className="absolute right-4 top-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 opacity-70 hover:opacity-100"
|
||||
onClick={() => metadata.setShowAlbumDialog(false)}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<DialogTitle className="text-sm font-medium">Fetch Album</DialogTitle>
|
||||
<DialogDescription>
|
||||
Do you want to fetch metadata for this album?
|
||||
</DialogDescription>
|
||||
{metadata.selectedAlbum && (
|
||||
<div className="py-2">
|
||||
<p className="font-medium bg-muted/50 rounded-md px-3 py-2">{metadata.selectedAlbum.name}</p>
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => metadata.setShowAlbumDialog(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={async () => {
|
||||
const albumUrl = await metadata.handleConfirmAlbumFetch();
|
||||
if (albumUrl) {
|
||||
setSpotifyUrl(albumUrl);
|
||||
}
|
||||
}}>
|
||||
<Search className="h-4 w-4" />
|
||||
Fetch Album
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<SearchBar
|
||||
url={spotifyUrl}
|
||||
loading={metadata.loading}
|
||||
onUrlChange={setSpotifyUrl}
|
||||
onFetch={handleFetchMetadata}
|
||||
history={fetchHistory}
|
||||
onHistorySelect={handleHistorySelect}
|
||||
onHistoryRemove={removeFromHistory}
|
||||
hasResult={!!metadata.metadata}
|
||||
/>
|
||||
|
||||
{metadata.metadata && renderMetadata()}
|
||||
</>
|
||||
)}
|
||||
{renderPage()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Download Progress Toast - Bottom Left */}
|
||||
<DownloadProgressToast onClick={downloadQueue.openQueue} />
|
||||
|
||||
{/* Download Queue Dialog */}
|
||||
<DownloadQueue
|
||||
isOpen={downloadQueue.isOpen}
|
||||
onClose={downloadQueue.closeQueue}
|
||||
/>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user