This commit is contained in:
afkarxyz
2026-01-13 23:28:06 +07:00
parent 46a7777698
commit 6e3ca48d3f
9 changed files with 552 additions and 401 deletions
+52
View File
@@ -1053,3 +1053,55 @@ func (a *App) SkipDownloadItem(itemID, filePath string) {
func (a *App) GetPreviewURL(trackID string) (string, error) {
return backend.GetPreviewURL(trackID)
}
func (a *App) GetConfigPath() (string, error) {
dir, err := backend.GetFFmpegDir()
if err != nil {
return "", err
}
return filepath.Join(dir, "config.json"), nil
}
func (a *App) SaveSettings(settings map[string]interface{}) error {
configPath, err := a.GetConfigPath()
if err != nil {
return err
}
dir := filepath.Dir(configPath)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
}
data, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return err
}
return os.WriteFile(configPath, data, 0644)
}
func (a *App) LoadSettings() (map[string]interface{}, error) {
configPath, err := a.GetConfigPath()
if err != nil {
return nil, err
}
if _, err := os.Stat(configPath); os.IsNotExist(err) {
return nil, nil
}
data, err := os.ReadFile(configPath)
if err != nil {
return nil, err
}
var settings map[string]interface{}
if err := json.Unmarshal(data, &settings); err != nil {
return nil, err
}
return settings, nil
}
+6 -1
View File
@@ -1,16 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300..800&family=Figtree:wght@300..900&family=Geist:wght@100..900&family=Google+Sans+Code:ital,wght@0,300..800;1,300..800&family=Google+Sans:ital,opsz,wght@0,17..18,400..700;1,17..18,400..700&family=Inter:wght@300..800&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Manrope:wght@300..800&family=Noto+Sans:wght@100..900&family=Nunito+Sans:opsz,wght@6..12,200..1000&family=Outfit:wght@100..900&family=Plus+Jakarta+Sans:wght@300..800&family=Poppins:wght@300;400;500;600;700;800&family=Public+Sans:ital,wght@0,100..900;1,100..900&family=Raleway:wght@100..900&family=Roboto:wght@300;400;500;700;900&family=Space+Grotesk:wght@300..700&display=swap" rel="stylesheet">
<link
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&family=DM+Sans:wght@300..800&family=Figtree:wght@300..900&family=Geist:wght@100..900&family=Google+Sans+Code:ital,wght@0,300..800;1,300..800&family=Google+Sans:ital,opsz,wght@0,17..18,400..700;1,17..18,400..700&family=Inter:wght@300..800&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Manrope:wght@300..800&family=Noto+Sans:wght@100..900&family=Nunito+Sans:opsz,wght@6..12,200..1000&family=Outfit:wght@100..900&family=Plus+Jakarta+Sans:wght@300..800&family=Poppins:wght@300;400;500;600;700;800&family=Public+Sans:ital,wght@0,100..900;1,100..900&family=Raleway:wght@100..900&family=Roboto:wght@300;400;500;700;900&family=Space+Grotesk:wght@300..700&display=swap"
rel="stylesheet">
<title>SpotiFLAC</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+2 -2
View File
@@ -27,7 +27,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.562.0",
"motion": "^12.26.1",
"motion": "^12.26.2",
"next-themes": "^0.4.6",
"react": "^19.2.3",
"react-dom": "^19.2.3",
@@ -37,7 +37,7 @@
},
"devDependencies": {
"@eslint/js": "^9.39.2",
"@types/node": "^25.0.7",
"@types/node": "^25.0.8",
"@types/react": "^19.2.8",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.2",
+1 -1
View File
@@ -1 +1 @@
65caca63c4f7ac1740046770c7a945b0
68754ba75ba7fe058dd9ebf6593e2759
+27 -27
View File
@@ -43,7 +43,7 @@ importers:
version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@tailwindcss/vite':
specifier: ^4.1.18
version: 4.1.18(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(lightningcss@1.30.2))
version: 4.1.18(vite@7.3.1(@types/node@25.0.8)(jiti@2.6.1)(lightningcss@1.30.2))
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -54,8 +54,8 @@ importers:
specifier: ^0.562.0
version: 0.562.0(react@19.2.3)
motion:
specifier: ^12.26.1
version: 12.26.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
specifier: ^12.26.2
version: 12.26.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -79,8 +79,8 @@ importers:
specifier: ^9.39.2
version: 9.39.2
'@types/node':
specifier: ^25.0.7
version: 25.0.7
specifier: ^25.0.8
version: 25.0.8
'@types/react':
specifier: ^19.2.8
version: 19.2.8
@@ -89,7 +89,7 @@ importers:
version: 19.2.3(@types/react@19.2.8)
'@vitejs/plugin-react':
specifier: ^5.1.2
version: 5.1.2(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(lightningcss@1.30.2))
version: 5.1.2(vite@7.3.1(@types/node@25.0.8)(jiti@2.6.1)(lightningcss@1.30.2))
eslint:
specifier: ^9.39.2
version: 9.39.2(jiti@2.6.1)
@@ -116,7 +116,7 @@ importers:
version: 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
vite:
specifier: ^7.3.1
version: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(lightningcss@1.30.2)
version: 7.3.1(@types/node@25.0.8)(jiti@2.6.1)(lightningcss@1.30.2)
packages:
@@ -1259,8 +1259,8 @@ packages:
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/node@25.0.7':
resolution: {integrity: sha512-C/er7DlIZgRJO7WtTdYovjIFzGsz0I95UlMyR9anTb4aCpBSRWe5Jc1/RvLKUfzmOxHPGjSE5+63HgLtndxU4w==}
'@types/node@25.0.8':
resolution: {integrity: sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==}
'@types/react-dom@19.2.3':
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
@@ -1540,8 +1540,8 @@ packages:
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
framer-motion@12.26.1:
resolution: {integrity: sha512-Uzc8wGldU4FpmGotthjjcj0SZhigcODjqvKT7lzVZHsmYkzQMFfMIv0vHQoXCeoe/Ahxqp4by4A6QbzFA/lblw==}
framer-motion@12.26.2:
resolution: {integrity: sha512-lflOQEdjquUi9sCg5Y1LrsZDlsjrHw7m0T9Yedvnk7Bnhqfkc89/Uha10J3CFhkL+TCZVCRw9eUGyM/lyYhXQA==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
@@ -1751,14 +1751,14 @@ packages:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
motion-dom@12.24.11:
resolution: {integrity: sha512-DlWOmsXMJrV8lzZyd+LKjG2CXULUs++bkq8GZ2Sr0R0RRhs30K2wtY+LKiTjhmJU3W61HK+rB0GLz6XmPvTA1A==}
motion-dom@12.26.2:
resolution: {integrity: sha512-KLMT1BroY8oKNeliA3JMNJ+nbCIsTKg6hJpDb4jtRAJ7nCKnnpg/LTq/NGqG90Limitz3kdAnAVXecdFVGlWTw==}
motion-utils@12.24.10:
resolution: {integrity: sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww==}
motion@12.26.1:
resolution: {integrity: sha512-IVhzx9HOQTiJ9ykthMOlZPnLwrkXziN5Q/yebsqBYlFJb2rHP8yhmKc8O/YUT9byPJlxOeqkzfNYCrVKZx8vqg==}
motion@12.26.2:
resolution: {integrity: sha512-2Q6g0zK1gUJKhGT742DAe42LgietcdiJ3L3OcYAHCQaC1UkLnn6aC8S/obe4CxYTLAgid2asS1QdQ/blYfo5dw==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
@@ -3016,12 +3016,12 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
'@tailwindcss/oxide-win32-x64-msvc': 4.1.18
'@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(lightningcss@1.30.2))':
'@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.0.8)(jiti@2.6.1)(lightningcss@1.30.2))':
dependencies:
'@tailwindcss/node': 4.1.18
'@tailwindcss/oxide': 4.1.18
tailwindcss: 4.1.18
vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(lightningcss@1.30.2)
vite: 7.3.1(@types/node@25.0.8)(jiti@2.6.1)(lightningcss@1.30.2)
'@types/babel__core@7.20.5':
dependencies:
@@ -3048,7 +3048,7 @@ snapshots:
'@types/json-schema@7.0.15': {}
'@types/node@25.0.7':
'@types/node@25.0.8':
dependencies:
undici-types: 7.16.0
@@ -3151,7 +3151,7 @@ snapshots:
'@typescript-eslint/types': 8.53.0
eslint-visitor-keys: 4.2.1
'@vitejs/plugin-react@5.1.2(vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(lightningcss@1.30.2))':
'@vitejs/plugin-react@5.1.2(vite@7.3.1(@types/node@25.0.8)(jiti@2.6.1)(lightningcss@1.30.2))':
dependencies:
'@babel/core': 7.28.6
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6)
@@ -3159,7 +3159,7 @@ snapshots:
'@rolldown/pluginutils': 1.0.0-beta.53
'@types/babel__core': 7.20.5
react-refresh: 0.18.0
vite: 7.3.1(@types/node@25.0.7)(jiti@2.6.1)(lightningcss@1.30.2)
vite: 7.3.1(@types/node@25.0.8)(jiti@2.6.1)(lightningcss@1.30.2)
transitivePeerDependencies:
- supports-color
@@ -3399,9 +3399,9 @@ snapshots:
flatted@3.3.3: {}
framer-motion@12.26.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
framer-motion@12.26.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
motion-dom: 12.24.11
motion-dom: 12.26.2
motion-utils: 12.24.10
tslib: 2.8.1
optionalDependencies:
@@ -3554,15 +3554,15 @@ snapshots:
dependencies:
brace-expansion: 2.0.2
motion-dom@12.24.11:
motion-dom@12.26.2:
dependencies:
motion-utils: 12.24.10
motion-utils@12.24.10: {}
motion@12.26.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
motion@12.26.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
framer-motion: 12.26.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
framer-motion: 12.26.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
tslib: 2.8.1
optionalDependencies:
react: 19.2.3
@@ -3808,7 +3808,7 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.8
vite@7.3.1(@types/node@25.0.7)(jiti@2.6.1)(lightningcss@1.30.2):
vite@7.3.1(@types/node@25.0.8)(jiti@2.6.1)(lightningcss@1.30.2):
dependencies:
esbuild: 0.27.2
fdir: 6.5.0(picomatch@4.0.3)
@@ -3817,7 +3817,7 @@ snapshots:
rollup: 4.55.1
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 25.0.7
'@types/node': 25.0.8
fsevents: 2.3.3
jiti: 2.6.1
lightningcss: 1.30.2
+3 -3
View File
@@ -5,7 +5,7 @@ import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Search, X, ArrowUp } from "lucide-react";
import { TooltipProvider } from "@/components/ui/tooltip";
import { getSettings, getSettingsWithDefaults, saveSettings, applyThemeMode, applyFont } from "@/lib/settings";
import { getSettings, getSettingsWithDefaults, loadSettings, saveSettings, 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";
@@ -59,13 +59,13 @@ function App() {
const downloadQueue = useDownloadQueueDialog();
useEffect(() => {
const initSettings = async () => {
const settings = getSettings();
const settings = await loadSettings();
applyThemeMode(settings.themeMode);
applyTheme(settings.theme);
applyFont(settings.fontFamily);
if (!settings.downloadPath) {
const settingsWithDefaults = await getSettingsWithDefaults();
saveSettings(settingsWithDefaults);
await saveSettings(settingsWithDefaults);
}
};
initSettings();
+3 -3
View File
@@ -76,13 +76,13 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest }: Setting
const settingsWithDefaults = await getSettingsWithDefaults();
setSavedSettings(settingsWithDefaults);
setTempSettings(settingsWithDefaults);
saveSettings(settingsWithDefaults);
await saveSettings(settingsWithDefaults);
}
};
loadDefaults();
}, []);
const handleSave = () => {
saveSettings(tempSettings);
const handleSave = async () => {
await saveSettings(tempSettings);
setSavedSettings(tempSettings);
toast.success("Settings saved");
onUnsavedChangesChange?.(false);
+23 -10
View File
@@ -26,6 +26,7 @@
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--font-sans: "Bricolage Grotesque", "Google Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
:root {
@@ -75,11 +76,15 @@
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
font-family: "Google Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-family: var(--font-sans);
}
code, pre, .font-mono {
code,
pre,
.font-mono {
font-family: "Google Sans Code", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important;
}
}
@@ -134,43 +139,51 @@
/* Specific color for each toast type - match icon color */
[data-sonner-toast][data-type="success"] [data-description],
[data-sonner-toast][data-type="success"] [data-description] * {
color: rgb(22 163 74) !important; /* green-600 - same as icon */
color: rgb(22 163 74) !important;
/* green-600 - same as icon */
}
[data-sonner-toast][data-type="error"] [data-description],
[data-sonner-toast][data-type="error"] [data-description] * {
color: rgb(220 38 38) !important; /* red-600 - same as icon */
color: rgb(220 38 38) !important;
/* red-600 - same as icon */
}
[data-sonner-toast][data-type="warning"] [data-description],
[data-sonner-toast][data-type="warning"] [data-description] * {
color: rgb(202 138 4) !important; /* yellow-600 - same as icon */
color: rgb(202 138 4) !important;
/* yellow-600 - same as icon */
}
[data-sonner-toast][data-type="info"] [data-description],
[data-sonner-toast][data-type="info"] [data-description] * {
color: rgb(37 99 235) !important; /* blue-600 - same as icon */
color: rgb(37 99 235) !important;
/* blue-600 - same as icon */
}
/* Dark mode - use same icon colors */
.dark [data-sonner-toast][data-type="success"] [data-description],
.dark [data-sonner-toast][data-type="success"] [data-description] * {
color: rgb(22 163 74) !important; /* green-600 */
color: rgb(22 163 74) !important;
/* green-600 */
}
.dark [data-sonner-toast][data-type="error"] [data-description],
.dark [data-sonner-toast][data-type="error"] [data-description] * {
color: rgb(220 38 38) !important; /* red-600 */
color: rgb(220 38 38) !important;
/* red-600 */
}
.dark [data-sonner-toast][data-type="warning"] [data-description],
.dark [data-sonner-toast][data-type="warning"] [data-description] * {
color: rgb(202 138 4) !important; /* yellow-600 */
color: rgb(202 138 4) !important;
/* yellow-600 */
}
.dark [data-sonner-toast][data-type="info"] [data-description],
.dark [data-sonner-toast][data-type="info"] [data-description] * {
color: rgb(37 99 235) !important; /* blue-600 */
color: rgb(37 99 235) !important;
/* blue-600 */
}
/* Dark mode toast styling */
+90 -9
View File
@@ -1,5 +1,5 @@
import { GetDefaults } from "../../wailsjs/go/main/App";
export type FontFamily = "google-sans" | "inter" | "poppins" | "roboto" | "dm-sans" | "plus-jakarta-sans" | "manrope" | "space-grotesk" | "noto-sans" | "nunito-sans" | "figtree" | "raleway" | "public-sans" | "outfit" | "jetbrains-mono" | "geist-sans";
import { GetDefaults, LoadSettings, SaveSettings as SaveToBackend } from "../../wailsjs/go/main/App";
export type FontFamily = "google-sans" | "inter" | "poppins" | "roboto" | "dm-sans" | "plus-jakarta-sans" | "manrope" | "space-grotesk" | "noto-sans" | "nunito-sans" | "figtree" | "raleway" | "public-sans" | "outfit" | "jetbrains-mono" | "geist-sans" | "bricolage-grotesque";
export type FolderPreset = "none" | "artist" | "album" | "year-album" | "year-artist-album" | "artist-album" | "artist-year-album" | "artist-year-nested-album" | "album-artist" | "album-artist-album" | "album-artist-year-album" | "album-artist-year-nested-album" | "year" | "year-artist" | "custom";
export type FilenamePreset = "title" | "title-artist" | "artist-title" | "track-title" | "track-title-artist" | "track-artist-title" | "title-album-artist" | "track-title-album-artist" | "artist-album-title" | "track-dash-title" | "disc-track-title" | "disc-track-title-artist" | "custom";
export interface Settings {
@@ -102,6 +102,7 @@ export const FONT_OPTIONS: {
label: string;
fontFamily: string;
}[] = [
{ value: "bricolage-grotesque", label: "Bricolage Grotesque", fontFamily: '"Bricolage Grotesque", system-ui, sans-serif' },
{ value: "dm-sans", label: "DM Sans", fontFamily: '"DM Sans", system-ui, sans-serif' },
{ value: "figtree", label: "Figtree", fontFamily: '"Figtree", system-ui, sans-serif' },
{ value: "geist-sans", label: "Geist Sans", fontFamily: '"Geist", system-ui, sans-serif' },
@@ -137,7 +138,8 @@ async function fetchDefaultPath(): Promise<string> {
}
}
const SETTINGS_KEY = "spotiflac-settings";
export function getSettings(): Settings {
let cachedSettings: Settings | null = null;
function getSettingsFromLocalStorage(): Settings {
try {
const stored = localStorage.getItem(SETTINGS_KEY);
if (stored) {
@@ -195,10 +197,86 @@ export function getSettings(): Settings {
}
}
catch (error) {
console.error("Failed to load settings:", error);
console.error("Failed to load settings from local storage:", error);
}
return DEFAULT_SETTINGS;
}
export function getSettings(): Settings {
if (cachedSettings)
return cachedSettings;
return getSettingsFromLocalStorage();
}
export async function loadSettings(): Promise<Settings> {
try {
const backendSettings = await LoadSettings();
if (backendSettings) {
const parsed = backendSettings as any;
if ('darkMode' in parsed && !('themeMode' in parsed)) {
parsed.themeMode = parsed.darkMode ? 'dark' : 'light';
delete parsed.darkMode;
}
if (!('folderPreset' in parsed) && ('artistSubfolder' in parsed || 'albumSubfolder' in parsed)) {
const hasArtist = parsed.artistSubfolder;
const hasAlbum = parsed.albumSubfolder;
if (hasArtist && hasAlbum) {
parsed.folderPreset = "artist-album";
parsed.folderTemplate = "{artist}/{album}";
}
else if (hasArtist) {
parsed.folderPreset = "artist";
parsed.folderTemplate = "{artist}";
}
else if (hasAlbum) {
parsed.folderPreset = "album";
parsed.folderTemplate = "{album}";
}
else {
parsed.folderPreset = "none";
parsed.folderTemplate = "";
}
}
if (!('filenamePreset' in parsed) && 'filenameFormat' in parsed) {
const format = parsed.filenameFormat;
if (format === "title-artist") {
parsed.filenamePreset = "artist-title";
parsed.filenameTemplate = "{artist} - {title}";
}
else if (format === "artist-title") {
parsed.filenamePreset = "artist-title";
parsed.filenameTemplate = "{artist} - {title}";
}
else {
parsed.filenamePreset = "title";
parsed.filenameTemplate = "{title}";
}
}
parsed.operatingSystem = detectOS();
if (!('tidalQuality' in parsed)) {
parsed.tidalQuality = "LOSSLESS";
}
if (!('qobuzQuality' in parsed)) {
parsed.qobuzQuality = "6";
}
if (!('amazonQuality' in parsed)) {
parsed.amazonQuality = "HI_RES";
}
cachedSettings = { ...DEFAULT_SETTINGS, ...parsed };
return cachedSettings!;
}
}
catch (error) {
console.error("Failed to load settings from backend:", error);
}
const local = getSettingsFromLocalStorage();
try {
await SaveToBackend(local as any);
cachedSettings = local;
}
catch (error) {
console.error("Failed to migrate settings to backend:", error);
}
return local;
}
export interface TemplateData {
artist?: string;
album?: string;
@@ -224,30 +302,33 @@ export function parseTemplate(template: string, data: TemplateData): string {
return result;
}
export async function getSettingsWithDefaults(): Promise<Settings> {
const settings = getSettings();
const settings = await loadSettings();
if (!settings.downloadPath) {
settings.downloadPath = await fetchDefaultPath();
await saveSettings(settings);
}
return settings;
}
export function saveSettings(settings: Settings): void {
export async function saveSettings(settings: Settings): Promise<void> {
try {
cachedSettings = settings;
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
await SaveToBackend(settings as any);
}
catch (error) {
console.error("Failed to save settings:", error);
}
}
export function updateSettings(partial: Partial<Settings>): Settings {
export async function updateSettings(partial: Partial<Settings>): Promise<Settings> {
const current = getSettings();
const updated = { ...current, ...partial };
saveSettings(updated);
await saveSettings(updated);
return updated;
}
export async function resetToDefaultSettings(): Promise<Settings> {
const defaultPath = await fetchDefaultPath();
const defaultSettings = { ...DEFAULT_SETTINGS, downloadPath: defaultPath };
saveSettings(defaultSettings);
await saveSettings(defaultSettings);
return defaultSettings;
}
export function applyThemeMode(mode: "auto" | "light" | "dark"): void {