v6.7
This commit is contained in:
@@ -16,12 +16,11 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { Settings as SettingsIcon, FolderOpen, Save, RotateCcw, Info, X, Volume2 } from "lucide-react";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { getSettings, getSettingsWithDefaults, saveSettings, resetToDefaultSettings, applyThemeMode, type Settings as SettingsType } from "@/lib/settings";
|
||||
import { getSettings, getSettingsWithDefaults, saveSettings, resetToDefaultSettings, applyThemeMode, FOLDER_PRESETS, FILENAME_PRESETS, TEMPLATE_VARIABLES, type Settings as SettingsType, type FolderPreset, type FilenamePreset } from "@/lib/settings";
|
||||
import { themes, applyTheme } from "@/lib/themes";
|
||||
import { SelectFolder } from "../../wailsjs/go/main/App";
|
||||
|
||||
@@ -325,81 +324,102 @@ export function Settings() {
|
||||
|
||||
{/* Right Column */}
|
||||
<div className="space-y-4">
|
||||
{/* Filename Format */}
|
||||
{/* Folder Structure */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm">Filename Format</Label>
|
||||
<RadioGroup
|
||||
value={tempSettings.filenameFormat}
|
||||
onValueChange={(value) => setTempSettings(prev => ({ ...prev, filenameFormat: value as any }))}
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-sm">Folder Structure</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
<p className="text-xs whitespace-nowrap">Variables: {TEMPLATE_VARIABLES.map(v => v.key).join(", ")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Select
|
||||
value={tempSettings.folderPreset}
|
||||
onValueChange={(value: FolderPreset) => {
|
||||
const preset = FOLDER_PRESETS[value];
|
||||
setTempSettings(prev => ({
|
||||
...prev,
|
||||
folderPreset: value,
|
||||
folderTemplate: value === "custom" ? prev.folderTemplate : preset.template
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="title-artist" id="title-artist" />
|
||||
<Label htmlFor="title-artist" className="cursor-pointer font-normal text-sm">Title - Artist</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="artist-title" id="artist-title" />
|
||||
<Label htmlFor="artist-title" className="cursor-pointer font-normal text-sm">Artist - Title</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="title" id="title" />
|
||||
<Label htmlFor="title" className="cursor-pointer font-normal text-sm">Title</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<SelectTrigger className="h-9">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.entries(FOLDER_PRESETS).map(([key, { label }]) => (
|
||||
<SelectItem key={key} value={key}>{label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{tempSettings.folderPreset === "custom" && (
|
||||
<InputWithContext
|
||||
value={tempSettings.folderTemplate}
|
||||
onChange={(e) => setTempSettings(prev => ({ ...prev, folderTemplate: e.target.value }))}
|
||||
placeholder="{artist}/{album}"
|
||||
className="h-9 text-sm"
|
||||
/>
|
||||
)}
|
||||
{tempSettings.folderTemplate && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Preview: <span className="font-mono">{tempSettings.folderTemplate.replace(/\{artist\}/g, "Taylor Swift").replace(/\{album\}/g, "1989").replace(/\{year\}/g, "2014")}/</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t" />
|
||||
|
||||
{/* Folder Settings */}
|
||||
{/* Filename Format */}
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-medium text-sm">Folder Settings</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="track-number"
|
||||
checked={tempSettings.trackNumber}
|
||||
onCheckedChange={(checked) => setTempSettings(prev => ({ ...prev, trackNumber: checked as boolean }))}
|
||||
/>
|
||||
<Label htmlFor="track-number" className="cursor-pointer text-sm">Track Number</Label>
|
||||
<Label className="text-sm">Filename Format</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-xs">
|
||||
<p className="text-xs text-center">Adds track numbers to filenames. Uses album track numbers when Album Subfolder is enabled, otherwise uses playlist position</p>
|
||||
<TooltipContent side="top">
|
||||
<p className="text-xs whitespace-nowrap">Variables: {TEMPLATE_VARIABLES.map(v => v.key).join(", ")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="artist-subfolder"
|
||||
checked={tempSettings.artistSubfolder}
|
||||
onCheckedChange={(checked) => setTempSettings(prev => ({ ...prev, artistSubfolder: checked as boolean }))}
|
||||
<Select
|
||||
value={tempSettings.filenamePreset}
|
||||
onValueChange={(value: FilenamePreset) => {
|
||||
const preset = FILENAME_PRESETS[value];
|
||||
setTempSettings(prev => ({
|
||||
...prev,
|
||||
filenamePreset: value,
|
||||
filenameTemplate: value === "custom" ? prev.filenameTemplate : preset.template
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-9">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.entries(FILENAME_PRESETS).map(([key, { label }]) => (
|
||||
<SelectItem key={key} value={key}>{label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{tempSettings.filenamePreset === "custom" && (
|
||||
<InputWithContext
|
||||
value={tempSettings.filenameTemplate}
|
||||
onChange={(e) => setTempSettings(prev => ({ ...prev, filenameTemplate: e.target.value }))}
|
||||
placeholder="{track}. {title}"
|
||||
className="h-9 text-sm"
|
||||
/>
|
||||
<Label htmlFor="artist-subfolder" className="cursor-pointer text-sm">Artist Subfolder</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-xs">
|
||||
<p className="text-xs text-center">Playlist only</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="album-subfolder"
|
||||
checked={tempSettings.albumSubfolder}
|
||||
onCheckedChange={(checked) => setTempSettings(prev => ({ ...prev, albumSubfolder: checked as boolean }))}
|
||||
/>
|
||||
<Label htmlFor="album-subfolder" className="cursor-pointer text-sm">Album Subfolder</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-xs">
|
||||
<p className="text-xs text-center">Playlist & Discography</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
{tempSettings.filenameTemplate && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Preview: <span className="font-mono">{tempSettings.filenameTemplate.replace(/\{artist\}/g, "Taylor Swift").replace(/\{title\}/g, "Shake It Off").replace(/\{track\}/g, "01").replace(/\{year\}/g, "2014")}.flac</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t" />
|
||||
|
||||
@@ -9,12 +9,11 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { FolderOpen, Save, RotateCcw, Info, Volume2 } from "lucide-react";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { getSettings, getSettingsWithDefaults, saveSettings, resetToDefaultSettings, applyThemeMode, applyFont, FONT_OPTIONS, type Settings as SettingsType, type FontFamily } from "@/lib/settings";
|
||||
import { getSettings, getSettingsWithDefaults, saveSettings, resetToDefaultSettings, applyThemeMode, applyFont, FONT_OPTIONS, FOLDER_PRESETS, FILENAME_PRESETS, TEMPLATE_VARIABLES, type Settings as SettingsType, type FontFamily, type FolderPreset, type FilenamePreset } from "@/lib/settings";
|
||||
import { themes, applyTheme } from "@/lib/themes";
|
||||
import { SelectFolder } from "../../wailsjs/go/main/App";
|
||||
import { toastWithSound as toast } from "@/lib/toast-with-sound";
|
||||
@@ -237,81 +236,102 @@ export function SettingsPage() {
|
||||
|
||||
{/* Right Column */}
|
||||
<div className="space-y-4">
|
||||
{/* Filename Format */}
|
||||
{/* Folder Structure */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm">Filename Format</Label>
|
||||
<RadioGroup
|
||||
value={tempSettings.filenameFormat}
|
||||
onValueChange={(value) => setTempSettings(prev => ({ ...prev, filenameFormat: value as any }))}
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-sm">Folder Structure</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
<p className="text-xs whitespace-nowrap">Variables: {TEMPLATE_VARIABLES.map(v => v.key).join(", ")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Select
|
||||
value={tempSettings.folderPreset}
|
||||
onValueChange={(value: FolderPreset) => {
|
||||
const preset = FOLDER_PRESETS[value];
|
||||
setTempSettings(prev => ({
|
||||
...prev,
|
||||
folderPreset: value,
|
||||
folderTemplate: value === "custom" ? prev.folderTemplate : preset.template
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="title-artist" id="title-artist" />
|
||||
<Label htmlFor="title-artist" className="cursor-pointer font-normal text-sm">Title - Artist</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="artist-title" id="artist-title" />
|
||||
<Label htmlFor="artist-title" className="cursor-pointer font-normal text-sm">Artist - Title</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="title" id="title" />
|
||||
<Label htmlFor="title" className="cursor-pointer font-normal text-sm">Title</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<SelectTrigger className="h-9">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.entries(FOLDER_PRESETS).map(([key, { label }]) => (
|
||||
<SelectItem key={key} value={key}>{label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{tempSettings.folderPreset === "custom" && (
|
||||
<InputWithContext
|
||||
value={tempSettings.folderTemplate}
|
||||
onChange={(e) => setTempSettings(prev => ({ ...prev, folderTemplate: e.target.value }))}
|
||||
placeholder="{artist}/{album}"
|
||||
className="h-9 text-sm"
|
||||
/>
|
||||
)}
|
||||
{tempSettings.folderTemplate && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Preview: <span className="font-mono">{tempSettings.folderTemplate.replace(/\{artist\}/g, "Taylor Swift").replace(/\{album\}/g, "1989").replace(/\{year\}/g, "2014")}/</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t pt-4" />
|
||||
|
||||
{/* Folder Settings */}
|
||||
{/* Filename Format */}
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-medium text-sm">Folder Settings</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="track-number"
|
||||
checked={tempSettings.trackNumber}
|
||||
onCheckedChange={(checked) => setTempSettings(prev => ({ ...prev, trackNumber: checked as boolean }))}
|
||||
/>
|
||||
<Label htmlFor="track-number" className="cursor-pointer text-sm">Track Number</Label>
|
||||
<Label className="text-sm">Filename Format</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-xs">
|
||||
<p className="text-xs text-center">Adds track numbers to filenames</p>
|
||||
<TooltipContent side="top">
|
||||
<p className="text-xs whitespace-nowrap">Variables: {TEMPLATE_VARIABLES.map(v => v.key).join(", ")}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="artist-subfolder"
|
||||
checked={tempSettings.artistSubfolder}
|
||||
onCheckedChange={(checked) => setTempSettings(prev => ({ ...prev, artistSubfolder: checked as boolean }))}
|
||||
<Select
|
||||
value={tempSettings.filenamePreset}
|
||||
onValueChange={(value: FilenamePreset) => {
|
||||
const preset = FILENAME_PRESETS[value];
|
||||
setTempSettings(prev => ({
|
||||
...prev,
|
||||
filenamePreset: value,
|
||||
filenameTemplate: value === "custom" ? prev.filenameTemplate : preset.template
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-9">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.entries(FILENAME_PRESETS).map(([key, { label }]) => (
|
||||
<SelectItem key={key} value={key}>{label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{tempSettings.filenamePreset === "custom" && (
|
||||
<InputWithContext
|
||||
value={tempSettings.filenameTemplate}
|
||||
onChange={(e) => setTempSettings(prev => ({ ...prev, filenameTemplate: e.target.value }))}
|
||||
placeholder="{track}. {title}"
|
||||
className="h-9 text-sm"
|
||||
/>
|
||||
<Label htmlFor="artist-subfolder" className="cursor-pointer text-sm">Artist Subfolder</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-xs">
|
||||
<p className="text-xs text-center">Playlist only</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="album-subfolder"
|
||||
checked={tempSettings.albumSubfolder}
|
||||
onCheckedChange={(checked) => setTempSettings(prev => ({ ...prev, albumSubfolder: checked as boolean }))}
|
||||
/>
|
||||
<Label htmlFor="album-subfolder" className="cursor-pointer text-sm">Album Subfolder</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-xs">
|
||||
<p className="text-xs text-center">Playlist & Discography</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
{tempSettings.filenameTemplate && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Preview: <span className="font-mono">{tempSettings.filenameTemplate.replace(/\{artist\}/g, "Taylor Swift").replace(/\{title\}/g, "Shake It Off").replace(/\{track\}/g, "01").replace(/\{year\}/g, "2014")}.flac</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t pt-4" />
|
||||
|
||||
@@ -49,7 +49,7 @@ interface TrackListProps {
|
||||
downloadingCoverTrack?: string | null;
|
||||
onToggleTrack: (isrc: string) => void;
|
||||
onToggleSelectAll: (tracks: TrackMetadata[]) => void;
|
||||
onDownloadTrack: (isrc: string, name: string, artists: string, albumName: string, spotifyId?: string, folderName?: string, isArtistDiscography?: boolean) => void;
|
||||
onDownloadTrack: (isrc: string, name: string, artists: string, albumName: string, spotifyId?: string, folderName?: string, durationMs?: number, position?: number) => void;
|
||||
onDownloadLyrics?: (spotifyId: string, name: string, artists: string, albumName: string, folderName?: string, isArtistDiscography?: boolean, position?: number) => void;
|
||||
onCheckAvailability?: (spotifyId: string, isrc?: string) => void;
|
||||
onDownloadCover?: (coverUrl: string, trackName: string, artistName: string, albumName: string, folderName?: string, isArtistDiscography?: boolean, position?: number, trackId?: string) => void;
|
||||
@@ -301,7 +301,7 @@ export function TrackList({
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
onClick={() =>
|
||||
onDownloadTrack(track.isrc, track.name, track.artists, track.album_name, track.spotify_id, folderName, isArtistDiscography)
|
||||
onDownloadTrack(track.isrc, track.name, track.artists, track.album_name, track.spotify_id, folderName, track.duration_ms, startIndex + index + 1)
|
||||
}
|
||||
size="sm"
|
||||
disabled={isDownloading || downloadingTrack === track.isrc}
|
||||
|
||||
Reference in New Issue
Block a user