v5.7-beta1
This commit is contained in:
@@ -58,3 +58,4 @@ temp/
|
|||||||
|
|
||||||
# Build notes (optional - uncomment if you don't want to commit)
|
# Build notes (optional - uncomment if you don't want to commit)
|
||||||
# BUILD_NOTES.md
|
# BUILD_NOTES.md
|
||||||
|
build.txt
|
||||||
@@ -159,6 +159,11 @@ func (a *App) OpenFolder(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SelectFolder opens a folder selection dialog and returns the selected path
|
||||||
|
func (a *App) SelectFolder(defaultPath string) (string, error) {
|
||||||
|
return backend.SelectFolderDialog(a.ctx, defaultPath)
|
||||||
|
}
|
||||||
|
|
||||||
// GetDefaults returns the default configuration
|
// GetDefaults returns the default configuration
|
||||||
func (a *App) GetDefaults() map[string]string {
|
func (a *App) GetDefaults() map[string]string {
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package backend
|
package backend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func OpenFolderInExplorer(path string) error {
|
func OpenFolderInExplorer(path string) error {
|
||||||
@@ -21,3 +24,27 @@ func OpenFolderInExplorer(path string) error {
|
|||||||
|
|
||||||
return cmd.Start()
|
return cmd.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SelectFolderDialog(ctx context.Context, defaultPath string) (string, error) {
|
||||||
|
// If defaultPath is empty, use default music path
|
||||||
|
if defaultPath == "" {
|
||||||
|
defaultPath = GetDefaultMusicPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
options := wailsRuntime.OpenDialogOptions{
|
||||||
|
Title: "Select Download Folder",
|
||||||
|
DefaultDirectory: defaultPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedPath, err := wailsRuntime.OpenDirectoryDialog(ctx, options)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user cancelled, selectedPath will be empty
|
||||||
|
if selectedPath == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedPath, nil
|
||||||
|
}
|
||||||
|
|||||||
+153
-13
@@ -19,7 +19,7 @@ import type { SpotifyMetadataResponse, TrackMetadata } from "@/types/api";
|
|||||||
import { Settings } from "@/components/Settings";
|
import { Settings } from "@/components/Settings";
|
||||||
import { getSettings, applyThemeMode } from "@/lib/settings";
|
import { getSettings, applyThemeMode } from "@/lib/settings";
|
||||||
import { applyTheme } from "@/lib/themes";
|
import { applyTheme } from "@/lib/themes";
|
||||||
import { Download, Search, CheckCircle, Info } from "lucide-react";
|
import { Download, Search, CheckCircle, Info, XCircle, ArrowUpDown, StopCircle, FolderOpen } from "lucide-react";
|
||||||
import { toastWithSound as toast } from "@/lib/toast-with-sound";
|
import { toastWithSound as toast } from "@/lib/toast-with-sound";
|
||||||
import {
|
import {
|
||||||
Pagination,
|
Pagination,
|
||||||
@@ -36,7 +36,15 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import { joinPath, sanitizePath } from "./lib/utils";
|
import { joinPath, sanitizePath } from "./lib/utils";
|
||||||
|
import { OpenFolder } from "../wailsjs/go/main/App";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [spotifyUrl, setSpotifyUrl] = useState("");
|
const [spotifyUrl, setSpotifyUrl] = useState("");
|
||||||
@@ -57,6 +65,7 @@ function App() {
|
|||||||
const [hasUpdate, setHasUpdate] = useState(false);
|
const [hasUpdate, setHasUpdate] = useState(false);
|
||||||
const [showAlbumDialog, setShowAlbumDialog] = useState(false);
|
const [showAlbumDialog, setShowAlbumDialog] = useState(false);
|
||||||
const [selectedAlbum, setSelectedAlbum] = useState<{ id: string; name: string; external_urls: string } | null>(null);
|
const [selectedAlbum, setSelectedAlbum] = useState<{ id: string; name: string; external_urls: string } | null>(null);
|
||||||
|
const [sortBy, setSortBy] = useState<string>("default");
|
||||||
const shouldStopDownloadRef = useRef(false);
|
const shouldStopDownloadRef = useRef(false);
|
||||||
|
|
||||||
const ITEMS_PER_PAGE = 50;
|
const ITEMS_PER_PAGE = 50;
|
||||||
@@ -104,10 +113,11 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Clear selection, search, downloaded tracks, and reset page when metadata changes
|
// Clear selection, search, downloaded tracks, sort, and reset page when metadata changes
|
||||||
setSelectedTracks([]);
|
setSelectedTracks([]);
|
||||||
setSearchQuery("");
|
setSearchQuery("");
|
||||||
setDownloadedTracks(new Set());
|
setDownloadedTracks(new Set());
|
||||||
|
setSortBy("default");
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
}, [metadata]);
|
}, [metadata]);
|
||||||
|
|
||||||
@@ -485,6 +495,21 @@ function App() {
|
|||||||
toast.info('Stopping download...');
|
toast.info('Stopping download...');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenFolder = async () => {
|
||||||
|
const settings = getSettings();
|
||||||
|
if (!settings.downloadPath) {
|
||||||
|
toast.error("Download path not set");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await OpenFolder(settings.downloadPath);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error opening folder:", error);
|
||||||
|
toast.error(`Error opening folder: ${error}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const renderDownloadProgress = () => {
|
const renderDownloadProgress = () => {
|
||||||
if (!isDownloading) return null;
|
if (!isDownloading) return null;
|
||||||
|
|
||||||
@@ -497,6 +522,7 @@ function App() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleStopDownload}
|
onClick={handleStopDownload}
|
||||||
>
|
>
|
||||||
|
<StopCircle className="h-4 w-4 mr-2" />
|
||||||
Stop
|
Stop
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -508,7 +534,7 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderTrackList = (tracks: TrackMetadata[], showCheckboxes: boolean = false, hideAlbumColumn: boolean = false) => {
|
const renderTrackList = (tracks: TrackMetadata[], showCheckboxes: boolean = false, hideAlbumColumn: boolean = false) => {
|
||||||
const filteredTracks = tracks.filter(track => {
|
let filteredTracks = tracks.filter(track => {
|
||||||
if (!searchQuery) return true;
|
if (!searchQuery) return true;
|
||||||
const query = searchQuery.toLowerCase();
|
const query = searchQuery.toLowerCase();
|
||||||
return (
|
return (
|
||||||
@@ -518,6 +544,33 @@ function App() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Apply sorting
|
||||||
|
if (sortBy === "title-asc") {
|
||||||
|
filteredTracks = [...filteredTracks].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
} else if (sortBy === "title-desc") {
|
||||||
|
filteredTracks = [...filteredTracks].sort((a, b) => b.name.localeCompare(a.name));
|
||||||
|
} else if (sortBy === "artist-asc") {
|
||||||
|
filteredTracks = [...filteredTracks].sort((a, b) => a.artists.localeCompare(b.artists));
|
||||||
|
} else if (sortBy === "artist-desc") {
|
||||||
|
filteredTracks = [...filteredTracks].sort((a, b) => b.artists.localeCompare(a.artists));
|
||||||
|
} else if (sortBy === "duration-asc") {
|
||||||
|
filteredTracks = [...filteredTracks].sort((a, b) => a.duration_ms - b.duration_ms);
|
||||||
|
} else if (sortBy === "duration-desc") {
|
||||||
|
filteredTracks = [...filteredTracks].sort((a, b) => b.duration_ms - a.duration_ms);
|
||||||
|
} else if (sortBy === "downloaded") {
|
||||||
|
filteredTracks = [...filteredTracks].sort((a, b) => {
|
||||||
|
const aDownloaded = downloadedTracks.has(a.isrc);
|
||||||
|
const bDownloaded = downloadedTracks.has(b.isrc);
|
||||||
|
return (bDownloaded ? 1 : 0) - (aDownloaded ? 1 : 0);
|
||||||
|
});
|
||||||
|
} else if (sortBy === "not-downloaded") {
|
||||||
|
filteredTracks = [...filteredTracks].sort((a, b) => {
|
||||||
|
const aDownloaded = downloadedTracks.has(a.isrc);
|
||||||
|
const bDownloaded = downloadedTracks.has(b.isrc);
|
||||||
|
return (aDownloaded ? 1 : 0) - (bDownloaded ? 1 : 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Pagination
|
// Pagination
|
||||||
const totalPages = Math.ceil(filteredTracks.length / ITEMS_PER_PAGE);
|
const totalPages = Math.ceil(filteredTracks.length / ITEMS_PER_PAGE);
|
||||||
const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
|
const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
|
||||||
@@ -676,7 +729,7 @@ function App() {
|
|||||||
const { track } = metadata;
|
const { track } = metadata;
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6">
|
<CardContent className="px-6">
|
||||||
<div className="flex gap-6 items-start">
|
<div className="flex gap-6 items-start">
|
||||||
{track.images && (
|
{track.images && (
|
||||||
<img
|
<img
|
||||||
@@ -726,7 +779,7 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6">
|
<CardContent className="px-6">
|
||||||
<div className="flex gap-6 items-start">
|
<div className="flex gap-6 items-start">
|
||||||
{album_info.images && (
|
{album_info.images && (
|
||||||
<img
|
<img
|
||||||
@@ -766,6 +819,12 @@ function App() {
|
|||||||
Download Selected ({selectedTracks.length})
|
Download Selected ({selectedTracks.length})
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{downloadedTracks.size > 0 && (
|
||||||
|
<Button onClick={handleOpenFolder} variant="outline" className="gap-2">
|
||||||
|
<FolderOpen className="h-4 w-4" />
|
||||||
|
Open Folder
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{renderDownloadProgress()}
|
{renderDownloadProgress()}
|
||||||
</div>
|
</div>
|
||||||
@@ -773,7 +832,8 @@ function App() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="relative">
|
<div className="flex gap-2">
|
||||||
|
<div className="relative flex-1">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search tracks..."
|
placeholder="Search tracks..."
|
||||||
@@ -782,6 +842,24 @@ function App() {
|
|||||||
className="pl-10"
|
className="pl-10"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<Select value={sortBy} onValueChange={setSortBy}>
|
||||||
|
<SelectTrigger className="w-[200px]">
|
||||||
|
<ArrowUpDown className="h-4 w-4 mr-2" />
|
||||||
|
<SelectValue placeholder="Sort by" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="default">Default</SelectItem>
|
||||||
|
<SelectItem value="title-asc">Title (A-Z)</SelectItem>
|
||||||
|
<SelectItem value="title-desc">Title (Z-A)</SelectItem>
|
||||||
|
<SelectItem value="artist-asc">Artist (A-Z)</SelectItem>
|
||||||
|
<SelectItem value="artist-desc">Artist (Z-A)</SelectItem>
|
||||||
|
<SelectItem value="duration-asc">Duration (Short)</SelectItem>
|
||||||
|
<SelectItem value="duration-desc">Duration (Long)</SelectItem>
|
||||||
|
<SelectItem value="downloaded">Downloaded</SelectItem>
|
||||||
|
<SelectItem value="not-downloaded">Not Downloaded</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
{renderTrackList(track_list, true, true)}
|
{renderTrackList(track_list, true, true)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -793,7 +871,7 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6">
|
<CardContent className="px-6">
|
||||||
<div className="flex gap-6 items-start">
|
<div className="flex gap-6 items-start">
|
||||||
{playlist_info.owner.images && (
|
{playlist_info.owner.images && (
|
||||||
<img
|
<img
|
||||||
@@ -833,6 +911,12 @@ function App() {
|
|||||||
Download Selected ({selectedTracks.length})
|
Download Selected ({selectedTracks.length})
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{downloadedTracks.size > 0 && (
|
||||||
|
<Button onClick={handleOpenFolder} variant="outline" className="gap-2">
|
||||||
|
<FolderOpen className="h-4 w-4" />
|
||||||
|
Open Folder
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{renderDownloadProgress()}
|
{renderDownloadProgress()}
|
||||||
</div>
|
</div>
|
||||||
@@ -840,7 +924,8 @@ function App() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="relative">
|
<div className="flex gap-2">
|
||||||
|
<div className="relative flex-1">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search tracks..."
|
placeholder="Search tracks..."
|
||||||
@@ -849,6 +934,24 @@ function App() {
|
|||||||
className="pl-10"
|
className="pl-10"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<Select value={sortBy} onValueChange={setSortBy}>
|
||||||
|
<SelectTrigger className="w-[200px]">
|
||||||
|
<ArrowUpDown className="h-4 w-4 mr-2" />
|
||||||
|
<SelectValue placeholder="Sort by" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="default">Default</SelectItem>
|
||||||
|
<SelectItem value="title-asc">Title (A-Z)</SelectItem>
|
||||||
|
<SelectItem value="title-desc">Title (Z-A)</SelectItem>
|
||||||
|
<SelectItem value="artist-asc">Artist (A-Z)</SelectItem>
|
||||||
|
<SelectItem value="artist-desc">Artist (Z-A)</SelectItem>
|
||||||
|
<SelectItem value="duration-asc">Duration (Short)</SelectItem>
|
||||||
|
<SelectItem value="duration-desc">Duration (Long)</SelectItem>
|
||||||
|
<SelectItem value="downloaded">Downloaded</SelectItem>
|
||||||
|
<SelectItem value="not-downloaded">Not Downloaded</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
{renderTrackList(track_list, true)}
|
{renderTrackList(track_list, true)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -860,7 +963,7 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6">
|
<CardContent className="px-6">
|
||||||
<div className="flex gap-6 items-start">
|
<div className="flex gap-6 items-start">
|
||||||
{artist_info.images && (
|
{artist_info.images && (
|
||||||
<img
|
<img
|
||||||
@@ -938,10 +1041,17 @@ function App() {
|
|||||||
Download Selected ({selectedTracks.length})
|
Download Selected ({selectedTracks.length})
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{downloadedTracks.size > 0 && (
|
||||||
|
<Button onClick={handleOpenFolder} size="sm" variant="outline" className="gap-2">
|
||||||
|
<FolderOpen className="h-4 w-4" />
|
||||||
|
Open Folder
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{renderDownloadProgress()}
|
{renderDownloadProgress()}
|
||||||
<div className="relative">
|
<div className="flex gap-2">
|
||||||
|
<div className="relative flex-1">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search tracks..."
|
placeholder="Search tracks..."
|
||||||
@@ -950,6 +1060,24 @@ function App() {
|
|||||||
className="pl-10"
|
className="pl-10"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<Select value={sortBy} onValueChange={setSortBy}>
|
||||||
|
<SelectTrigger className="w-[200px]">
|
||||||
|
<ArrowUpDown className="h-4 w-4 mr-2" />
|
||||||
|
<SelectValue placeholder="Sort by" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="default">Default</SelectItem>
|
||||||
|
<SelectItem value="title-asc">Title (A-Z)</SelectItem>
|
||||||
|
<SelectItem value="title-desc">Title (Z-A)</SelectItem>
|
||||||
|
<SelectItem value="artist-asc">Artist (A-Z)</SelectItem>
|
||||||
|
<SelectItem value="artist-desc">Artist (Z-A)</SelectItem>
|
||||||
|
<SelectItem value="duration-asc">Duration (Short)</SelectItem>
|
||||||
|
<SelectItem value="duration-desc">Duration (Long)</SelectItem>
|
||||||
|
<SelectItem value="downloaded">Downloaded</SelectItem>
|
||||||
|
<SelectItem value="not-downloaded">Not Downloaded</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
{renderTrackList(track_list, true)}
|
{renderTrackList(track_list, true)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -996,8 +1124,8 @@ function App() {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="text-center space-y-2">
|
<div className="text-center space-y-2">
|
||||||
<div className="flex items-center justify-center gap-3">
|
<div className="flex items-center justify-center gap-3">
|
||||||
<img src="/icon.svg" alt="SpotiFLAC" className="w-12 h-12" />
|
<img src="/icon.svg" alt="SpotiFLAC" className="w-12 h-12 cursor-pointer" onClick={() => window.location.reload()} />
|
||||||
<h1 className="text-4xl font-bold">SpotiFLAC</h1>
|
<h1 className="text-4xl font-bold cursor-pointer" onClick={() => window.location.reload()}>SpotiFLAC</h1>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Badge variant="default" asChild>
|
<Badge variant="default" asChild>
|
||||||
<a
|
<a
|
||||||
@@ -1117,7 +1245,7 @@ function App() {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="px-6 space-y-4">
|
<CardContent className="px-6 py-6 space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Label htmlFor="spotify-url">Spotify URL</Label>
|
<Label htmlFor="spotify-url">Spotify URL</Label>
|
||||||
@@ -1131,13 +1259,25 @@ function App() {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
<div className="relative flex-1">
|
||||||
<Input
|
<Input
|
||||||
id="spotify-url"
|
id="spotify-url"
|
||||||
placeholder="https://open.spotify.com/..."
|
placeholder="https://open.spotify.com/..."
|
||||||
value={spotifyUrl}
|
value={spotifyUrl}
|
||||||
onChange={(e) => setSpotifyUrl(e.target.value)}
|
onChange={(e) => setSpotifyUrl(e.target.value)}
|
||||||
onKeyDown={(e) => e.key === "Enter" && handleFetchMetadata()}
|
onKeyDown={(e) => e.key === "Enter" && handleFetchMetadata()}
|
||||||
|
className="pr-8"
|
||||||
/>
|
/>
|
||||||
|
{spotifyUrl && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
onClick={() => setSpotifyUrl("")}
|
||||||
|
>
|
||||||
|
<XCircle className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<Button onClick={handleFetchMetadata} disabled={loading}>
|
<Button onClick={handleFetchMetadata} disabled={loading}>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|||||||
import { Settings as SettingsIcon, FolderOpen, Save, RotateCcw } from "lucide-react";
|
import { Settings as SettingsIcon, FolderOpen, Save, RotateCcw } from "lucide-react";
|
||||||
import { getSettings, getSettingsWithDefaults, saveSettings, resetToDefaultSettings, applyThemeMode, type Settings as SettingsType } from "@/lib/settings";
|
import { getSettings, getSettingsWithDefaults, saveSettings, resetToDefaultSettings, applyThemeMode, type Settings as SettingsType } from "@/lib/settings";
|
||||||
import { themes, applyTheme } from "@/lib/themes";
|
import { themes, applyTheme } from "@/lib/themes";
|
||||||
import { OpenFolder } from "../../wailsjs/go/main/App";
|
import { SelectFolder } from "../../wailsjs/go/main/App";
|
||||||
|
|
||||||
export function Settings() {
|
export function Settings() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -147,17 +147,19 @@ export function Settings() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleBrowseFolder = async () => {
|
const handleBrowseFolder = async () => {
|
||||||
if (!tempSettings.downloadPath) {
|
|
||||||
alert("Please enter a download path first");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Call backend to open folder in file explorer
|
// Call backend to open folder selection dialog
|
||||||
await OpenFolder(tempSettings.downloadPath);
|
const selectedPath = await SelectFolder(tempSettings.downloadPath || "");
|
||||||
|
console.log("Selected path:", selectedPath);
|
||||||
|
|
||||||
|
if (selectedPath && selectedPath.trim() !== "") {
|
||||||
|
setTempSettings((prev) => ({ ...prev, downloadPath: selectedPath }));
|
||||||
|
} else {
|
||||||
|
console.log("No folder selected or user cancelled");
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error opening folder:", error);
|
console.error("Error selecting folder:", error);
|
||||||
alert(`Error opening folder: ${error}`);
|
alert(`Error selecting folder: ${error}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -184,8 +186,8 @@ export function Settings() {
|
|||||||
placeholder="C:\Users\YourUsername\Music"
|
placeholder="C:\Users\YourUsername\Music"
|
||||||
/>
|
/>
|
||||||
<Button type="button" onClick={handleBrowseFolder}>
|
<Button type="button" onClick={handleBrowseFolder}>
|
||||||
<FolderOpen className="h-4 w-4" />
|
<FolderOpen className="h-4 w-4 mr-2" />
|
||||||
Open
|
Browse
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,17 +24,14 @@ export const DEFAULT_SETTINGS: Settings = {
|
|||||||
operatingSystem: "Windows"
|
operatingSystem: "Windows"
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: add mac/linux defaults
|
|
||||||
const DEFAULT_PATH : string = "C:\\Users\\Public\\Music";
|
|
||||||
|
|
||||||
async function fetchDefaultPath(): Promise<string> {
|
async function fetchDefaultPath(): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const data = await GetDefaults();
|
const data = await GetDefaults();
|
||||||
return data.downloadPath || DEFAULT_PATH;
|
return data.downloadPath || "";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch default path:", error);
|
console.error("Failed to fetch default path:", error);
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
return DEFAULT_PATH;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SETTINGS_KEY = "spotiflac-settings";
|
const SETTINGS_KEY = "spotiflac-settings";
|
||||||
|
|||||||
Reference in New Issue
Block a user