v7.0.5
This commit is contained in:
@@ -1053,3 +1053,55 @@ func (a *App) SkipDownloadItem(itemID, filePath string) {
|
|||||||
func (a *App) GetPreviewURL(trackID string) (string, error) {
|
func (a *App) GetPreviewURL(trackID string) (string, error) {
|
||||||
return backend.GetPreviewURL(trackID)
|
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
@@ -1,16 +1,21 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<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.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<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>
|
<title>SpotiFLAC</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.562.0",
|
||||||
"motion": "^12.26.1",
|
"motion": "^12.26.2",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.2.3",
|
"react": "^19.2.3",
|
||||||
"react-dom": "^19.2.3",
|
"react-dom": "^19.2.3",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"@types/node": "^25.0.7",
|
"@types/node": "^25.0.8",
|
||||||
"@types/react": "^19.2.8",
|
"@types/react": "^19.2.8",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.2",
|
"@vitejs/plugin-react": "^5.1.2",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
65caca63c4f7ac1740046770c7a945b0
|
68754ba75ba7fe058dd9ebf6593e2759
|
||||||
Generated
+27
-27
@@ -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)
|
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':
|
'@tailwindcss/vite':
|
||||||
specifier: ^4.1.18
|
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:
|
class-variance-authority:
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
@@ -54,8 +54,8 @@ importers:
|
|||||||
specifier: ^0.562.0
|
specifier: ^0.562.0
|
||||||
version: 0.562.0(react@19.2.3)
|
version: 0.562.0(react@19.2.3)
|
||||||
motion:
|
motion:
|
||||||
specifier: ^12.26.1
|
specifier: ^12.26.2
|
||||||
version: 12.26.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
version: 12.26.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
next-themes:
|
next-themes:
|
||||||
specifier: ^0.4.6
|
specifier: ^0.4.6
|
||||||
version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
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
|
specifier: ^9.39.2
|
||||||
version: 9.39.2
|
version: 9.39.2
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.0.7
|
specifier: ^25.0.8
|
||||||
version: 25.0.7
|
version: 25.0.8
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^19.2.8
|
specifier: ^19.2.8
|
||||||
version: 19.2.8
|
version: 19.2.8
|
||||||
@@ -89,7 +89,7 @@ importers:
|
|||||||
version: 19.2.3(@types/react@19.2.8)
|
version: 19.2.3(@types/react@19.2.8)
|
||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: ^5.1.2
|
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:
|
eslint:
|
||||||
specifier: ^9.39.2
|
specifier: ^9.39.2
|
||||||
version: 9.39.2(jiti@2.6.1)
|
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)
|
version: 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
vite:
|
vite:
|
||||||
specifier: ^7.3.1
|
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:
|
packages:
|
||||||
|
|
||||||
@@ -1259,8 +1259,8 @@ packages:
|
|||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
'@types/node@25.0.7':
|
'@types/node@25.0.8':
|
||||||
resolution: {integrity: sha512-C/er7DlIZgRJO7WtTdYovjIFzGsz0I95UlMyR9anTb4aCpBSRWe5Jc1/RvLKUfzmOxHPGjSE5+63HgLtndxU4w==}
|
resolution: {integrity: sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==}
|
||||||
|
|
||||||
'@types/react-dom@19.2.3':
|
'@types/react-dom@19.2.3':
|
||||||
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
|
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
|
||||||
@@ -1540,8 +1540,8 @@ packages:
|
|||||||
flatted@3.3.3:
|
flatted@3.3.3:
|
||||||
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
|
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
|
||||||
|
|
||||||
framer-motion@12.26.1:
|
framer-motion@12.26.2:
|
||||||
resolution: {integrity: sha512-Uzc8wGldU4FpmGotthjjcj0SZhigcODjqvKT7lzVZHsmYkzQMFfMIv0vHQoXCeoe/Ahxqp4by4A6QbzFA/lblw==}
|
resolution: {integrity: sha512-lflOQEdjquUi9sCg5Y1LrsZDlsjrHw7m0T9Yedvnk7Bnhqfkc89/Uha10J3CFhkL+TCZVCRw9eUGyM/lyYhXQA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/is-prop-valid': '*'
|
'@emotion/is-prop-valid': '*'
|
||||||
react: ^18.0.0 || ^19.0.0
|
react: ^18.0.0 || ^19.0.0
|
||||||
@@ -1751,14 +1751,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
motion-dom@12.24.11:
|
motion-dom@12.26.2:
|
||||||
resolution: {integrity: sha512-DlWOmsXMJrV8lzZyd+LKjG2CXULUs++bkq8GZ2Sr0R0RRhs30K2wtY+LKiTjhmJU3W61HK+rB0GLz6XmPvTA1A==}
|
resolution: {integrity: sha512-KLMT1BroY8oKNeliA3JMNJ+nbCIsTKg6hJpDb4jtRAJ7nCKnnpg/LTq/NGqG90Limitz3kdAnAVXecdFVGlWTw==}
|
||||||
|
|
||||||
motion-utils@12.24.10:
|
motion-utils@12.24.10:
|
||||||
resolution: {integrity: sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww==}
|
resolution: {integrity: sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww==}
|
||||||
|
|
||||||
motion@12.26.1:
|
motion@12.26.2:
|
||||||
resolution: {integrity: sha512-IVhzx9HOQTiJ9ykthMOlZPnLwrkXziN5Q/yebsqBYlFJb2rHP8yhmKc8O/YUT9byPJlxOeqkzfNYCrVKZx8vqg==}
|
resolution: {integrity: sha512-2Q6g0zK1gUJKhGT742DAe42LgietcdiJ3L3OcYAHCQaC1UkLnn6aC8S/obe4CxYTLAgid2asS1QdQ/blYfo5dw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/is-prop-valid': '*'
|
'@emotion/is-prop-valid': '*'
|
||||||
react: ^18.0.0 || ^19.0.0
|
react: ^18.0.0 || ^19.0.0
|
||||||
@@ -3016,12 +3016,12 @@ snapshots:
|
|||||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
|
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
|
||||||
'@tailwindcss/oxide-win32-x64-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:
|
dependencies:
|
||||||
'@tailwindcss/node': 4.1.18
|
'@tailwindcss/node': 4.1.18
|
||||||
'@tailwindcss/oxide': 4.1.18
|
'@tailwindcss/oxide': 4.1.18
|
||||||
tailwindcss: 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':
|
'@types/babel__core@7.20.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3048,7 +3048,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/node@25.0.7':
|
'@types/node@25.0.8':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.16.0
|
undici-types: 7.16.0
|
||||||
|
|
||||||
@@ -3151,7 +3151,7 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.53.0
|
'@typescript-eslint/types': 8.53.0
|
||||||
eslint-visitor-keys: 4.2.1
|
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:
|
dependencies:
|
||||||
'@babel/core': 7.28.6
|
'@babel/core': 7.28.6
|
||||||
'@babel/plugin-transform-react-jsx-self': 7.27.1(@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
|
'@rolldown/pluginutils': 1.0.0-beta.53
|
||||||
'@types/babel__core': 7.20.5
|
'@types/babel__core': 7.20.5
|
||||||
react-refresh: 0.18.0
|
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:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -3399,9 +3399,9 @@ snapshots:
|
|||||||
|
|
||||||
flatted@3.3.3: {}
|
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:
|
dependencies:
|
||||||
motion-dom: 12.24.11
|
motion-dom: 12.26.2
|
||||||
motion-utils: 12.24.10
|
motion-utils: 12.24.10
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -3554,15 +3554,15 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 2.0.2
|
brace-expansion: 2.0.2
|
||||||
|
|
||||||
motion-dom@12.24.11:
|
motion-dom@12.26.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
motion-utils: 12.24.10
|
motion-utils: 12.24.10
|
||||||
|
|
||||||
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:
|
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
|
tslib: 2.8.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
react: 19.2.3
|
react: 19.2.3
|
||||||
@@ -3808,7 +3808,7 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.8
|
'@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:
|
dependencies:
|
||||||
esbuild: 0.27.2
|
esbuild: 0.27.2
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
@@ -3817,7 +3817,7 @@ snapshots:
|
|||||||
rollup: 4.55.1
|
rollup: 4.55.1
|
||||||
tinyglobby: 0.2.15
|
tinyglobby: 0.2.15
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 25.0.7
|
'@types/node': 25.0.8
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
jiti: 2.6.1
|
jiti: 2.6.1
|
||||||
lightningcss: 1.30.2
|
lightningcss: 1.30.2
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Search, X, ArrowUp } from "lucide-react";
|
import { Search, X, ArrowUp } from "lucide-react";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
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 { applyTheme } from "@/lib/themes";
|
||||||
import { OpenFolder } from "../wailsjs/go/main/App";
|
import { OpenFolder } from "../wailsjs/go/main/App";
|
||||||
import { toastWithSound as toast } from "@/lib/toast-with-sound";
|
import { toastWithSound as toast } from "@/lib/toast-with-sound";
|
||||||
@@ -59,13 +59,13 @@ function App() {
|
|||||||
const downloadQueue = useDownloadQueueDialog();
|
const downloadQueue = useDownloadQueueDialog();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initSettings = async () => {
|
const initSettings = async () => {
|
||||||
const settings = getSettings();
|
const settings = await loadSettings();
|
||||||
applyThemeMode(settings.themeMode);
|
applyThemeMode(settings.themeMode);
|
||||||
applyTheme(settings.theme);
|
applyTheme(settings.theme);
|
||||||
applyFont(settings.fontFamily);
|
applyFont(settings.fontFamily);
|
||||||
if (!settings.downloadPath) {
|
if (!settings.downloadPath) {
|
||||||
const settingsWithDefaults = await getSettingsWithDefaults();
|
const settingsWithDefaults = await getSettingsWithDefaults();
|
||||||
saveSettings(settingsWithDefaults);
|
await saveSettings(settingsWithDefaults);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
initSettings();
|
initSettings();
|
||||||
|
|||||||
@@ -76,13 +76,13 @@ export function SettingsPage({ onUnsavedChangesChange, onResetRequest }: Setting
|
|||||||
const settingsWithDefaults = await getSettingsWithDefaults();
|
const settingsWithDefaults = await getSettingsWithDefaults();
|
||||||
setSavedSettings(settingsWithDefaults);
|
setSavedSettings(settingsWithDefaults);
|
||||||
setTempSettings(settingsWithDefaults);
|
setTempSettings(settingsWithDefaults);
|
||||||
saveSettings(settingsWithDefaults);
|
await saveSettings(settingsWithDefaults);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
loadDefaults();
|
loadDefaults();
|
||||||
}, []);
|
}, []);
|
||||||
const handleSave = () => {
|
const handleSave = async () => {
|
||||||
saveSettings(tempSettings);
|
await saveSettings(tempSettings);
|
||||||
setSavedSettings(tempSettings);
|
setSavedSettings(tempSettings);
|
||||||
toast.success("Settings saved");
|
toast.success("Settings saved");
|
||||||
onUnsavedChangesChange?.(false);
|
onUnsavedChangesChange?.(false);
|
||||||
|
|||||||
+23
-10
@@ -26,6 +26,7 @@
|
|||||||
--color-border: var(--border);
|
--color-border: var(--border);
|
||||||
--color-input: var(--input);
|
--color-input: var(--input);
|
||||||
--color-ring: var(--ring);
|
--color-ring: var(--ring);
|
||||||
|
--font-sans: "Bricolage Grotesque", "Google Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -75,11 +76,15 @@
|
|||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@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;
|
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 */
|
/* 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],
|
||||||
[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],
|
||||||
[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],
|
||||||
[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],
|
||||||
[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 mode - use same icon colors */
|
||||||
.dark [data-sonner-toast][data-type="success"] [data-description],
|
.dark [data-sonner-toast][data-type="success"] [data-description],
|
||||||
.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],
|
||||||
.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],
|
||||||
.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],
|
||||||
.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 */
|
/* Dark mode toast styling */
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { GetDefaults } from "../../wailsjs/go/main/App";
|
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";
|
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 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 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 {
|
export interface Settings {
|
||||||
@@ -102,6 +102,7 @@ export const FONT_OPTIONS: {
|
|||||||
label: string;
|
label: string;
|
||||||
fontFamily: 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: "dm-sans", label: "DM Sans", fontFamily: '"DM Sans", system-ui, sans-serif' },
|
||||||
{ value: "figtree", label: "Figtree", fontFamily: '"Figtree", 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' },
|
{ 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";
|
const SETTINGS_KEY = "spotiflac-settings";
|
||||||
export function getSettings(): Settings {
|
let cachedSettings: Settings | null = null;
|
||||||
|
function getSettingsFromLocalStorage(): Settings {
|
||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem(SETTINGS_KEY);
|
const stored = localStorage.getItem(SETTINGS_KEY);
|
||||||
if (stored) {
|
if (stored) {
|
||||||
@@ -195,10 +197,86 @@ export function getSettings(): Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error("Failed to load settings:", error);
|
console.error("Failed to load settings from local storage:", error);
|
||||||
}
|
}
|
||||||
return DEFAULT_SETTINGS;
|
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 {
|
export interface TemplateData {
|
||||||
artist?: string;
|
artist?: string;
|
||||||
album?: string;
|
album?: string;
|
||||||
@@ -224,30 +302,33 @@ export function parseTemplate(template: string, data: TemplateData): string {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
export async function getSettingsWithDefaults(): Promise<Settings> {
|
export async function getSettingsWithDefaults(): Promise<Settings> {
|
||||||
const settings = getSettings();
|
const settings = await loadSettings();
|
||||||
if (!settings.downloadPath) {
|
if (!settings.downloadPath) {
|
||||||
settings.downloadPath = await fetchDefaultPath();
|
settings.downloadPath = await fetchDefaultPath();
|
||||||
|
await saveSettings(settings);
|
||||||
}
|
}
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
export function saveSettings(settings: Settings): void {
|
export async function saveSettings(settings: Settings): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
cachedSettings = settings;
|
||||||
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
|
localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
|
||||||
|
await SaveToBackend(settings as any);
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error("Failed to save settings:", 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 current = getSettings();
|
||||||
const updated = { ...current, ...partial };
|
const updated = { ...current, ...partial };
|
||||||
saveSettings(updated);
|
await saveSettings(updated);
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
export async function resetToDefaultSettings(): Promise<Settings> {
|
export async function resetToDefaultSettings(): Promise<Settings> {
|
||||||
const defaultPath = await fetchDefaultPath();
|
const defaultPath = await fetchDefaultPath();
|
||||||
const defaultSettings = { ...DEFAULT_SETTINGS, downloadPath: defaultPath };
|
const defaultSettings = { ...DEFAULT_SETTINGS, downloadPath: defaultPath };
|
||||||
saveSettings(defaultSettings);
|
await saveSettings(defaultSettings);
|
||||||
return defaultSettings;
|
return defaultSettings;
|
||||||
}
|
}
|
||||||
export function applyThemeMode(mode: "auto" | "light" | "dark"): void {
|
export function applyThemeMode(mode: "auto" | "light" | "dark"): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user