mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
add global music folder selector
This commit is contained in:
+1
-1
@@ -127,7 +127,7 @@
|
|||||||
"semver": "^7.5.4",
|
"semver": "^7.5.4",
|
||||||
"string-to-color": "^2.2.2",
|
"string-to-color": "^2.2.2",
|
||||||
"ws": "^8.18.2",
|
"ws": "^8.18.2",
|
||||||
"zod": "^4.1.12",
|
"zod": "^3.22.3",
|
||||||
"zustand": "^5.0.5"
|
"zustand": "^5.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
Generated
+85
-85
@@ -61,7 +61,7 @@ importers:
|
|||||||
version: 5.90.11(@tanstack/react-query@5.90.9(react@19.1.0))(react@19.1.0)
|
version: 5.90.11(@tanstack/react-query@5.90.9(react@19.1.0))(react@19.1.0)
|
||||||
'@ts-rest/core':
|
'@ts-rest/core':
|
||||||
specifier: ^3.52.1
|
specifier: ^3.52.1
|
||||||
version: 3.52.1(@types/node@24.10.1)(zod@4.1.12)
|
version: 3.52.1(@types/node@24.10.1)(zod@3.25.76)
|
||||||
'@types/react-window':
|
'@types/react-window':
|
||||||
specifier: ^1.8.8
|
specifier: ^1.8.8
|
||||||
version: 1.8.8
|
version: 1.8.8
|
||||||
@@ -121,7 +121,7 @@ importers:
|
|||||||
version: 7.1.0
|
version: 7.1.0
|
||||||
i18next:
|
i18next:
|
||||||
specifier: ^25.6.2
|
specifier: ^25.6.2
|
||||||
version: 25.6.2(typescript@5.9.3)
|
version: 25.6.2(typescript@5.8.3)
|
||||||
idb-keyval:
|
idb-keyval:
|
||||||
specifier: ^6.2.2
|
specifier: ^6.2.2
|
||||||
version: 6.2.2
|
version: 6.2.2
|
||||||
@@ -175,7 +175,7 @@ importers:
|
|||||||
version: 5.0.0(react@19.1.0)
|
version: 5.0.0(react@19.1.0)
|
||||||
react-i18next:
|
react-i18next:
|
||||||
specifier: ^16.3.3
|
specifier: ^16.3.3
|
||||||
version: 16.3.3(i18next@25.6.2(typescript@5.9.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.9.3)
|
version: 16.3.3(i18next@25.6.2(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
|
||||||
react-icons:
|
react-icons:
|
||||||
specifier: ^5.5.0
|
specifier: ^5.5.0
|
||||||
version: 5.5.0(react@19.1.0)
|
version: 5.5.0(react@19.1.0)
|
||||||
@@ -210,8 +210,8 @@ importers:
|
|||||||
specifier: ^8.18.2
|
specifier: ^8.18.2
|
||||||
version: 8.18.2
|
version: 8.18.2
|
||||||
zod:
|
zod:
|
||||||
specifier: ^4.1.12
|
specifier: ^3.22.3
|
||||||
version: 4.1.12
|
version: 3.25.76
|
||||||
zustand:
|
zustand:
|
||||||
specifier: ^5.0.5
|
specifier: ^5.0.5
|
||||||
version: 5.0.8(@types/react@19.2.5)(immer@10.2.0)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0))
|
version: 5.0.8(@types/react@19.2.5)(immer@10.2.0)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0))
|
||||||
@@ -221,7 +221,7 @@ importers:
|
|||||||
version: 3.0.0(eslint@9.27.0)(prettier@3.6.2)
|
version: 3.0.0(eslint@9.27.0)(prettier@3.6.2)
|
||||||
'@electron-toolkit/eslint-config-ts':
|
'@electron-toolkit/eslint-config-ts':
|
||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.1.0(eslint@9.27.0)(typescript@5.9.3)
|
version: 3.1.0(eslint@9.27.0)(typescript@5.8.3)
|
||||||
'@electron-toolkit/tsconfig':
|
'@electron-toolkit/tsconfig':
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.0.0(@types/node@24.10.1)
|
version: 2.0.0(@types/node@24.10.1)
|
||||||
@@ -275,7 +275,7 @@ importers:
|
|||||||
version: 9.27.0
|
version: 9.27.0
|
||||||
eslint-plugin-perfectionist:
|
eslint-plugin-perfectionist:
|
||||||
specifier: ^4.13.0
|
specifier: ^4.13.0
|
||||||
version: 4.13.0(eslint@9.27.0)(typescript@5.9.3)
|
version: 4.13.0(eslint@9.27.0)(typescript@5.8.3)
|
||||||
eslint-plugin-prettier:
|
eslint-plugin-prettier:
|
||||||
specifier: ^5.4.0
|
specifier: ^5.4.0
|
||||||
version: 5.4.0(eslint-config-prettier@10.1.5(eslint@9.27.0))(eslint@9.27.0)(prettier@3.6.2)
|
version: 5.4.0(eslint-config-prettier@10.1.5(eslint@9.27.0))(eslint@9.27.0)(prettier@3.6.2)
|
||||||
@@ -305,19 +305,19 @@ importers:
|
|||||||
version: 2.5.19(prettier@3.6.2)
|
version: 2.5.19(prettier@3.6.2)
|
||||||
stylelint:
|
stylelint:
|
||||||
specifier: ^16.25.0
|
specifier: ^16.25.0
|
||||||
version: 16.25.0(typescript@5.9.3)
|
version: 16.25.0(typescript@5.8.3)
|
||||||
stylelint-config-css-modules:
|
stylelint-config-css-modules:
|
||||||
specifier: ^4.5.1
|
specifier: ^4.5.1
|
||||||
version: 4.5.1(stylelint@16.25.0(typescript@5.9.3))
|
version: 4.5.1(stylelint@16.25.0(typescript@5.8.3))
|
||||||
stylelint-config-recess-order:
|
stylelint-config-recess-order:
|
||||||
specifier: ^7.4.0
|
specifier: ^7.4.0
|
||||||
version: 7.4.0(stylelint-order@6.0.4(stylelint@16.25.0(typescript@5.9.3)))(stylelint@16.25.0(typescript@5.9.3))
|
version: 7.4.0(stylelint-order@6.0.4(stylelint@16.25.0(typescript@5.8.3)))(stylelint@16.25.0(typescript@5.8.3))
|
||||||
stylelint-config-standard:
|
stylelint-config-standard:
|
||||||
specifier: ^39.0.1
|
specifier: ^39.0.1
|
||||||
version: 39.0.1(stylelint@16.25.0(typescript@5.9.3))
|
version: 39.0.1(stylelint@16.25.0(typescript@5.8.3))
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.8.3
|
specifier: ^5.8.3
|
||||||
version: 5.9.3
|
version: 5.8.3
|
||||||
vite:
|
vite:
|
||||||
specifier: ^7.2.2
|
specifier: ^7.2.2
|
||||||
version: 7.2.2(@types/node@24.10.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(yaml@2.8.1)
|
version: 7.2.2(@types/node@24.10.1)(sass-embedded@1.89.0)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(yaml@2.8.1)
|
||||||
@@ -5367,8 +5367,8 @@ packages:
|
|||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
typescript: '>=4.8.4 <5.9.0'
|
typescript: '>=4.8.4 <5.9.0'
|
||||||
|
|
||||||
typescript@5.9.3:
|
typescript@5.8.3:
|
||||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -5790,8 +5790,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
zod: ^3.25.0 || ^4.0.0
|
zod: ^3.25.0 || ^4.0.0
|
||||||
|
|
||||||
zod@4.1.12:
|
zod@3.25.76:
|
||||||
resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==}
|
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||||
|
|
||||||
zustand@5.0.8:
|
zustand@5.0.8:
|
||||||
resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==}
|
resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==}
|
||||||
@@ -6608,14 +6608,14 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/eslint'
|
- '@types/eslint'
|
||||||
|
|
||||||
'@electron-toolkit/eslint-config-ts@3.1.0(eslint@9.27.0)(typescript@5.9.3)':
|
'@electron-toolkit/eslint-config-ts@3.1.0(eslint@9.27.0)(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint/js': 9.27.0
|
'@eslint/js': 9.27.0
|
||||||
eslint: 9.27.0
|
eslint: 9.27.0
|
||||||
globals: 16.1.0
|
globals: 16.1.0
|
||||||
typescript-eslint: 8.32.1(eslint@9.27.0)(typescript@5.9.3)
|
typescript-eslint: 8.32.1(eslint@9.27.0)(typescript@5.8.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.8.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -7504,10 +7504,10 @@ snapshots:
|
|||||||
|
|
||||||
'@tootallnate/once@2.0.0': {}
|
'@tootallnate/once@2.0.0': {}
|
||||||
|
|
||||||
'@ts-rest/core@3.52.1(@types/node@24.10.1)(zod@4.1.12)':
|
'@ts-rest/core@3.52.1(@types/node@24.10.1)(zod@3.25.76)':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 24.10.1
|
||||||
zod: 4.1.12
|
zod: 3.25.76
|
||||||
|
|
||||||
'@types/babel__core@7.20.5':
|
'@types/babel__core@7.20.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7625,32 +7625,32 @@ snapshots:
|
|||||||
'@types/node': 24.10.1
|
'@types/node': 24.10.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.27.0)(typescript@5.9.3))(eslint@9.27.0)(typescript@5.9.3)':
|
'@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.27.0)(typescript@5.8.3))(eslint@9.27.0)(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.1
|
'@eslint-community/regexpp': 4.12.1
|
||||||
'@typescript-eslint/parser': 8.32.1(eslint@9.27.0)(typescript@5.9.3)
|
'@typescript-eslint/parser': 8.32.1(eslint@9.27.0)(typescript@5.8.3)
|
||||||
'@typescript-eslint/scope-manager': 8.32.1
|
'@typescript-eslint/scope-manager': 8.32.1
|
||||||
'@typescript-eslint/type-utils': 8.32.1(eslint@9.27.0)(typescript@5.9.3)
|
'@typescript-eslint/type-utils': 8.32.1(eslint@9.27.0)(typescript@5.8.3)
|
||||||
'@typescript-eslint/utils': 8.32.1(eslint@9.27.0)(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.32.1(eslint@9.27.0)(typescript@5.8.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.32.1
|
'@typescript-eslint/visitor-keys': 8.32.1
|
||||||
eslint: 9.27.0
|
eslint: 9.27.0
|
||||||
graphemer: 1.4.0
|
graphemer: 1.4.0
|
||||||
ignore: 7.0.4
|
ignore: 7.0.4
|
||||||
natural-compare: 1.4.0
|
natural-compare: 1.4.0
|
||||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
ts-api-utils: 2.1.0(typescript@5.8.3)
|
||||||
typescript: 5.9.3
|
typescript: 5.8.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/parser@8.32.1(eslint@9.27.0)(typescript@5.9.3)':
|
'@typescript-eslint/parser@8.32.1(eslint@9.27.0)(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/scope-manager': 8.32.1
|
'@typescript-eslint/scope-manager': 8.32.1
|
||||||
'@typescript-eslint/types': 8.32.1
|
'@typescript-eslint/types': 8.32.1
|
||||||
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.32.1
|
'@typescript-eslint/visitor-keys': 8.32.1
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 9.27.0
|
eslint: 9.27.0
|
||||||
typescript: 5.9.3
|
typescript: 5.8.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -7659,20 +7659,20 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.32.1
|
'@typescript-eslint/types': 8.32.1
|
||||||
'@typescript-eslint/visitor-keys': 8.32.1
|
'@typescript-eslint/visitor-keys': 8.32.1
|
||||||
|
|
||||||
'@typescript-eslint/type-utils@8.32.1(eslint@9.27.0)(typescript@5.9.3)':
|
'@typescript-eslint/type-utils@8.32.1(eslint@9.27.0)(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
|
||||||
'@typescript-eslint/utils': 8.32.1(eslint@9.27.0)(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.32.1(eslint@9.27.0)(typescript@5.8.3)
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
eslint: 9.27.0
|
eslint: 9.27.0
|
||||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
ts-api-utils: 2.1.0(typescript@5.8.3)
|
||||||
typescript: 5.9.3
|
typescript: 5.8.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/types@8.32.1': {}
|
'@typescript-eslint/types@8.32.1': {}
|
||||||
|
|
||||||
'@typescript-eslint/typescript-estree@8.32.1(typescript@5.9.3)':
|
'@typescript-eslint/typescript-estree@8.32.1(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.32.1
|
'@typescript-eslint/types': 8.32.1
|
||||||
'@typescript-eslint/visitor-keys': 8.32.1
|
'@typescript-eslint/visitor-keys': 8.32.1
|
||||||
@@ -7681,19 +7681,19 @@ snapshots:
|
|||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
minimatch: 9.0.5
|
minimatch: 9.0.5
|
||||||
semver: 7.7.2
|
semver: 7.7.2
|
||||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
ts-api-utils: 2.1.0(typescript@5.8.3)
|
||||||
typescript: 5.9.3
|
typescript: 5.8.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@typescript-eslint/utils@8.32.1(eslint@9.27.0)(typescript@5.9.3)':
|
'@typescript-eslint/utils@8.32.1(eslint@9.27.0)(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.7.0(eslint@9.27.0)
|
'@eslint-community/eslint-utils': 4.7.0(eslint@9.27.0)
|
||||||
'@typescript-eslint/scope-manager': 8.32.1
|
'@typescript-eslint/scope-manager': 8.32.1
|
||||||
'@typescript-eslint/types': 8.32.1
|
'@typescript-eslint/types': 8.32.1
|
||||||
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
|
||||||
eslint: 9.27.0
|
eslint: 9.27.0
|
||||||
typescript: 5.9.3
|
typescript: 5.8.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -8301,7 +8301,7 @@ snapshots:
|
|||||||
config-file-ts@0.2.8-rc1:
|
config-file-ts@0.2.8-rc1:
|
||||||
dependencies:
|
dependencies:
|
||||||
glob: 10.4.5
|
glob: 10.4.5
|
||||||
typescript: 5.9.3
|
typescript: 5.8.3
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
@@ -8316,14 +8316,14 @@ snapshots:
|
|||||||
|
|
||||||
core-util-is@1.0.3: {}
|
core-util-is@1.0.3: {}
|
||||||
|
|
||||||
cosmiconfig@9.0.0(typescript@5.9.3):
|
cosmiconfig@9.0.0(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
env-paths: 2.2.1
|
env-paths: 2.2.1
|
||||||
import-fresh: 3.3.1
|
import-fresh: 3.3.1
|
||||||
js-yaml: 4.1.0
|
js-yaml: 4.1.0
|
||||||
parse-json: 5.2.0
|
parse-json: 5.2.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.8.3
|
||||||
|
|
||||||
crc@3.8.0:
|
crc@3.8.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8852,10 +8852,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
eslint: 9.27.0
|
eslint: 9.27.0
|
||||||
|
|
||||||
eslint-plugin-perfectionist@4.13.0(eslint@9.27.0)(typescript@5.9.3):
|
eslint-plugin-perfectionist@4.13.0(eslint@9.27.0)(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.32.1
|
'@typescript-eslint/types': 8.32.1
|
||||||
'@typescript-eslint/utils': 8.32.1(eslint@9.27.0)(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.32.1(eslint@9.27.0)(typescript@5.8.3)
|
||||||
eslint: 9.27.0
|
eslint: 9.27.0
|
||||||
natural-orderby: 5.0.0
|
natural-orderby: 5.0.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -8877,8 +8877,8 @@ snapshots:
|
|||||||
'@babel/parser': 7.28.5
|
'@babel/parser': 7.28.5
|
||||||
eslint: 9.27.0
|
eslint: 9.27.0
|
||||||
hermes-parser: 0.25.1
|
hermes-parser: 0.25.1
|
||||||
zod: 4.1.12
|
zod: 3.25.76
|
||||||
zod-validation-error: 4.0.2(zod@4.1.12)
|
zod-validation-error: 4.0.2(zod@3.25.76)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -9461,28 +9461,28 @@ snapshots:
|
|||||||
esbuild: 0.25.11
|
esbuild: 0.25.11
|
||||||
fs-extra: 11.3.2
|
fs-extra: 11.3.2
|
||||||
gulp-sort: 2.0.0
|
gulp-sort: 2.0.0
|
||||||
i18next: 24.2.3(typescript@5.9.3)
|
i18next: 24.2.3(typescript@5.8.3)
|
||||||
js-yaml: 4.1.0
|
js-yaml: 4.1.0
|
||||||
lilconfig: 3.1.3
|
lilconfig: 3.1.3
|
||||||
rsvp: 4.8.5
|
rsvp: 4.8.5
|
||||||
sort-keys: 5.1.0
|
sort-keys: 5.1.0
|
||||||
typescript: 5.9.3
|
typescript: 5.8.3
|
||||||
vinyl: 3.0.0
|
vinyl: 3.0.0
|
||||||
vinyl-fs: 4.0.0
|
vinyl-fs: 4.0.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
i18next@24.2.3(typescript@5.9.3):
|
i18next@24.2.3(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.1
|
'@babel/runtime': 7.27.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.8.3
|
||||||
|
|
||||||
i18next@25.6.2(typescript@5.9.3):
|
i18next@25.6.2(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.8.3
|
||||||
|
|
||||||
iconv-corefoundation@1.1.7:
|
iconv-corefoundation@1.1.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -10431,16 +10431,16 @@ snapshots:
|
|||||||
|
|
||||||
react-fast-compare@3.2.2: {}
|
react-fast-compare@3.2.2: {}
|
||||||
|
|
||||||
react-i18next@16.3.3(i18next@25.6.2(typescript@5.9.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.9.3):
|
react-i18next@16.3.3(i18next@25.6.2(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.4
|
'@babel/runtime': 7.28.4
|
||||||
html-parse-stringify: 3.0.1
|
html-parse-stringify: 3.0.1
|
||||||
i18next: 25.6.2(typescript@5.9.3)
|
i18next: 25.6.2(typescript@5.8.3)
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
use-sync-external-store: 1.6.0(react@19.1.0)
|
use-sync-external-store: 1.6.0(react@19.1.0)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
typescript: 5.9.3
|
typescript: 5.8.3
|
||||||
|
|
||||||
react-icons@5.5.0(react@19.1.0):
|
react-icons@5.5.0(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -11139,33 +11139,33 @@ snapshots:
|
|||||||
|
|
||||||
strnum@2.1.1: {}
|
strnum@2.1.1: {}
|
||||||
|
|
||||||
stylelint-config-css-modules@4.5.1(stylelint@16.25.0(typescript@5.9.3)):
|
stylelint-config-css-modules@4.5.1(stylelint@16.25.0(typescript@5.8.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
stylelint: 16.25.0(typescript@5.9.3)
|
stylelint: 16.25.0(typescript@5.8.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
stylelint-scss: 6.12.1(stylelint@16.25.0(typescript@5.9.3))
|
stylelint-scss: 6.12.1(stylelint@16.25.0(typescript@5.8.3))
|
||||||
|
|
||||||
stylelint-config-recess-order@7.4.0(stylelint-order@6.0.4(stylelint@16.25.0(typescript@5.9.3)))(stylelint@16.25.0(typescript@5.9.3)):
|
stylelint-config-recess-order@7.4.0(stylelint-order@6.0.4(stylelint@16.25.0(typescript@5.8.3)))(stylelint@16.25.0(typescript@5.8.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
stylelint: 16.25.0(typescript@5.9.3)
|
stylelint: 16.25.0(typescript@5.8.3)
|
||||||
stylelint-order: 6.0.4(stylelint@16.25.0(typescript@5.9.3))
|
stylelint-order: 6.0.4(stylelint@16.25.0(typescript@5.8.3))
|
||||||
|
|
||||||
stylelint-config-recommended@17.0.0(stylelint@16.25.0(typescript@5.9.3)):
|
stylelint-config-recommended@17.0.0(stylelint@16.25.0(typescript@5.8.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
stylelint: 16.25.0(typescript@5.9.3)
|
stylelint: 16.25.0(typescript@5.8.3)
|
||||||
|
|
||||||
stylelint-config-standard@39.0.1(stylelint@16.25.0(typescript@5.9.3)):
|
stylelint-config-standard@39.0.1(stylelint@16.25.0(typescript@5.8.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
stylelint: 16.25.0(typescript@5.9.3)
|
stylelint: 16.25.0(typescript@5.8.3)
|
||||||
stylelint-config-recommended: 17.0.0(stylelint@16.25.0(typescript@5.9.3))
|
stylelint-config-recommended: 17.0.0(stylelint@16.25.0(typescript@5.8.3))
|
||||||
|
|
||||||
stylelint-order@6.0.4(stylelint@16.25.0(typescript@5.9.3)):
|
stylelint-order@6.0.4(stylelint@16.25.0(typescript@5.8.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss: 8.5.6
|
postcss: 8.5.6
|
||||||
postcss-sorting: 8.0.2(postcss@8.5.6)
|
postcss-sorting: 8.0.2(postcss@8.5.6)
|
||||||
stylelint: 16.25.0(typescript@5.9.3)
|
stylelint: 16.25.0(typescript@5.8.3)
|
||||||
|
|
||||||
stylelint-scss@6.12.1(stylelint@16.25.0(typescript@5.9.3)):
|
stylelint-scss@6.12.1(stylelint@16.25.0(typescript@5.8.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
css-tree: 3.1.0
|
css-tree: 3.1.0
|
||||||
is-plain-object: 5.0.0
|
is-plain-object: 5.0.0
|
||||||
@@ -11175,10 +11175,10 @@ snapshots:
|
|||||||
postcss-resolve-nested-selector: 0.1.6
|
postcss-resolve-nested-selector: 0.1.6
|
||||||
postcss-selector-parser: 7.1.0
|
postcss-selector-parser: 7.1.0
|
||||||
postcss-value-parser: 4.2.0
|
postcss-value-parser: 4.2.0
|
||||||
stylelint: 16.25.0(typescript@5.9.3)
|
stylelint: 16.25.0(typescript@5.8.3)
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
stylelint@16.25.0(typescript@5.9.3):
|
stylelint@16.25.0(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
'@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
|
||||||
'@csstools/css-tokenizer': 3.0.4
|
'@csstools/css-tokenizer': 3.0.4
|
||||||
@@ -11187,7 +11187,7 @@ snapshots:
|
|||||||
'@dual-bundle/import-meta-resolve': 4.2.1
|
'@dual-bundle/import-meta-resolve': 4.2.1
|
||||||
balanced-match: 2.0.0
|
balanced-match: 2.0.0
|
||||||
colord: 2.9.3
|
colord: 2.9.3
|
||||||
cosmiconfig: 9.0.0(typescript@5.9.3)
|
cosmiconfig: 9.0.0(typescript@5.8.3)
|
||||||
css-functions-list: 3.2.3
|
css-functions-list: 3.2.3
|
||||||
css-tree: 3.1.0
|
css-tree: 3.1.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3
|
||||||
@@ -11362,9 +11362,9 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
utf8-byte-length: 1.0.5
|
utf8-byte-length: 1.0.5
|
||||||
|
|
||||||
ts-api-utils@2.1.0(typescript@5.9.3):
|
ts-api-utils@2.1.0(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.8.3
|
||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
@@ -11414,17 +11414,17 @@ snapshots:
|
|||||||
possible-typed-array-names: 1.1.0
|
possible-typed-array-names: 1.1.0
|
||||||
reflect.getprototypeof: 1.0.10
|
reflect.getprototypeof: 1.0.10
|
||||||
|
|
||||||
typescript-eslint@8.32.1(eslint@9.27.0)(typescript@5.9.3):
|
typescript-eslint@8.32.1(eslint@9.27.0)(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/eslint-plugin': 8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.27.0)(typescript@5.9.3))(eslint@9.27.0)(typescript@5.9.3)
|
'@typescript-eslint/eslint-plugin': 8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.27.0)(typescript@5.8.3))(eslint@9.27.0)(typescript@5.8.3)
|
||||||
'@typescript-eslint/parser': 8.32.1(eslint@9.27.0)(typescript@5.9.3)
|
'@typescript-eslint/parser': 8.32.1(eslint@9.27.0)(typescript@5.8.3)
|
||||||
'@typescript-eslint/utils': 8.32.1(eslint@9.27.0)(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.32.1(eslint@9.27.0)(typescript@5.8.3)
|
||||||
eslint: 9.27.0
|
eslint: 9.27.0
|
||||||
typescript: 5.9.3
|
typescript: 5.8.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
typescript@5.9.3: {}
|
typescript@5.8.3: {}
|
||||||
|
|
||||||
unbox-primitive@1.1.0:
|
unbox-primitive@1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -11883,11 +11883,11 @@ snapshots:
|
|||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
zod-validation-error@4.0.2(zod@4.1.12):
|
zod-validation-error@4.0.2(zod@3.25.76):
|
||||||
dependencies:
|
dependencies:
|
||||||
zod: 4.1.12
|
zod: 3.25.76
|
||||||
|
|
||||||
zod@4.1.12: {}
|
zod@3.25.76: {}
|
||||||
|
|
||||||
zustand@5.0.8(@types/react@19.2.5)(immer@10.2.0)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)):
|
zustand@5.0.8(@types/react@19.2.5)(immer@10.2.0)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
|||||||
@@ -349,6 +349,9 @@
|
|||||||
"openBrowserDevtools": "open browser devtools",
|
"openBrowserDevtools": "open browser devtools",
|
||||||
"quit": "$t(common.quit)",
|
"quit": "$t(common.quit)",
|
||||||
"selectServer": "select server",
|
"selectServer": "select server",
|
||||||
|
"selectMusicFolder": "select music folder",
|
||||||
|
"noMusicFolder": "no music folder selected",
|
||||||
|
"multipleMusicFolders": "{{count}} music folders selected",
|
||||||
"settings": "$t(common.setting_other)",
|
"settings": "$t(common.setting_other)",
|
||||||
"version": "version {{version}}"
|
"version": "version {{version}}"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import i18n from '/@/i18n/i18n';
|
|||||||
import { JellyfinController } from '/@/renderer/api/jellyfin/jellyfin-controller';
|
import { JellyfinController } from '/@/renderer/api/jellyfin/jellyfin-controller';
|
||||||
import { NavidromeController } from '/@/renderer/api/navidrome/navidrome-controller';
|
import { NavidromeController } from '/@/renderer/api/navidrome/navidrome-controller';
|
||||||
import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller';
|
import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller';
|
||||||
|
import { mergeMusicFolderId } from '/@/renderer/api/utils-music-folder';
|
||||||
import { getServerById, useAuthStore } from '/@/renderer/store';
|
import { getServerById, useAuthStore } from '/@/renderer/store';
|
||||||
import { toast } from '/@/shared/components/toast/toast';
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
import {
|
import {
|
||||||
@@ -167,7 +168,11 @@ export const controller: GeneralController = {
|
|||||||
return apiController(
|
return apiController(
|
||||||
'getAlbumArtistList',
|
'getAlbumArtistList',
|
||||||
server.type,
|
server.type,
|
||||||
)?.({ ...args, apiClientProps: { ...args.apiClientProps, server } });
|
)?.({
|
||||||
|
...args,
|
||||||
|
apiClientProps: { ...args.apiClientProps, server },
|
||||||
|
query: mergeMusicFolderId(args.query, server),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
getAlbumArtistListCount(args) {
|
getAlbumArtistListCount(args) {
|
||||||
const server = getServerById(args.apiClientProps.serverId);
|
const server = getServerById(args.apiClientProps.serverId);
|
||||||
@@ -181,7 +186,11 @@ export const controller: GeneralController = {
|
|||||||
return apiController(
|
return apiController(
|
||||||
'getAlbumArtistListCount',
|
'getAlbumArtistListCount',
|
||||||
server.type,
|
server.type,
|
||||||
)?.({ ...args, apiClientProps: { ...args.apiClientProps, server } });
|
)?.({
|
||||||
|
...args,
|
||||||
|
apiClientProps: { ...args.apiClientProps, server },
|
||||||
|
query: mergeMusicFolderId(args.query, server),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
getAlbumDetail(args) {
|
getAlbumDetail(args) {
|
||||||
const server = getServerById(args.apiClientProps.serverId);
|
const server = getServerById(args.apiClientProps.serverId);
|
||||||
@@ -223,7 +232,11 @@ export const controller: GeneralController = {
|
|||||||
return apiController(
|
return apiController(
|
||||||
'getAlbumList',
|
'getAlbumList',
|
||||||
server.type,
|
server.type,
|
||||||
)?.({ ...args, apiClientProps: { ...args.apiClientProps, server } });
|
)?.({
|
||||||
|
...args,
|
||||||
|
apiClientProps: { ...args.apiClientProps, server },
|
||||||
|
query: mergeMusicFolderId(args.query, server),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
getAlbumListCount(args) {
|
getAlbumListCount(args) {
|
||||||
const server = getServerById(args.apiClientProps.serverId);
|
const server = getServerById(args.apiClientProps.serverId);
|
||||||
@@ -237,7 +250,11 @@ export const controller: GeneralController = {
|
|||||||
return apiController(
|
return apiController(
|
||||||
'getAlbumListCount',
|
'getAlbumListCount',
|
||||||
server.type,
|
server.type,
|
||||||
)?.({ ...args, apiClientProps: { ...args.apiClientProps, server } });
|
)?.({
|
||||||
|
...args,
|
||||||
|
apiClientProps: { ...args.apiClientProps, server },
|
||||||
|
query: mergeMusicFolderId(args.query, server),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
getArtistList(args) {
|
getArtistList(args) {
|
||||||
const server = getServerById(args.apiClientProps.serverId);
|
const server = getServerById(args.apiClientProps.serverId);
|
||||||
@@ -251,7 +268,11 @@ export const controller: GeneralController = {
|
|||||||
return apiController(
|
return apiController(
|
||||||
'getArtistList',
|
'getArtistList',
|
||||||
server.type,
|
server.type,
|
||||||
)?.({ ...args, apiClientProps: { ...args.apiClientProps, server } });
|
)?.({
|
||||||
|
...args,
|
||||||
|
apiClientProps: { ...args.apiClientProps, server },
|
||||||
|
query: mergeMusicFolderId(args.query, server),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
getArtistListCount(args) {
|
getArtistListCount(args) {
|
||||||
const server = getServerById(args.apiClientProps.serverId);
|
const server = getServerById(args.apiClientProps.serverId);
|
||||||
@@ -265,7 +286,11 @@ export const controller: GeneralController = {
|
|||||||
return apiController(
|
return apiController(
|
||||||
'getArtistListCount',
|
'getArtistListCount',
|
||||||
server.type,
|
server.type,
|
||||||
)?.({ ...args, apiClientProps: { ...args.apiClientProps, server } });
|
)?.({
|
||||||
|
...args,
|
||||||
|
apiClientProps: { ...args.apiClientProps, server },
|
||||||
|
query: mergeMusicFolderId(args.query, server),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
getDownloadUrl(args) {
|
getDownloadUrl(args) {
|
||||||
const server = getServerById(args.apiClientProps.serverId);
|
const server = getServerById(args.apiClientProps.serverId);
|
||||||
@@ -293,7 +318,11 @@ export const controller: GeneralController = {
|
|||||||
return apiController(
|
return apiController(
|
||||||
'getGenreList',
|
'getGenreList',
|
||||||
server.type,
|
server.type,
|
||||||
)?.({ ...args, apiClientProps: { ...args.apiClientProps, server } });
|
)?.({
|
||||||
|
...args,
|
||||||
|
apiClientProps: { ...args.apiClientProps, server },
|
||||||
|
query: mergeMusicFolderId(args.query, server),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
getLyrics(args) {
|
getLyrics(args) {
|
||||||
const server = getServerById(args.apiClientProps.serverId);
|
const server = getServerById(args.apiClientProps.serverId);
|
||||||
@@ -461,7 +490,11 @@ export const controller: GeneralController = {
|
|||||||
return apiController(
|
return apiController(
|
||||||
'getSongList',
|
'getSongList',
|
||||||
server.type,
|
server.type,
|
||||||
)?.({ ...args, apiClientProps: { ...args.apiClientProps, server } });
|
)?.({
|
||||||
|
...args,
|
||||||
|
apiClientProps: { ...args.apiClientProps, server },
|
||||||
|
query: mergeMusicFolderId(args.query, server),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
getSongListCount(args) {
|
getSongListCount(args) {
|
||||||
const server = getServerById(args.apiClientProps.serverId);
|
const server = getServerById(args.apiClientProps.serverId);
|
||||||
@@ -475,7 +508,11 @@ export const controller: GeneralController = {
|
|||||||
return apiController(
|
return apiController(
|
||||||
'getSongListCount',
|
'getSongListCount',
|
||||||
server.type,
|
server.type,
|
||||||
)?.({ ...args, apiClientProps: { ...args.apiClientProps, server } });
|
)?.({
|
||||||
|
...args,
|
||||||
|
apiClientProps: { ...args.apiClientProps, server },
|
||||||
|
query: mergeMusicFolderId(args.query, server),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
getStructuredLyrics(args) {
|
getStructuredLyrics(args) {
|
||||||
const server = getServerById(args.apiClientProps.serverId);
|
const server = getServerById(args.apiClientProps.serverId);
|
||||||
@@ -601,7 +638,11 @@ export const controller: GeneralController = {
|
|||||||
return apiController(
|
return apiController(
|
||||||
'search',
|
'search',
|
||||||
server.type,
|
server.type,
|
||||||
)?.({ ...args, apiClientProps: { ...args.apiClientProps, server } });
|
)?.({
|
||||||
|
...args,
|
||||||
|
apiClientProps: { ...args.apiClientProps, server },
|
||||||
|
query: mergeMusicFolderId(args.query, server),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
setRating(args) {
|
setRating(args) {
|
||||||
const server = getServerById(args.apiClientProps.serverId);
|
const server = getServerById(args.apiClientProps.serverId);
|
||||||
|
|||||||
@@ -59,6 +59,14 @@ const excludeMissing = (server?: null | ServerListItemWithCredential) => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getLibraryId = (musicFolderId?: string | string[]): string[] | undefined => {
|
||||||
|
if (!musicFolderId) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.isArray(musicFolderId) ? musicFolderId : [musicFolderId];
|
||||||
|
};
|
||||||
|
|
||||||
const getArtistSongKey = (server: null | ServerListItemWithCredential) =>
|
const getArtistSongKey = (server: null | ServerListItemWithCredential) =>
|
||||||
hasFeature(server, ServerFeature.TRACK_ALBUM_ARTIST_SEARCH) ? 'artists_id' : 'album_artist_id';
|
hasFeature(server, ServerFeature.TRACK_ALBUM_ARTIST_SEARCH) ? 'artists_id' : 'album_artist_id';
|
||||||
|
|
||||||
@@ -189,6 +197,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||||
_sort: albumArtistListSortMap.navidrome[query.sortBy],
|
_sort: albumArtistListSortMap.navidrome[query.sortBy],
|
||||||
_start: query.startIndex,
|
_start: query.startIndex,
|
||||||
|
library_id: getLibraryId(query.musicFolderId),
|
||||||
name: query.searchTerm,
|
name: query.searchTerm,
|
||||||
...query._custom,
|
...query._custom,
|
||||||
role: hasFeature(apiClientProps.server, ServerFeature.BFR) ? 'albumartist' : '',
|
role: hasFeature(apiClientProps.server, ServerFeature.BFR) ? 'albumartist' : '',
|
||||||
@@ -287,6 +296,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
artist_id: query.artistIds?.[0],
|
artist_id: query.artistIds?.[0],
|
||||||
compilation: query.compilation,
|
compilation: query.compilation,
|
||||||
genre_id: genres,
|
genre_id: genres,
|
||||||
|
library_id: getLibraryId(query.musicFolderId),
|
||||||
name: query.searchTerm,
|
name: query.searchTerm,
|
||||||
...query._custom,
|
...query._custom,
|
||||||
starred: query.favorite,
|
starred: query.favorite,
|
||||||
@@ -318,6 +328,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||||
_sort: albumArtistListSortMap.navidrome[query.sortBy],
|
_sort: albumArtistListSortMap.navidrome[query.sortBy],
|
||||||
_start: query.startIndex,
|
_start: query.startIndex,
|
||||||
|
library_id: getLibraryId(query.musicFolderId),
|
||||||
name: query.searchTerm,
|
name: query.searchTerm,
|
||||||
...query._custom,
|
...query._custom,
|
||||||
role: query.role || undefined,
|
role: query.role || undefined,
|
||||||
@@ -361,6 +372,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||||
_sort: genreListSortMap.navidrome[query.sortBy],
|
_sort: genreListSortMap.navidrome[query.sortBy],
|
||||||
_start: query.startIndex,
|
_start: query.startIndex,
|
||||||
|
library_id: getLibraryId(query.musicFolderId),
|
||||||
name: query.searchTerm,
|
name: query.searchTerm,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -480,6 +492,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
...navidromeFeatures,
|
...navidromeFeatures,
|
||||||
...subsonicArgs.features,
|
...subsonicArgs.features,
|
||||||
publicPlaylist: [1],
|
publicPlaylist: [1],
|
||||||
|
[ServerFeature.MUSIC_FOLDER_MULTISELECT]: [1],
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -567,6 +580,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
album_id: query.albumIds,
|
album_id: query.albumIds,
|
||||||
genre_id: query.genreIds,
|
genre_id: query.genreIds,
|
||||||
[getArtistSongKey(apiClientProps.server)]: query.artistIds ?? query.albumArtistIds,
|
[getArtistSongKey(apiClientProps.server)]: query.artistIds ?? query.albumArtistIds,
|
||||||
|
library_id: getLibraryId(query.musicFolderId),
|
||||||
starred: query.favorite,
|
starred: query.favorite,
|
||||||
title: query.searchTerm,
|
title: query.searchTerm,
|
||||||
...query._custom,
|
...query._custom,
|
||||||
|
|||||||
@@ -1388,7 +1388,7 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
setRating: async (args) => {
|
setRating: async (args) => {
|
||||||
const { apiClientProps, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
const itemIds = query.item.map((item) => item.id);
|
const itemIds = query.id;
|
||||||
|
|
||||||
for (const id of itemIds) {
|
for (const id of itemIds) {
|
||||||
await ssApiClient(apiClientProps).setRating({
|
await ssApiClient(apiClientProps).setRating({
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { ServerListItemWithCredential } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
export const mergeMusicFolderId = <T extends { musicFolderId?: string | string[] }>(
|
||||||
|
query: T,
|
||||||
|
server: null | ServerListItemWithCredential,
|
||||||
|
): T => {
|
||||||
|
if (
|
||||||
|
!server ||
|
||||||
|
!server.musicFolderId ||
|
||||||
|
server.musicFolderId.length === 0 ||
|
||||||
|
query.musicFolderId
|
||||||
|
) {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only merge if server matches and musicFolderId is not already in query
|
||||||
|
const musicFolderId =
|
||||||
|
server.musicFolderId.length === 1 ? server.musicFolderId[0] : server.musicFolderId;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...query,
|
||||||
|
musicFolderId,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { ALBUM_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
import { ALBUM_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import { ListFilters } from '/@/renderer/features/shared/components/list-filters';
|
import { ListFilters } from '/@/renderer/features/shared/components/list-filters';
|
||||||
import { ListMusicFolderDropdown } from '/@/renderer/features/shared/components/list-music-folder-dropdown';
|
|
||||||
import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button';
|
import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button';
|
||||||
import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown';
|
import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown';
|
||||||
import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button';
|
import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button';
|
||||||
@@ -25,7 +24,6 @@ export const AlbumListHeaderFilters = () => {
|
|||||||
defaultSortOrder={SortOrder.ASC}
|
defaultSortOrder={SortOrder.ASC}
|
||||||
listKey={ItemListKey.ALBUM}
|
listKey={ItemListKey.ALBUM}
|
||||||
/>
|
/>
|
||||||
<ListMusicFolderDropdown listKey={ItemListKey.ALBUM} />
|
|
||||||
<ListFilters itemType={LibraryItem.ALBUM} />
|
<ListFilters itemType={LibraryItem.ALBUM} />
|
||||||
<ListRefreshButton listKey={ItemListKey.ALBUM} />
|
<ListRefreshButton listKey={ItemListKey.ALBUM} />
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
useQueryState,
|
useQueryState,
|
||||||
} from 'nuqs';
|
} from 'nuqs';
|
||||||
|
|
||||||
import { useMusicFolderIdFilter } from '/@/renderer/features/shared/hooks/use-music-folder-id-filter';
|
|
||||||
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
|
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
|
||||||
import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter';
|
import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter';
|
||||||
import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-order-filter';
|
import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-order-filter';
|
||||||
@@ -20,8 +19,6 @@ export const useAlbumListFilters = () => {
|
|||||||
|
|
||||||
const { sortOrder } = useSortOrderFilter(null, ItemListKey.ALBUM);
|
const { sortOrder } = useSortOrderFilter(null, ItemListKey.ALBUM);
|
||||||
|
|
||||||
const { musicFolderId } = useMusicFolderIdFilter(null, ItemListKey.ALBUM);
|
|
||||||
|
|
||||||
const { searchTerm, setSearchTerm } = useSearchTermFilter('');
|
const { searchTerm, setSearchTerm } = useSearchTermFilter('');
|
||||||
|
|
||||||
const [genreId, setGenreId] = useQueryState(
|
const [genreId, setGenreId] = useQueryState(
|
||||||
@@ -67,7 +64,6 @@ export const useAlbumListFilters = () => {
|
|||||||
[FILTER_KEYS.ALBUM.MAX_YEAR]: maxYear ?? undefined,
|
[FILTER_KEYS.ALBUM.MAX_YEAR]: maxYear ?? undefined,
|
||||||
[FILTER_KEYS.ALBUM.MIN_YEAR]: minYear ?? undefined,
|
[FILTER_KEYS.ALBUM.MIN_YEAR]: minYear ?? undefined,
|
||||||
[FILTER_KEYS.ALBUM.RECENTLY_PLAYED]: recentlyPlayed ?? undefined,
|
[FILTER_KEYS.ALBUM.RECENTLY_PLAYED]: recentlyPlayed ?? undefined,
|
||||||
[FILTER_KEYS.SHARED.MUSIC_FOLDER_ID]: musicFolderId ?? undefined,
|
|
||||||
[FILTER_KEYS.SHARED.SEARCH_TERM]: searchTerm ?? undefined,
|
[FILTER_KEYS.SHARED.SEARCH_TERM]: searchTerm ?? undefined,
|
||||||
[FILTER_KEYS.SHARED.SORT_BY]: sortBy ?? undefined,
|
[FILTER_KEYS.SHARED.SORT_BY]: sortBy ?? undefined,
|
||||||
[FILTER_KEYS.SHARED.SORT_ORDER]: sortOrder ?? undefined,
|
[FILTER_KEYS.SHARED.SORT_ORDER]: sortOrder ?? undefined,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ALBUM_ARTIST_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
import { ALBUM_ARTIST_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import { ListMusicFolderDropdown } from '/@/renderer/features/shared/components/list-music-folder-dropdown';
|
|
||||||
import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button';
|
import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button';
|
||||||
import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown';
|
import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown';
|
||||||
import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button';
|
import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button';
|
||||||
@@ -27,7 +26,6 @@ export const AlbumArtistListHeaderFilters = () => {
|
|||||||
defaultSortOrder={SortOrder.ASC}
|
defaultSortOrder={SortOrder.ASC}
|
||||||
listKey={ItemListKey.ALBUM_ARTIST}
|
listKey={ItemListKey.ALBUM_ARTIST}
|
||||||
/>
|
/>
|
||||||
<ListMusicFolderDropdown listKey={ItemListKey.ALBUM_ARTIST} />
|
|
||||||
<ListRefreshButton listKey={ItemListKey.ALBUM_ARTIST} />
|
<ListRefreshButton listKey={ItemListKey.ALBUM_ARTIST} />
|
||||||
</Group>
|
</Group>
|
||||||
<Group gap="sm" wrap="nowrap">
|
<Group gap="sm" wrap="nowrap">
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { ALBUM_ARTIST_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
import { ALBUM_ARTIST_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||||
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import { ListMusicFolderDropdown } from '/@/renderer/features/shared/components/list-music-folder-dropdown';
|
|
||||||
import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button';
|
import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button';
|
||||||
import { ListSelectFilter } from '/@/renderer/features/shared/components/list-select-filter';
|
import { ListSelectFilter } from '/@/renderer/features/shared/components/list-select-filter';
|
||||||
import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown';
|
import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown';
|
||||||
@@ -36,7 +35,6 @@ export const ArtistListHeaderFilters = () => {
|
|||||||
defaultSortOrder={SortOrder.ASC}
|
defaultSortOrder={SortOrder.ASC}
|
||||||
listKey={ItemListKey.ARTIST}
|
listKey={ItemListKey.ARTIST}
|
||||||
/>
|
/>
|
||||||
<ListMusicFolderDropdown listKey={ItemListKey.ARTIST} />
|
|
||||||
{rolesQuery.data && rolesQuery.data.length > 0 && (
|
{rolesQuery.data && rolesQuery.data.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useMusicFolderIdFilter } from '/@/renderer/features/shared/hooks/use-music-folder-id-filter';
|
|
||||||
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
|
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
|
||||||
import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter';
|
import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter';
|
||||||
import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-order-filter';
|
import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-order-filter';
|
||||||
@@ -11,12 +10,9 @@ export const useAlbumArtistListFilters = () => {
|
|||||||
|
|
||||||
const { sortOrder } = useSortOrderFilter(null, ItemListKey.ALBUM_ARTIST);
|
const { sortOrder } = useSortOrderFilter(null, ItemListKey.ALBUM_ARTIST);
|
||||||
|
|
||||||
const { musicFolderId } = useMusicFolderIdFilter(null, ItemListKey.ALBUM_ARTIST);
|
|
||||||
|
|
||||||
const { searchTerm, setSearchTerm } = useSearchTermFilter('');
|
const { searchTerm, setSearchTerm } = useSearchTermFilter('');
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
[FILTER_KEYS.SHARED.MUSIC_FOLDER_ID]: musicFolderId ?? undefined,
|
|
||||||
[FILTER_KEYS.SHARED.SEARCH_TERM]: searchTerm ?? undefined,
|
[FILTER_KEYS.SHARED.SEARCH_TERM]: searchTerm ?? undefined,
|
||||||
[FILTER_KEYS.SHARED.SORT_BY]: sortBy ?? undefined,
|
[FILTER_KEYS.SHARED.SORT_BY]: sortBy ?? undefined,
|
||||||
[FILTER_KEYS.SHARED.SORT_ORDER]: sortOrder ?? undefined,
|
[FILTER_KEYS.SHARED.SORT_ORDER]: sortOrder ?? undefined,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useMusicFolderIdFilter } from '/@/renderer/features/shared/hooks/use-music-folder-id-filter';
|
|
||||||
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
|
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
|
||||||
import { useSelectFilter } from '/@/renderer/features/shared/hooks/use-select-filter';
|
import { useSelectFilter } from '/@/renderer/features/shared/hooks/use-select-filter';
|
||||||
import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter';
|
import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter';
|
||||||
@@ -12,15 +11,12 @@ export const useArtistListFilters = () => {
|
|||||||
|
|
||||||
const { sortOrder } = useSortOrderFilter(null, ItemListKey.ARTIST);
|
const { sortOrder } = useSortOrderFilter(null, ItemListKey.ARTIST);
|
||||||
|
|
||||||
const { musicFolderId } = useMusicFolderIdFilter(null, ItemListKey.ARTIST);
|
|
||||||
|
|
||||||
const { searchTerm, setSearchTerm } = useSearchTermFilter('');
|
const { searchTerm, setSearchTerm } = useSearchTermFilter('');
|
||||||
|
|
||||||
const { value: role } = useSelectFilter(FILTER_KEYS.ARTIST.ROLE, '', ItemListKey.ARTIST);
|
const { value: role } = useSelectFilter(FILTER_KEYS.ARTIST.ROLE, '', ItemListKey.ARTIST);
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
[FILTER_KEYS.ARTIST.ROLE]: role ?? undefined,
|
[FILTER_KEYS.ARTIST.ROLE]: role ?? undefined,
|
||||||
[FILTER_KEYS.SHARED.MUSIC_FOLDER_ID]: musicFolderId ?? undefined,
|
|
||||||
[FILTER_KEYS.SHARED.SEARCH_TERM]: searchTerm ?? undefined,
|
[FILTER_KEYS.SHARED.SEARCH_TERM]: searchTerm ?? undefined,
|
||||||
[FILTER_KEYS.SHARED.SORT_BY]: sortBy ?? undefined,
|
[FILTER_KEYS.SHARED.SORT_BY]: sortBy ?? undefined,
|
||||||
[FILTER_KEYS.SHARED.SORT_ORDER]: sortOrder ?? undefined,
|
[FILTER_KEYS.SHARED.SORT_ORDER]: sortOrder ?? undefined,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { GENRE_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
import { GENRE_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import { ListFilters } from '/@/renderer/features/shared/components/list-filters';
|
import { ListFilters } from '/@/renderer/features/shared/components/list-filters';
|
||||||
import { ListMusicFolderDropdown } from '/@/renderer/features/shared/components/list-music-folder-dropdown';
|
|
||||||
import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button';
|
import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button';
|
||||||
import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown';
|
import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown';
|
||||||
import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button';
|
import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button';
|
||||||
@@ -28,7 +27,6 @@ export const GenreListHeaderFilters = () => {
|
|||||||
defaultSortOrder={SortOrder.ASC}
|
defaultSortOrder={SortOrder.ASC}
|
||||||
listKey={ItemListKey.GENRE}
|
listKey={ItemListKey.GENRE}
|
||||||
/>
|
/>
|
||||||
<ListMusicFolderDropdown listKey={ItemListKey.GENRE} />
|
|
||||||
<ListFilters itemType={LibraryItem.GENRE} />
|
<ListFilters itemType={LibraryItem.GENRE} />
|
||||||
<ListRefreshButton listKey={ItemListKey.GENRE} />
|
<ListRefreshButton listKey={ItemListKey.GENRE} />
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { useMusicFolderIdFilter } from '/@/renderer/features/shared/hooks/use-music-folder-id-filter';
|
|
||||||
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
|
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
|
||||||
import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter';
|
import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter';
|
||||||
import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-order-filter';
|
import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-order-filter';
|
||||||
@@ -11,12 +10,9 @@ export const useGenreListFilters = () => {
|
|||||||
|
|
||||||
const { sortOrder } = useSortOrderFilter(null, ItemListKey.GENRE);
|
const { sortOrder } = useSortOrderFilter(null, ItemListKey.GENRE);
|
||||||
|
|
||||||
const { musicFolderId } = useMusicFolderIdFilter(null, ItemListKey.GENRE);
|
|
||||||
|
|
||||||
const { searchTerm, setSearchTerm } = useSearchTermFilter('');
|
const { searchTerm, setSearchTerm } = useSearchTermFilter('');
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
[FILTER_KEYS.SHARED.MUSIC_FOLDER_ID]: musicFolderId ?? undefined,
|
|
||||||
[FILTER_KEYS.SHARED.SEARCH_TERM]: searchTerm ?? undefined,
|
[FILTER_KEYS.SHARED.SEARCH_TERM]: searchTerm ?? undefined,
|
||||||
[FILTER_KEYS.SHARED.SORT_BY]: sortBy ?? undefined,
|
[FILTER_KEYS.SHARED.SORT_BY]: sortBy ?? undefined,
|
||||||
[FILTER_KEYS.SHARED.SORT_ORDER]: sortOrder ?? undefined,
|
[FILTER_KEYS.SHARED.SORT_ORDER]: sortOrder ?? undefined,
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ import { useNavigate } from 'react-router';
|
|||||||
|
|
||||||
import styles from './action-bar.module.css';
|
import styles from './action-bar.module.css';
|
||||||
|
|
||||||
import { AppMenu } from '/@/renderer/features/titlebar/components/app-menu';
|
|
||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
|
||||||
import { useCommandPalette } from '/@/renderer/store';
|
import { useCommandPalette } from '/@/renderer/store';
|
||||||
import { Button } from '/@/shared/components/button/button';
|
import { Button } from '/@/shared/components/button/button';
|
||||||
import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
|
|
||||||
import { Grid } from '/@/shared/components/grid/grid';
|
import { Grid } from '/@/shared/components/grid/grid';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
import { Icon } from '/@/shared/components/icon/icon';
|
import { Icon } from '/@/shared/components/icon/icon';
|
||||||
@@ -15,14 +12,18 @@ import { TextInput } from '/@/shared/components/text-input/text-input';
|
|||||||
|
|
||||||
export const ActionBar = () => {
|
export const ActionBar = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { ref, ...cq } = useContainerQuery({ md: 300 });
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { open } = useCommandPalette();
|
const { open } = useCommandPalette();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container} ref={ref}>
|
<div className={styles.container}>
|
||||||
<Grid display="flex" gutter="sm" px="1rem" w="100%">
|
<Grid
|
||||||
<Grid.Col span={6}>
|
display="flex"
|
||||||
|
gutter="sm"
|
||||||
|
style={{ padding: '0 var(--theme-spacing-md)' }}
|
||||||
|
w="100%"
|
||||||
|
>
|
||||||
|
<Grid.Col span={8}>
|
||||||
<TextInput
|
<TextInput
|
||||||
leftSection={<Icon icon="search" />}
|
leftSection={<Icon icon="search" />}
|
||||||
onClick={open}
|
onClick={open}
|
||||||
@@ -35,18 +36,8 @@ export const ActionBar = () => {
|
|||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={6}>
|
<Grid.Col span={4}>
|
||||||
<Group gap="sm" grow wrap="nowrap">
|
<Group gap="sm" grow wrap="nowrap">
|
||||||
<DropdownMenu position="bottom-start">
|
|
||||||
<DropdownMenu.Target>
|
|
||||||
<Button p="0.5rem">
|
|
||||||
<Icon icon="menu" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenu.Target>
|
|
||||||
<DropdownMenu.Dropdown>
|
|
||||||
<AppMenu />
|
|
||||||
</DropdownMenu.Dropdown>
|
|
||||||
</DropdownMenu>
|
|
||||||
<Button onClick={() => navigate(-1)} p="0.5rem">
|
<Button onClick={() => navigate(-1)} p="0.5rem">
|
||||||
<Icon icon="arrowLeftS" />
|
<Icon icon="arrowLeftS" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import { openModal } from '@mantine/modals';
|
||||||
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useNavigate } from 'react-router';
|
||||||
|
|
||||||
|
import JellyfinLogo from '/@/renderer/features/servers/assets/jellyfin.png';
|
||||||
|
import NavidromeLogo from '/@/renderer/features/servers/assets/navidrome.png';
|
||||||
|
import OpenSubsonicLogo from '/@/renderer/features/servers/assets/opensubsonic.png';
|
||||||
|
import { ServerList } from '/@/renderer/features/servers/components/server-list';
|
||||||
|
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||||
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
import { useAuthStoreActions, useCurrentServer, useServerList } from '/@/renderer/store';
|
||||||
|
import { hasFeature } from '/@/shared/api/utils';
|
||||||
|
import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
|
||||||
|
import { Icon } from '/@/shared/components/icon/icon';
|
||||||
|
import { ServerListItemWithCredential, ServerType } from '/@/shared/types/domain-types';
|
||||||
|
import { ServerFeature } from '/@/shared/types/features-types';
|
||||||
|
|
||||||
|
export const ServerSelectorItems = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const currentServer = useCurrentServer();
|
||||||
|
const serverList = useServerList();
|
||||||
|
const { setCurrentServer, setMusicFolderId } = useAuthStoreActions();
|
||||||
|
|
||||||
|
const { data: musicFolders } = useQuery(
|
||||||
|
currentServer
|
||||||
|
? sharedQueries.musicFolders({ query: null, serverId: currentServer.id })
|
||||||
|
: { enabled: false, queryKey: ['disabled'] },
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSetCurrentServer = (server: ServerListItemWithCredential) => {
|
||||||
|
navigate(AppRoute.HOME);
|
||||||
|
setCurrentServer(server);
|
||||||
|
setMusicFolderId(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const supportsMultiSelect = hasFeature(currentServer, ServerFeature.MUSIC_FOLDER_MULTISELECT);
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const handleToggleMusicFolder = (musicFolderId: string) => {
|
||||||
|
if (supportsMultiSelect) {
|
||||||
|
const currentIds = currentServer.musicFolderId || [];
|
||||||
|
const isSelected = currentIds.includes(musicFolderId);
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
// Remove from selection
|
||||||
|
const newIds = currentIds.filter((id) => id !== musicFolderId);
|
||||||
|
setMusicFolderId(newIds.length > 0 ? newIds : undefined);
|
||||||
|
} else {
|
||||||
|
// Add to selection
|
||||||
|
setMusicFolderId([...currentIds, musicFolderId]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const currentId = Array.isArray(currentServer.musicFolderId)
|
||||||
|
? currentServer.musicFolderId[0]
|
||||||
|
: currentServer.musicFolderId;
|
||||||
|
const isSelected = currentId === musicFolderId;
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
setMusicFolderId(undefined);
|
||||||
|
} else {
|
||||||
|
setMusicFolderId([musicFolderId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryClient.resetQueries();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearMusicFolders = () => {
|
||||||
|
setMusicFolderId(undefined);
|
||||||
|
queryClient.resetQueries();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!currentServer) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedMusicFolders =
|
||||||
|
musicFolders?.items.filter((folder) => currentServer.musicFolderId?.includes(folder.id)) ||
|
||||||
|
[];
|
||||||
|
|
||||||
|
const handleManageServersModal = () => {
|
||||||
|
openModal({
|
||||||
|
children: <ServerList />,
|
||||||
|
title: t('page.manageServers.title', { postProcess: 'titleCase' }),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DropdownMenu.Label>
|
||||||
|
{t('page.appMenu.selectServer', { postProcess: 'titleCase' })}
|
||||||
|
</DropdownMenu.Label>
|
||||||
|
{Object.values(serverList).map((server) => {
|
||||||
|
const isNavidromeExpired =
|
||||||
|
server.type === ServerType.NAVIDROME && !server.ndCredential;
|
||||||
|
const isJellyfinExpired = server.type === ServerType.JELLYFIN && !server.credential;
|
||||||
|
const isSessionExpired = isNavidromeExpired || isJellyfinExpired;
|
||||||
|
|
||||||
|
const logo =
|
||||||
|
server.type === ServerType.NAVIDROME
|
||||||
|
? NavidromeLogo
|
||||||
|
: server.type === ServerType.JELLYFIN
|
||||||
|
? JellyfinLogo
|
||||||
|
: OpenSubsonicLogo;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu.Item
|
||||||
|
isSelected={currentServer?.id === server.id}
|
||||||
|
key={`server-${server.id}`}
|
||||||
|
leftSection={<img src={logo} style={{ height: '1rem', width: '1rem' }} />}
|
||||||
|
onClick={() => {
|
||||||
|
if (!isSessionExpired) {
|
||||||
|
handleSetCurrentServer(server);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{server.name}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<DropdownMenu.Item
|
||||||
|
leftSection={<Icon icon="edit" />}
|
||||||
|
onClick={handleManageServersModal}
|
||||||
|
>
|
||||||
|
{t('page.appMenu.manageServers', { postProcess: 'sentenceCase' })}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{musicFolders && musicFolders.items.length > 0 && (
|
||||||
|
<>
|
||||||
|
<DropdownMenu.Divider />
|
||||||
|
<DropdownMenu.Label>
|
||||||
|
{t('page.appMenu.selectMusicFolder', { postProcess: 'sentenceCase' })}
|
||||||
|
</DropdownMenu.Label>
|
||||||
|
<DropdownMenu.Item
|
||||||
|
isSelected={selectedMusicFolders.length === 0}
|
||||||
|
leftSection={<Icon icon="minus" />}
|
||||||
|
onClick={handleClearMusicFolders}
|
||||||
|
>
|
||||||
|
{t('common.none', { postProcess: 'titleCase' })}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{musicFolders.items.map((folder) => {
|
||||||
|
const isSelected = supportsMultiSelect
|
||||||
|
? currentServer.musicFolderId?.includes(folder.id) || false
|
||||||
|
: (Array.isArray(currentServer.musicFolderId)
|
||||||
|
? currentServer.musicFolderId[0]
|
||||||
|
: currentServer.musicFolderId) === folder.id;
|
||||||
|
return (
|
||||||
|
<DropdownMenu.Item
|
||||||
|
isSelected={isSelected}
|
||||||
|
key={`musicFolder-${folder.id}`}
|
||||||
|
leftSection={<Icon icon={isSelected ? 'check' : 'folder'} />}
|
||||||
|
onClick={() => handleToggleMusicFolder(folder.id)}
|
||||||
|
>
|
||||||
|
{folder.name}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
.button-container {
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--theme-spacing-md);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container-no-bottom-padding {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
padding: var(--theme-spacing-sm);
|
||||||
|
background: var(--theme-colors-surface);
|
||||||
|
border-radius: var(--theme-radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: var(--theme-radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-stack {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popover-target {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-area {
|
||||||
|
max-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-logo {
|
||||||
|
width: var(--theme-font-size-md);
|
||||||
|
height: var(--theme-font-size-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-button {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import styles from './server-selector.module.css';
|
||||||
|
|
||||||
|
import JellyfinLogo from '/@/renderer/features/servers/assets/jellyfin.png';
|
||||||
|
import NavidromeLogo from '/@/renderer/features/servers/assets/navidrome.png';
|
||||||
|
import OpenSubsonicLogo from '/@/renderer/features/servers/assets/opensubsonic.png';
|
||||||
|
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||||
|
import { AppMenu } from '/@/renderer/features/titlebar/components/app-menu';
|
||||||
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
|
import { hasFeature } from '/@/shared/api/utils';
|
||||||
|
import { Box } from '/@/shared/components/box/box';
|
||||||
|
import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
|
||||||
|
import { Group } from '/@/shared/components/group/group';
|
||||||
|
import { Icon } from '/@/shared/components/icon/icon';
|
||||||
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
|
import { Text } from '/@/shared/components/text/text';
|
||||||
|
import { ServerType } from '/@/shared/types/domain-types';
|
||||||
|
import { ServerFeature } from '/@/shared/types/features-types';
|
||||||
|
|
||||||
|
interface ServerSelectorProps {
|
||||||
|
showImage?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServerSelector = ({ showImage = false }: ServerSelectorProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const currentServer = useCurrentServer();
|
||||||
|
|
||||||
|
const { data: musicFolders } = useQuery(
|
||||||
|
currentServer
|
||||||
|
? sharedQueries.musicFolders({ query: null, serverId: currentServer.id })
|
||||||
|
: { enabled: false, queryKey: ['disabled'] },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!currentServer) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const supportsMultiSelect = hasFeature(currentServer, ServerFeature.MUSIC_FOLDER_MULTISELECT);
|
||||||
|
|
||||||
|
const selectedMusicFolders =
|
||||||
|
musicFolders?.items.filter((folder) => currentServer.musicFolderId?.includes(folder.id)) ||
|
||||||
|
[];
|
||||||
|
|
||||||
|
const musicFolderDisplayText = (() => {
|
||||||
|
if (selectedMusicFolders.length === 0) {
|
||||||
|
return t('page.appMenu.noMusicFolder', { postProcess: 'sentenceCase' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportsMultiSelect && selectedMusicFolders.length > 1) {
|
||||||
|
return t('page.appMenu.multipleMusicFolders', {
|
||||||
|
count: selectedMusicFolders.length,
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedMusicFolders[0].name;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const logo =
|
||||||
|
currentServer.type === ServerType.NAVIDROME
|
||||||
|
? NavidromeLogo
|
||||||
|
: currentServer.type === ServerType.JELLYFIN
|
||||||
|
? JellyfinLogo
|
||||||
|
: OpenSubsonicLogo;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu position="right">
|
||||||
|
<DropdownMenu.Target>
|
||||||
|
<div className={styles.popoverTarget}>
|
||||||
|
<Box
|
||||||
|
className={`${styles.buttonContainer} ${
|
||||||
|
showImage ? styles.buttonContainerNoBottomPadding : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Group className={styles.buttonGroup} gap="sm">
|
||||||
|
<img className={styles.logo} src={logo} />
|
||||||
|
<Stack className={styles.buttonStack} gap={2}>
|
||||||
|
<Text fw={600} size="sm" truncate>
|
||||||
|
{currentServer.name}
|
||||||
|
</Text>
|
||||||
|
<Text isMuted size="xs" truncate>
|
||||||
|
{musicFolderDisplayText}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<Icon icon="ellipsisVertical" size="sm" />
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
</DropdownMenu.Target>
|
||||||
|
<DropdownMenu.Dropdown>
|
||||||
|
<AppMenu />
|
||||||
|
</DropdownMenu.Dropdown>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -13,6 +13,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.scroll-area {
|
.scroll-area {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
padding: 0 var(--theme-spacing-md) var(--theme-spacing-md) var(--theme-spacing-md);
|
padding: 0 var(--theme-spacing-md) var(--theme-spacing-md) var(--theme-spacing-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,10 +29,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.image-container {
|
.image-container {
|
||||||
position: absolute;
|
position: relative;
|
||||||
bottom: 0;
|
flex-shrink: 0;
|
||||||
width: var(--sidebar-image-height);
|
width: var(--sidebar-image-height);
|
||||||
height: var(--sidebar-image-height);
|
height: var(--sidebar-image-height);
|
||||||
|
padding: var(--theme-spacing-md);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
animation: fade-in 0.2s ease-in-out;
|
animation: fade-in 0.2s ease-in-out;
|
||||||
|
|
||||||
@@ -48,7 +51,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: var(--theme-image-fit);
|
object-fit: var(--theme-image-fit);
|
||||||
background: var(--theme-colors-foreground-muted);
|
background: var(--theme-colors-foreground-muted);
|
||||||
border-radius: 0;
|
border-radius: var(--theme-radius-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion-root {
|
.accordion-root {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import styles from './sidebar.module.css';
|
|||||||
|
|
||||||
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
|
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
|
||||||
import { ActionBar } from '/@/renderer/features/sidebar/components/action-bar';
|
import { ActionBar } from '/@/renderer/features/sidebar/components/action-bar';
|
||||||
|
import { ServerSelector } from '/@/renderer/features/sidebar/components/server-selector';
|
||||||
import { SidebarIcon } from '/@/renderer/features/sidebar/components/sidebar-icon';
|
import { SidebarIcon } from '/@/renderer/features/sidebar/components/sidebar-icon';
|
||||||
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
|
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
|
||||||
import {
|
import {
|
||||||
@@ -106,15 +107,6 @@ export const Sidebar = () => {
|
|||||||
return items;
|
return items;
|
||||||
}, [sidebarItems, translatedSidebarItemMap]);
|
}, [sidebarItems, translatedSidebarItemMap]);
|
||||||
|
|
||||||
const scrollAreaHeight = useMemo(() => {
|
|
||||||
if (showImage) {
|
|
||||||
// Subtract the height of the top bar and padding
|
|
||||||
return `calc(100% - 65px - var(--mantine-spacing-xs) - ${sidebar.leftWidth})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '100%';
|
|
||||||
}, [showImage, sidebar.leftWidth]);
|
|
||||||
|
|
||||||
const isCustomWindowBar =
|
const isCustomWindowBar =
|
||||||
windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS;
|
windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS;
|
||||||
|
|
||||||
@@ -125,16 +117,10 @@ export const Sidebar = () => {
|
|||||||
})}
|
})}
|
||||||
id="left-sidebar"
|
id="left-sidebar"
|
||||||
>
|
>
|
||||||
<Group grow id="global-search-container">
|
<Group grow id="global-search-container" style={{ flexShrink: 0 }}>
|
||||||
<ActionBar />
|
<ActionBar />
|
||||||
</Group>
|
</Group>
|
||||||
<ScrollArea
|
<ScrollArea allowDragScroll className={styles.scrollArea}>
|
||||||
allowDragScroll
|
|
||||||
className={styles.scrollArea}
|
|
||||||
style={{
|
|
||||||
height: scrollAreaHeight,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Accordion
|
<Accordion
|
||||||
classNames={{
|
classNames={{
|
||||||
content: styles.accordionContent,
|
content: styles.accordionContent,
|
||||||
@@ -177,6 +163,9 @@ export const Sidebar = () => {
|
|||||||
)}
|
)}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
<div style={{ flexShrink: 0, position: 'relative', zIndex: 1 }}>
|
||||||
|
<ServerSelector showImage={showImage} />
|
||||||
|
</div>
|
||||||
<AnimatePresence initial={false} mode="popLayout">
|
<AnimatePresence initial={false} mode="popLayout">
|
||||||
{showImage && (
|
{showImage && (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -221,8 +210,8 @@ export const Sidebar = () => {
|
|||||||
style={{
|
style={{
|
||||||
cursor: 'default',
|
cursor: 'default',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
right: 5,
|
right: '1rem',
|
||||||
top: 5,
|
top: '1rem',
|
||||||
}}
|
}}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
label: t('common.collapse', {
|
label: t('common.collapse', {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import { ListFilters } from '/@/renderer/features/shared/components/list-filters';
|
import { ListFilters } from '/@/renderer/features/shared/components/list-filters';
|
||||||
import { ListMusicFolderDropdown } from '/@/renderer/features/shared/components/list-music-folder-dropdown';
|
|
||||||
import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button';
|
import { ListRefreshButton } from '/@/renderer/features/shared/components/list-refresh-button';
|
||||||
import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown';
|
import { ListSortByDropdown } from '/@/renderer/features/shared/components/list-sort-by-dropdown';
|
||||||
import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button';
|
import { ListSortOrderToggleButton } from '/@/renderer/features/shared/components/list-sort-order-toggle-button';
|
||||||
@@ -25,7 +24,6 @@ export const SongListHeaderFilters = () => {
|
|||||||
defaultSortOrder={SortOrder.ASC}
|
defaultSortOrder={SortOrder.ASC}
|
||||||
listKey={ItemListKey.SONG}
|
listKey={ItemListKey.SONG}
|
||||||
/>
|
/>
|
||||||
<ListMusicFolderDropdown listKey={ItemListKey.SONG} />
|
|
||||||
<ListFilters itemType={LibraryItem.SONG} />
|
<ListFilters itemType={LibraryItem.SONG} />
|
||||||
<ListRefreshButton listKey={ItemListKey.SONG} />
|
<ListRefreshButton listKey={ItemListKey.SONG} />
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
useQueryState,
|
useQueryState,
|
||||||
} from 'nuqs';
|
} from 'nuqs';
|
||||||
|
|
||||||
import { useMusicFolderIdFilter } from '/@/renderer/features/shared/hooks/use-music-folder-id-filter';
|
|
||||||
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
|
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
|
||||||
import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter';
|
import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter';
|
||||||
import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-order-filter';
|
import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-order-filter';
|
||||||
@@ -20,8 +19,6 @@ export const useSongListFilters = () => {
|
|||||||
|
|
||||||
const { sortOrder } = useSortOrderFilter(null, ItemListKey.SONG);
|
const { sortOrder } = useSortOrderFilter(null, ItemListKey.SONG);
|
||||||
|
|
||||||
const { musicFolderId } = useMusicFolderIdFilter(null, ItemListKey.SONG);
|
|
||||||
|
|
||||||
const { searchTerm, setSearchTerm } = useSearchTermFilter('');
|
const { searchTerm, setSearchTerm } = useSearchTermFilter('');
|
||||||
|
|
||||||
const [albumIds, setAlbumIds] = useQueryState(
|
const [albumIds, setAlbumIds] = useQueryState(
|
||||||
@@ -51,7 +48,6 @@ export const useSongListFilters = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
[FILTER_KEYS.SHARED.MUSIC_FOLDER_ID]: musicFolderId ?? undefined,
|
|
||||||
[FILTER_KEYS.SHARED.SEARCH_TERM]: searchTerm ?? undefined,
|
[FILTER_KEYS.SHARED.SEARCH_TERM]: searchTerm ?? undefined,
|
||||||
[FILTER_KEYS.SHARED.SORT_BY]: sortBy ?? undefined,
|
[FILTER_KEYS.SHARED.SORT_BY]: sortBy ?? undefined,
|
||||||
[FILTER_KEYS.SHARED.SORT_ORDER]: sortOrder ?? undefined,
|
[FILTER_KEYS.SHARED.SORT_ORDER]: sortOrder ?? undefined,
|
||||||
|
|||||||
@@ -1,76 +1,77 @@
|
|||||||
import { closeAllModals, openModal } from '@mantine/modals';
|
|
||||||
import isElectron from 'is-electron';
|
import isElectron from 'is-electron';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router';
|
import { Link, useNavigate } from 'react-router';
|
||||||
import { Link } from 'react-router';
|
|
||||||
|
|
||||||
import packageJson from '../../../../../package.json';
|
import packageJson from '../../../../../package.json';
|
||||||
|
|
||||||
import { EditServerForm } from '/@/renderer/features/servers/components/edit-server-form';
|
import { ServerSelectorItems } from '/@/renderer/features/sidebar/components/server-selector-items';
|
||||||
import { ServerList } from '/@/renderer/features/servers/components/server-list';
|
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import {
|
import { useAppStore, useAppStoreActions, useSidebarStore } from '/@/renderer/store';
|
||||||
useAppStore,
|
|
||||||
useAppStoreActions,
|
|
||||||
useAuthStoreActions,
|
|
||||||
useCurrentServer,
|
|
||||||
useServerList,
|
|
||||||
useSidebarStore,
|
|
||||||
} from '/@/renderer/store';
|
|
||||||
import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
|
import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
|
||||||
import { Icon } from '/@/shared/components/icon/icon';
|
import { Icon } from '/@/shared/components/icon/icon';
|
||||||
import { toast } from '/@/shared/components/toast/toast';
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
import { ServerListItemWithCredential, ServerType } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
const browser = isElectron() ? window.api.browser : null;
|
const browser = isElectron() ? window.api.browser : null;
|
||||||
const localSettings = isElectron() ? window.api.localSettings : null;
|
|
||||||
|
interface BaseMenuItem {
|
||||||
|
id: string;
|
||||||
|
type: 'conditional-group' | 'conditional-item' | 'custom' | 'divider' | 'item';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConditionalGroupItem extends BaseMenuItem {
|
||||||
|
condition: boolean;
|
||||||
|
items: MenuItem[];
|
||||||
|
type: 'conditional-group';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConditionalItem extends BaseMenuItem {
|
||||||
|
condition: boolean;
|
||||||
|
item: Omit<MenuItem, 'id' | 'type'>;
|
||||||
|
type: 'conditional-item';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CustomItem extends BaseMenuItem {
|
||||||
|
component: ReactNode;
|
||||||
|
type: 'custom';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DividerItem extends BaseMenuItem {
|
||||||
|
type: 'divider';
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuItem = ConditionalGroupItem | ConditionalItem | CustomItem | DividerItem | RegularMenuItem;
|
||||||
|
|
||||||
|
interface RegularMenuItem extends BaseMenuItem {
|
||||||
|
component?: 'a' | typeof Link;
|
||||||
|
href?: string;
|
||||||
|
icon?: keyof typeof import('/@/shared/components/icon/icon').AppIcon;
|
||||||
|
iconColor?:
|
||||||
|
| 'contrast'
|
||||||
|
| 'default'
|
||||||
|
| 'error'
|
||||||
|
| 'info'
|
||||||
|
| 'inherit'
|
||||||
|
| 'muted'
|
||||||
|
| 'primary'
|
||||||
|
| 'success'
|
||||||
|
| 'warn';
|
||||||
|
label: string;
|
||||||
|
leftSection?: ReactNode;
|
||||||
|
onClick?: () => void;
|
||||||
|
rightSection?: ReactNode;
|
||||||
|
target?: string;
|
||||||
|
to?: string;
|
||||||
|
type: 'item';
|
||||||
|
}
|
||||||
|
|
||||||
export const AppMenu = () => {
|
export const AppMenu = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const currentServer = useCurrentServer();
|
|
||||||
const serverList = useServerList();
|
|
||||||
const { setCurrentServer } = useAuthStoreActions();
|
|
||||||
const { collapsed } = useSidebarStore();
|
const { collapsed } = useSidebarStore();
|
||||||
const { privateMode } = useAppStore();
|
const { privateMode } = useAppStore();
|
||||||
const { setPrivateMode, setSideBar } = useAppStoreActions();
|
const { setPrivateMode, setSideBar } = useAppStoreActions();
|
||||||
|
|
||||||
const handleSetCurrentServer = (server: ServerListItemWithCredential) => {
|
|
||||||
navigate(AppRoute.HOME);
|
|
||||||
setCurrentServer(server);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCredentialsModal = async (server: ServerListItemWithCredential) => {
|
|
||||||
let password: null | string = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (localSettings && server.savePassword) {
|
|
||||||
password = await localSettings.passwordGet(server.id);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
openModal({
|
|
||||||
children: server && (
|
|
||||||
<EditServerForm
|
|
||||||
isUpdate
|
|
||||||
onCancel={closeAllModals}
|
|
||||||
password={password}
|
|
||||||
server={server}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
size: 'sm',
|
|
||||||
title: `Update session for "${server.name}"`,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleManageServersModal = () => {
|
|
||||||
openModal({
|
|
||||||
children: <ServerList />,
|
|
||||||
title: t('page.manageServers.title', { postProcess: 'titleCase' }),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBrowserDevTools = () => {
|
const handleBrowserDevTools = () => {
|
||||||
browser?.devtools();
|
browser?.devtools();
|
||||||
};
|
};
|
||||||
@@ -103,124 +104,184 @@ export const AppMenu = () => {
|
|||||||
browser?.quit();
|
browser?.quit();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const menuConfig: MenuItem[] = [
|
||||||
<>
|
{
|
||||||
<DropdownMenu.Item
|
condition: privateMode,
|
||||||
leftSection={<Icon icon="arrowLeftS" />}
|
id: 'private-mode',
|
||||||
onClick={() => navigate(-1)}
|
item: {
|
||||||
>
|
icon: 'lock',
|
||||||
{t('page.appMenu.goBack', { postProcess: 'sentenceCase' })}
|
iconColor: 'error',
|
||||||
</DropdownMenu.Item>
|
label: t('page.appMenu.privateModeOff', { postProcess: 'sentenceCase' }),
|
||||||
<DropdownMenu.Item
|
onClick: handlePrivateModeOff,
|
||||||
leftSection={<Icon icon="arrowRightS" />}
|
type: 'item',
|
||||||
onClick={() => navigate(1)}
|
},
|
||||||
>
|
type: 'conditional-item',
|
||||||
{t('page.appMenu.goForward', { postProcess: 'sentenceCase' })}
|
},
|
||||||
</DropdownMenu.Item>
|
{
|
||||||
{collapsed ? (
|
condition: !privateMode,
|
||||||
<DropdownMenu.Item
|
id: 'private-mode',
|
||||||
leftSection={<Icon icon="panelRightOpen" />}
|
item: {
|
||||||
onClick={handleExpandSidebar}
|
icon: 'lockOpen',
|
||||||
>
|
label: t('page.appMenu.privateModeOn', { postProcess: 'sentenceCase' }),
|
||||||
{t('page.appMenu.expandSidebar', { postProcess: 'sentenceCase' })}
|
onClick: handlePrivateModeOn,
|
||||||
</DropdownMenu.Item>
|
type: 'item',
|
||||||
) : (
|
},
|
||||||
<DropdownMenu.Item
|
type: 'conditional-item',
|
||||||
leftSection={<Icon icon="panelRightClose" />}
|
},
|
||||||
onClick={handleCollapseSidebar}
|
{
|
||||||
>
|
id: 'divider-1',
|
||||||
{t('page.appMenu.collapseSidebar', { postProcess: 'sentenceCase' })}
|
type: 'divider',
|
||||||
</DropdownMenu.Item>
|
},
|
||||||
)}
|
{
|
||||||
<DropdownMenu.Divider />
|
condition: collapsed,
|
||||||
<DropdownMenu.Item
|
id: 'navigation-group',
|
||||||
component={Link}
|
items: [
|
||||||
leftSection={<Icon icon="settings" />}
|
{
|
||||||
to={AppRoute.SETTINGS}
|
icon: 'arrowLeftS',
|
||||||
>
|
id: 'go-back',
|
||||||
{t('page.appMenu.settings', { postProcess: 'sentenceCase' })}
|
label: t('page.appMenu.goBack', { postProcess: 'sentenceCase' }),
|
||||||
</DropdownMenu.Item>
|
onClick: () => navigate(-1),
|
||||||
<DropdownMenu.Item
|
type: 'item',
|
||||||
leftSection={<Icon icon="edit" />}
|
},
|
||||||
onClick={handleManageServersModal}
|
{
|
||||||
>
|
icon: 'arrowRightS',
|
||||||
{t('page.appMenu.manageServers', { postProcess: 'sentenceCase' })}
|
id: 'go-forward',
|
||||||
</DropdownMenu.Item>
|
label: t('page.appMenu.goForward', { postProcess: 'sentenceCase' }),
|
||||||
{privateMode ? (
|
onClick: () => navigate(1),
|
||||||
<DropdownMenu.Item
|
type: 'item',
|
||||||
leftSection={<Icon color="error" icon="lock" />}
|
},
|
||||||
onClick={handlePrivateModeOff}
|
],
|
||||||
>
|
type: 'conditional-group',
|
||||||
{t('page.appMenu.privateModeOff', { postProcess: 'sentenceCase' })}
|
},
|
||||||
</DropdownMenu.Item>
|
{
|
||||||
) : (
|
condition: collapsed,
|
||||||
<DropdownMenu.Item
|
id: 'sidebar-toggle',
|
||||||
leftSection={<Icon icon="lockOpen" />}
|
item: {
|
||||||
onClick={handlePrivateModeOn}
|
icon: 'panelRightOpen',
|
||||||
>
|
id: 'expand-sidebar',
|
||||||
{t('page.appMenu.privateModeOn', { postProcess: 'sentenceCase' })}
|
label: t('page.appMenu.expandSidebar', { postProcess: 'sentenceCase' }),
|
||||||
</DropdownMenu.Item>
|
onClick: handleExpandSidebar,
|
||||||
)}
|
type: 'item',
|
||||||
<DropdownMenu.Divider />
|
},
|
||||||
<DropdownMenu.Label>
|
type: 'conditional-item',
|
||||||
{t('page.appMenu.selectServer', { postProcess: 'sentenceCase' })}
|
},
|
||||||
</DropdownMenu.Label>
|
{
|
||||||
{Object.keys(serverList).map((serverId) => {
|
condition: !collapsed,
|
||||||
const server = serverList[serverId];
|
id: 'sidebar-toggle',
|
||||||
const isNavidromeExpired =
|
item: {
|
||||||
server.type === ServerType.NAVIDROME && !server.ndCredential;
|
icon: 'panelRightClose',
|
||||||
const isJellyfinExpired = server.type === ServerType.JELLYFIN && !server.credential;
|
id: 'collapse-sidebar',
|
||||||
const isSessionExpired = isNavidromeExpired || isJellyfinExpired;
|
label: t('page.appMenu.collapseSidebar', { postProcess: 'sentenceCase' }),
|
||||||
|
onClick: handleCollapseSidebar,
|
||||||
return (
|
type: 'item',
|
||||||
<DropdownMenu.Item
|
},
|
||||||
key={`server-${server.id}`}
|
type: 'conditional-item',
|
||||||
leftSection={
|
},
|
||||||
isSessionExpired ? (
|
{
|
||||||
<Icon fill="error" icon="lock" />
|
id: 'divider-2',
|
||||||
) : (
|
type: 'divider',
|
||||||
<Icon
|
},
|
||||||
color={server.id === currentServer?.id ? 'primary' : undefined}
|
{
|
||||||
icon="server"
|
component: Link,
|
||||||
/>
|
icon: 'settings',
|
||||||
)
|
id: 'settings',
|
||||||
}
|
label: t('page.appMenu.settings', { postProcess: 'sentenceCase' }),
|
||||||
onClick={() => {
|
to: AppRoute.SETTINGS,
|
||||||
if (!isSessionExpired) return handleSetCurrentServer(server);
|
type: 'item',
|
||||||
return handleCredentialsModal(server);
|
},
|
||||||
}}
|
{
|
||||||
>
|
id: 'divider-3',
|
||||||
{server.name}
|
type: 'divider',
|
||||||
</DropdownMenu.Item>
|
},
|
||||||
);
|
{
|
||||||
})}
|
component: <ServerSelectorItems />,
|
||||||
<DropdownMenu.Divider />
|
id: 'server-selector',
|
||||||
<DropdownMenu.Item
|
type: 'custom',
|
||||||
component="a"
|
},
|
||||||
href="https://github.com/jeffvli/feishin/releases"
|
{
|
||||||
leftSection={<Icon icon="brandGitHub" />}
|
id: 'divider-4',
|
||||||
rightSection={<Icon icon="externalLink" />}
|
type: 'divider',
|
||||||
target="_blank"
|
},
|
||||||
>
|
{
|
||||||
{t('page.appMenu.version', {
|
component: 'a',
|
||||||
|
href: 'https://github.com/jeffvli/feishin/releases',
|
||||||
|
icon: 'brandGitHub',
|
||||||
|
id: 'version',
|
||||||
|
label: t('page.appMenu.version', {
|
||||||
postProcess: 'sentenceCase',
|
postProcess: 'sentenceCase',
|
||||||
version: packageJson.version,
|
version: packageJson.version,
|
||||||
})}
|
}),
|
||||||
</DropdownMenu.Item>
|
rightSection: <Icon icon="externalLink" />,
|
||||||
{isElectron() && (
|
target: '_blank',
|
||||||
<>
|
type: 'item',
|
||||||
<DropdownMenu.Divider />
|
},
|
||||||
<DropdownMenu.Item
|
{
|
||||||
leftSection={<Icon icon="appWindow" />}
|
condition: isElectron(),
|
||||||
onClick={handleBrowserDevTools}
|
id: 'devtools',
|
||||||
>
|
item: {
|
||||||
{t('page.appMenu.openBrowserDevtools', { postProcess: 'sentenceCase' })}
|
icon: 'appWindow',
|
||||||
</DropdownMenu.Item>
|
id: 'open-devtools',
|
||||||
<DropdownMenu.Item leftSection={<Icon icon="x" />} onClick={handleQuit}>
|
label: t('page.appMenu.openBrowserDevtools', { postProcess: 'sentenceCase' }),
|
||||||
{t('page.appMenu.quit', { postProcess: 'sentenceCase' })}
|
onClick: handleBrowserDevTools,
|
||||||
</DropdownMenu.Item>
|
type: 'item',
|
||||||
</>
|
},
|
||||||
)}
|
type: 'conditional-item',
|
||||||
</>
|
},
|
||||||
|
{
|
||||||
|
condition: isElectron(),
|
||||||
|
id: 'quit',
|
||||||
|
item: {
|
||||||
|
icon: 'x',
|
||||||
|
id: 'quit-app',
|
||||||
|
label: t('page.appMenu.quit', { postProcess: 'sentenceCase' }),
|
||||||
|
onClick: handleQuit,
|
||||||
|
type: 'item',
|
||||||
|
},
|
||||||
|
type: 'conditional-item',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const renderMenuItem = (item: MenuItem): ReactNode => {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'conditional-group':
|
||||||
|
if (!item.condition) return null;
|
||||||
|
return (
|
||||||
|
<div key={item.id}>{item.items.map((subItem) => renderMenuItem(subItem))}</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
case 'conditional-item':
|
||||||
|
if (!item.condition) return null;
|
||||||
|
return renderMenuItem(item.item as MenuItem);
|
||||||
|
|
||||||
|
case 'custom':
|
||||||
|
return <div key={item.id}>{item.component}</div>;
|
||||||
|
|
||||||
|
case 'divider':
|
||||||
|
return <DropdownMenu.Divider key={item.id} />;
|
||||||
|
|
||||||
|
case 'item': {
|
||||||
|
const leftSection =
|
||||||
|
item.leftSection ||
|
||||||
|
(item.icon && <Icon color={item.iconColor} icon={item.icon} />);
|
||||||
|
|
||||||
|
const props: any = {
|
||||||
|
key: item.id,
|
||||||
|
leftSection,
|
||||||
|
...(item.rightSection && { rightSection: item.rightSection }),
|
||||||
|
...(item.onClick && { onClick: item.onClick }),
|
||||||
|
...(item.component && { component: item.component }),
|
||||||
|
...(item.to && { to: item.to }),
|
||||||
|
...(item.href && { href: item.href }),
|
||||||
|
...(item.target && { target: item.target }),
|
||||||
|
};
|
||||||
|
|
||||||
|
return <DropdownMenu.Item {...props}>{item.label}</DropdownMenu.Item>;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return <>{menuConfig.map((item) => renderMenuItem(item))}</>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface AuthSlice extends AuthState {
|
|||||||
deleteServer: (id: string) => void;
|
deleteServer: (id: string) => void;
|
||||||
getServer: (id: string) => null | ServerListItemWithCredential;
|
getServer: (id: string) => null | ServerListItemWithCredential;
|
||||||
setCurrentServer: (server: null | ServerListItemWithCredential) => void;
|
setCurrentServer: (server: null | ServerListItemWithCredential) => void;
|
||||||
|
setMusicFolderId: (musicFolderId: string[] | undefined) => void;
|
||||||
updateServer: (id: string, args: Partial<ServerListItemWithCredential>) => void;
|
updateServer: (id: string, args: Partial<ServerListItemWithCredential>) => void;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -64,15 +65,36 @@ export const useAuthStore = createWithEqualityFn<AuthSlice>()(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateServer: (id: string, args: Partial<ServerListItem>) => {
|
setMusicFolderId: (musicFolderId: string[] | undefined) => {
|
||||||
|
set((state) => {
|
||||||
|
if (state.currentServer) {
|
||||||
|
state.currentServer.musicFolderId = musicFolderId;
|
||||||
|
const serverId = state.currentServer.id;
|
||||||
|
if (state.serverList[serverId]) {
|
||||||
|
state.serverList[serverId].musicFolderId = musicFolderId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updateServer: (id: string, args: Partial<ServerListItemWithCredential>) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const updatedServer = {
|
const updatedServer = {
|
||||||
...state.serverList[id],
|
...state.serverList[id],
|
||||||
...args,
|
...args,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
state.currentServer?.id === id &&
|
||||||
|
!('musicFolderId' in args) &&
|
||||||
|
state.currentServer.musicFolderId !== undefined
|
||||||
|
) {
|
||||||
|
updatedServer.musicFolderId = state.currentServer.musicFolderId;
|
||||||
|
}
|
||||||
|
|
||||||
state.serverList[id] = updatedServer;
|
state.serverList[id] = updatedServer;
|
||||||
|
if (state.currentServer?.id === id) {
|
||||||
state.currentServer = updatedServer;
|
state.currentServer = updatedServer;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -97,6 +119,7 @@ export const useCurrentServer = () =>
|
|||||||
return {
|
return {
|
||||||
features: state.currentServer?.features,
|
features: state.currentServer?.features,
|
||||||
id: state.currentServer?.id,
|
id: state.currentServer?.id,
|
||||||
|
musicFolderId: state.currentServer?.musicFolderId,
|
||||||
name: state.currentServer?.name,
|
name: state.currentServer?.name,
|
||||||
preferInstantMix: state.currentServer?.preferInstantMix,
|
preferInstantMix: state.currentServer?.preferInstantMix,
|
||||||
savePassword: state.currentServer?.savePassword,
|
savePassword: state.currentServer?.savePassword,
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ const genreListSort = {
|
|||||||
|
|
||||||
const genreListParameters = paginationParameters.extend({
|
const genreListParameters = paginationParameters.extend({
|
||||||
_sort: z.nativeEnum(genreListSort).optional(),
|
_sort: z.nativeEnum(genreListSort).optional(),
|
||||||
|
library_id: z.array(z.string()).optional(),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -308,6 +309,7 @@ const albumArtistList = z.array(albumArtist);
|
|||||||
const albumArtistListParameters = paginationParameters.extend({
|
const albumArtistListParameters = paginationParameters.extend({
|
||||||
_sort: z.nativeEnum(NDAlbumArtistListSort).optional(),
|
_sort: z.nativeEnum(NDAlbumArtistListSort).optional(),
|
||||||
genre_id: z.string().optional(),
|
genre_id: z.string().optional(),
|
||||||
|
library_id: z.array(z.string()).optional(),
|
||||||
missing: z.boolean().optional(),
|
missing: z.boolean().optional(),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
role: z.string().optional(),
|
role: z.string().optional(),
|
||||||
@@ -374,6 +376,7 @@ const albumListParameters = paginationParameters.extend({
|
|||||||
genre_id: z.union([z.string(), z.string().array()]).optional(),
|
genre_id: z.union([z.string(), z.string().array()]).optional(),
|
||||||
has_rating: z.boolean().optional(),
|
has_rating: z.boolean().optional(),
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
|
library_id: z.array(z.string()).optional(),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
recently_added: z.boolean().optional(),
|
recently_added: z.boolean().optional(),
|
||||||
recently_played: z.boolean().optional(),
|
recently_played: z.boolean().optional(),
|
||||||
@@ -456,6 +459,7 @@ const songListParameters = paginationParameters.extend({
|
|||||||
artist_id: z.array(z.string()).optional(),
|
artist_id: z.array(z.string()).optional(),
|
||||||
artists_id: z.array(z.string()).optional(),
|
artists_id: z.array(z.string()).optional(),
|
||||||
genre_id: z.array(z.string()).optional(),
|
genre_id: z.array(z.string()).optional(),
|
||||||
|
library_id: z.array(z.string()).optional(),
|
||||||
path: z.string().optional(),
|
path: z.string().optional(),
|
||||||
starred: z.boolean().optional(),
|
starred: z.boolean().optional(),
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ export type QueueSong = Song & {
|
|||||||
export type ServerListItem = {
|
export type ServerListItem = {
|
||||||
features?: ServerFeatures;
|
features?: ServerFeatures;
|
||||||
id: string;
|
id: string;
|
||||||
|
musicFolderId?: string[];
|
||||||
name: string;
|
name: string;
|
||||||
preferInstantMix?: boolean;
|
preferInstantMix?: boolean;
|
||||||
savePassword?: boolean;
|
savePassword?: boolean;
|
||||||
@@ -280,7 +281,7 @@ export interface GenreListQuery extends BaseQuery<GenreListSort> {
|
|||||||
navidrome?: null;
|
navidrome?: null;
|
||||||
};
|
};
|
||||||
limit?: number;
|
limit?: number;
|
||||||
musicFolderId?: string;
|
musicFolderId?: string | string[];
|
||||||
searchTerm?: string;
|
searchTerm?: string;
|
||||||
startIndex: number;
|
startIndex: number;
|
||||||
}
|
}
|
||||||
@@ -436,7 +437,7 @@ export interface AlbumListQuery extends AlbumListNavidromeQuery, BaseQuery<Album
|
|||||||
limit?: number;
|
limit?: number;
|
||||||
maxYear?: number;
|
maxYear?: number;
|
||||||
minYear?: number;
|
minYear?: number;
|
||||||
musicFolderId?: string;
|
musicFolderId?: string | string[];
|
||||||
searchTerm?: string;
|
searchTerm?: string;
|
||||||
startIndex: number;
|
startIndex: number;
|
||||||
}
|
}
|
||||||
@@ -564,7 +565,7 @@ export interface SongListQuery extends BaseQuery<SongListSort> {
|
|||||||
limit?: number;
|
limit?: number;
|
||||||
maxYear?: number;
|
maxYear?: number;
|
||||||
minYear?: number;
|
minYear?: number;
|
||||||
musicFolderId?: string;
|
musicFolderId?: string | string[];
|
||||||
searchTerm?: string;
|
searchTerm?: string;
|
||||||
startIndex: number;
|
startIndex: number;
|
||||||
}
|
}
|
||||||
@@ -667,7 +668,7 @@ export type AlbumArtistListCountArgs = BaseEndpointArgs & {
|
|||||||
export interface AlbumArtistListQuery extends BaseQuery<AlbumArtistListSort> {
|
export interface AlbumArtistListQuery extends BaseQuery<AlbumArtistListSort> {
|
||||||
_custom?: Record<string, any>;
|
_custom?: Record<string, any>;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
musicFolderId?: string;
|
musicFolderId?: string | string[];
|
||||||
searchTerm?: string;
|
searchTerm?: string;
|
||||||
startIndex: number;
|
startIndex: number;
|
||||||
}
|
}
|
||||||
@@ -759,7 +760,7 @@ export type ArtistListCountArgs = BaseEndpointArgs & { query: ListCountQuery<Art
|
|||||||
export interface ArtistListQuery extends BaseQuery<ArtistListSort> {
|
export interface ArtistListQuery extends BaseQuery<ArtistListSort> {
|
||||||
_custom?: Record<string, any>;
|
_custom?: Record<string, any>;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
musicFolderId?: string;
|
musicFolderId?: string | string[];
|
||||||
role?: string;
|
role?: string;
|
||||||
searchTerm?: string;
|
searchTerm?: string;
|
||||||
startIndex: number;
|
startIndex: number;
|
||||||
@@ -1058,7 +1059,7 @@ export type ArtistInfoArgs = BaseEndpointArgs & { query: ArtistInfoQuery };
|
|||||||
export type ArtistInfoQuery = {
|
export type ArtistInfoQuery = {
|
||||||
artistId: string;
|
artistId: string;
|
||||||
limit: number;
|
limit: number;
|
||||||
musicFolderId?: string;
|
musicFolderId?: string | string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FullLyricsMetadata = Omit<InternetProviderLyricResponse, 'id' | 'lyrics' | 'source'> & {
|
export type FullLyricsMetadata = Omit<InternetProviderLyricResponse, 'id' | 'lyrics' | 'source'> & {
|
||||||
@@ -1104,7 +1105,7 @@ export type RandomSongListQuery = {
|
|||||||
limit?: number;
|
limit?: number;
|
||||||
maxYear?: number;
|
maxYear?: number;
|
||||||
minYear?: number;
|
minYear?: number;
|
||||||
musicFolderId?: string;
|
musicFolderId?: string | string[];
|
||||||
played: Played;
|
played: Played;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1127,14 +1128,14 @@ export type ScrobbleResponse = null;
|
|||||||
export type SearchAlbumArtistsQuery = {
|
export type SearchAlbumArtistsQuery = {
|
||||||
albumArtistLimit?: number;
|
albumArtistLimit?: number;
|
||||||
albumArtistStartIndex?: number;
|
albumArtistStartIndex?: number;
|
||||||
musicFolderId?: string;
|
musicFolderId?: string | string[];
|
||||||
query?: string;
|
query?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SearchAlbumsQuery = {
|
export type SearchAlbumsQuery = {
|
||||||
albumLimit?: number;
|
albumLimit?: number;
|
||||||
albumStartIndex?: number;
|
albumStartIndex?: number;
|
||||||
musicFolderId?: string;
|
musicFolderId?: string | string[];
|
||||||
query?: string;
|
query?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1147,7 +1148,7 @@ export type SearchQuery = {
|
|||||||
albumArtistStartIndex?: number;
|
albumArtistStartIndex?: number;
|
||||||
albumLimit?: number;
|
albumLimit?: number;
|
||||||
albumStartIndex?: number;
|
albumStartIndex?: number;
|
||||||
musicFolderId?: string;
|
musicFolderId?: string | string[];
|
||||||
query?: string;
|
query?: string;
|
||||||
songLimit?: number;
|
songLimit?: number;
|
||||||
songStartIndex?: number;
|
songStartIndex?: number;
|
||||||
@@ -1160,7 +1161,7 @@ export type SearchResponse = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type SearchSongsQuery = {
|
export type SearchSongsQuery = {
|
||||||
musicFolderId?: string;
|
musicFolderId?: string | string[];
|
||||||
query?: string;
|
query?: string;
|
||||||
songLimit?: number;
|
songLimit?: number;
|
||||||
songStartIndex?: number;
|
songStartIndex?: number;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export enum ServerFeature {
|
|||||||
BFR = 'bfr',
|
BFR = 'bfr',
|
||||||
LYRICS_MULTIPLE_STRUCTURED = 'lyricsMultipleStructured',
|
LYRICS_MULTIPLE_STRUCTURED = 'lyricsMultipleStructured',
|
||||||
LYRICS_SINGLE_STRUCTURED = 'lyricsSingleStructured',
|
LYRICS_SINGLE_STRUCTURED = 'lyricsSingleStructured',
|
||||||
|
MUSIC_FOLDER_MULTISELECT = 'musicFolderMultiselect',
|
||||||
PLAYLISTS_SMART = 'playlistsSmart',
|
PLAYLISTS_SMART = 'playlistsSmart',
|
||||||
PUBLIC_PLAYLIST = 'publicPlaylist',
|
PUBLIC_PLAYLIST = 'publicPlaylist',
|
||||||
SHARING_ALBUM_SONG = 'sharingAlbumSong',
|
SHARING_ALBUM_SONG = 'sharingAlbumSong',
|
||||||
|
|||||||
Reference in New Issue
Block a user