import { useState, useEffect, useCallback } from "react"; import { flushSync } from "react-dom"; import { Button } from "@/components/ui/button"; import { InputWithContext } from "@/components/ui/input-with-context"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { FolderOpen, Save, RotateCcw, Info, ArrowRight, Settings, FolderCog, } from "lucide-react"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Switch } from "@/components/ui/switch"; 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"; const TidalIcon = ({ className }: { className?: string; }) => ( ); const QobuzIcon = ({ className }: { className?: string; }) => ( ); const AmazonIcon = ({ className }: { className?: string; }) => ( ); const DeezerIcon = ({ className }: { className?: string; }) => ( ); interface SettingsPageProps { onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void; onResetRequest?: (resetFn: () => void) => void; } export function SettingsPage({ onUnsavedChangesChange, onResetRequest, }: SettingsPageProps) { const [savedSettings, setSavedSettings] = useState(getSettings()); const [tempSettings, setTempSettings] = useState(savedSettings); const [isDark, setIsDark] = useState(document.documentElement.classList.contains("dark")); const [showResetConfirm, setShowResetConfirm] = useState(false); const hasUnsavedChanges = JSON.stringify(savedSettings) !== JSON.stringify(tempSettings); const resetToSaved = useCallback(() => { const freshSavedSettings = getSettings(); flushSync(() => { setTempSettings(freshSavedSettings); setIsDark(document.documentElement.classList.contains("dark")); }); }, []); useEffect(() => { if (onResetRequest) { onResetRequest(resetToSaved); } }, [onResetRequest, resetToSaved]); useEffect(() => { onUnsavedChangesChange?.(hasUnsavedChanges); }, [hasUnsavedChanges, onUnsavedChangesChange]); useEffect(() => { applyThemeMode(savedSettings.themeMode); applyTheme(savedSettings.theme); const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); const handleChange = () => { if (savedSettings.themeMode === "auto") { applyThemeMode("auto"); applyTheme(savedSettings.theme); } }; mediaQuery.addEventListener("change", handleChange); return () => mediaQuery.removeEventListener("change", handleChange); }, [savedSettings.themeMode, savedSettings.theme]); useEffect(() => { applyThemeMode(tempSettings.themeMode); applyTheme(tempSettings.theme); applyFont(tempSettings.fontFamily); setTimeout(() => { setIsDark(document.documentElement.classList.contains("dark")); }, 0); }, [tempSettings.themeMode, tempSettings.theme, tempSettings.fontFamily]); useEffect(() => { const loadDefaults = async () => { if (!savedSettings.downloadPath) { const settingsWithDefaults = await getSettingsWithDefaults(); setSavedSettings(settingsWithDefaults); setTempSettings(settingsWithDefaults); await saveSettings(settingsWithDefaults); } }; loadDefaults(); }, []); const handleSave = async () => { await saveSettings(tempSettings); setSavedSettings(tempSettings); toast.success("Settings saved"); onUnsavedChangesChange?.(false); }; const handleReset = async () => { const defaultSettings = await resetToDefaultSettings(); setTempSettings(defaultSettings); setSavedSettings(defaultSettings); applyThemeMode(defaultSettings.themeMode); applyTheme(defaultSettings.theme); applyFont(defaultSettings.fontFamily); setShowResetConfirm(false); toast.success("Settings reset to default"); }; const handleBrowseFolder = async () => { try { const selectedPath = await SelectFolder(tempSettings.downloadPath || ""); if (selectedPath && selectedPath.trim() !== "") { setTempSettings((prev) => ({ ...prev, downloadPath: selectedPath })); } } catch (error) { console.error("Error selecting folder:", error); toast.error(`Error selecting folder: ${error}`); } }; const handleTidalQualityChange = async (value: "LOSSLESS" | "HI_RES_LOSSLESS") => { setTempSettings((prev) => ({ ...prev, tidalQuality: value })); }; const handleQobuzQualityChange = (value: "6" | "7" | "27") => { setTempSettings((prev) => ({ ...prev, qobuzQuality: value })); }; const handleAutoQualityChange = async (value: "16" | "24") => { setTempSettings((prev) => ({ ...prev, autoQuality: value })); }; const [activeTab, setActiveTab] = useState<"general" | "files">("general"); return (

Settings

{activeTab === "general" && (
setTempSettings((prev) => ({ ...prev, downloadPath: e.target.value, }))} placeholder="C:\Users\YourUsername\Music"/>
setTempSettings((prev) => ({ ...prev, sfxEnabled: checked, }))}/>
{tempSettings.downloader === "auto" && (<> )} {tempSettings.downloader === "tidal" && ()} {tempSettings.downloader === "qobuz" && ()} {tempSettings.downloader === "amazon" && (
16-bit - 24-bit/44.1kHz - 192kHz
)} {tempSettings.downloader === "deezer" && (
16-bit/44.1kHz
)}
{((tempSettings.downloader === "tidal" && tempSettings.tidalQuality === "HI_RES_LOSSLESS") || (tempSettings.downloader === "qobuz" && tempSettings.qobuzQuality === "27") || (tempSettings.downloader === "auto" && tempSettings.autoQuality === "24")) && (
setTempSettings((prev) => ({ ...prev, allowFallback: checked, }))}/>
)}
setTempSettings((prev) => ({ ...prev, embedLyrics: checked, }))}/>
setTempSettings((prev) => ({ ...prev, embedMaxQualityCover: checked, }))}/>
setTempSettings((prev) => ({ ...prev, embedGenre: checked, }))}/>
{tempSettings.embedGenre && (
setTempSettings((prev) => ({ ...prev, useSingleGenre: checked, }))}/>
)}
)} {activeTab === "files" && (

Variables:{" "} {TEMPLATE_VARIABLES.map((v) => v.key).join(", ")}

{tempSettings.folderPreset === "custom" && ( setTempSettings((prev) => ({ ...prev, folderTemplate: e.target.value, }))} placeholder="{artist}/{album}" className="h-9 text-sm flex-1"/>)}
{tempSettings.folderTemplate && (

Preview:{" "} {tempSettings.folderTemplate .replace(/\{artist\}/g, "Kendrick Lamar, SZA") .replace(/\{album\}/g, "Black Panther") .replace(/\{album_artist\}/g, "Kendrick Lamar") .replace(/\{year\}/g, "2018") .replace(/\{date\}/g, "2018-02-09")} /

)}
setTempSettings((prev) => ({ ...prev, createPlaylistFolder: checked, }))}/>
setTempSettings((prev) => ({ ...prev, createM3u8File: checked, }))}/>
setTempSettings((prev) => ({ ...prev, useFirstArtistOnly: checked, }))}/>

Variables:{" "} {TEMPLATE_VARIABLES.map((v) => v.key).join(", ")}

{tempSettings.filenamePreset === "custom" && ( setTempSettings((prev) => ({ ...prev, filenameTemplate: e.target.value, }))} placeholder="{track}. {title}" className="h-9 text-sm flex-1"/>)}
{tempSettings.filenameTemplate && (

Preview:{" "} {tempSettings.filenameTemplate .replace(/\{artist\}/g, "Kendrick Lamar, SZA") .replace(/\{album_artist\}/g, "Kendrick Lamar") .replace(/\{title\}/g, "All The Stars") .replace(/\{track\}/g, "01") .replace(/\{disc\}/g, "1") .replace(/\{year\}/g, "2018") .replace(/\{date\}/g, "2018-02-09")} .flac

)}
)}
Reset to Default? This will reset all settings to their default values. Your custom configurations will be lost.
); }