mirror of
https://github.com/antebudimir/feishin.git
synced 2025-12-31 10:03:33 +00:00
upgrade and refactor for react-query v5
This commit is contained in:
parent
dd70d30cd3
commit
8115963264
94 changed files with 1650 additions and 1750 deletions
|
|
@ -77,9 +77,9 @@
|
||||||
"@mantine/hooks": "^8.2.8",
|
"@mantine/hooks": "^8.2.8",
|
||||||
"@mantine/modals": "^8.2.8",
|
"@mantine/modals": "^8.2.8",
|
||||||
"@mantine/notifications": "^8.2.8",
|
"@mantine/notifications": "^8.2.8",
|
||||||
"@tanstack/react-query": "^4.32.1",
|
"@tanstack/react-query": "^5.89.0",
|
||||||
"@tanstack/react-query-devtools": "^4.32.1",
|
"@tanstack/react-query-devtools": "^5.89.0",
|
||||||
"@tanstack/react-query-persist-client": "^4.32.1",
|
"@tanstack/react-query-persist-client": "^5.89.0",
|
||||||
"@ts-rest/core": "^3.23.0",
|
"@ts-rest/core": "^3.23.0",
|
||||||
"@xhayper/discord-rpc": "^1.3.0",
|
"@xhayper/discord-rpc": "^1.3.0",
|
||||||
"audiomotion-analyzer": "^4.5.0",
|
"audiomotion-analyzer": "^4.5.0",
|
||||||
|
|
|
||||||
128
pnpm-lock.yaml
generated
128
pnpm-lock.yaml
generated
|
|
@ -60,14 +60,14 @@ importers:
|
||||||
specifier: ^8.2.8
|
specifier: ^8.2.8
|
||||||
version: 8.2.8(@mantine/core@8.2.8(@mantine/hooks@8.2.8(react@19.1.0))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@8.2.8(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 8.2.8(@mantine/core@8.2.8(@mantine/hooks@8.2.8(react@19.1.0))(@types/react@18.3.23)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@8.2.8(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
'@tanstack/react-query':
|
'@tanstack/react-query':
|
||||||
specifier: ^4.32.1
|
specifier: ^5.89.0
|
||||||
version: 4.36.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 5.89.0(react@19.1.0)
|
||||||
'@tanstack/react-query-devtools':
|
'@tanstack/react-query-devtools':
|
||||||
specifier: ^4.32.1
|
specifier: ^5.89.0
|
||||||
version: 4.36.1(@tanstack/react-query@4.36.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 5.89.0(@tanstack/react-query@5.89.0(react@19.1.0))(react@19.1.0)
|
||||||
'@tanstack/react-query-persist-client':
|
'@tanstack/react-query-persist-client':
|
||||||
specifier: ^4.32.1
|
specifier: ^5.89.0
|
||||||
version: 4.36.1(@tanstack/react-query@4.36.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))
|
version: 5.89.0(@tanstack/react-query@5.89.0(react@19.1.0))(react@19.1.0)
|
||||||
'@ts-rest/core':
|
'@ts-rest/core':
|
||||||
specifier: ^3.23.0
|
specifier: ^3.23.0
|
||||||
version: 3.52.1(@types/node@22.15.32)(zod@3.25.23)
|
version: 3.52.1(@types/node@22.15.32)(zod@3.25.23)
|
||||||
|
|
@ -1848,39 +1848,31 @@ packages:
|
||||||
resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
|
resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
'@tanstack/match-sorter-utils@8.19.4':
|
'@tanstack/query-core@5.89.0':
|
||||||
resolution: {integrity: sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==}
|
resolution: {integrity: sha512-joFV1MuPhSLsKfTzwjmPDrp8ENfZ9N23ymFu07nLfn3JCkSHy0CFgsyhHTJOmWaumC/WiNIKM0EJyduCF/Ih/Q==}
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
'@tanstack/query-core@4.36.1':
|
'@tanstack/query-devtools@5.87.3':
|
||||||
resolution: {integrity: sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==}
|
resolution: {integrity: sha512-LkzxzSr2HS1ALHTgDmJH5eGAVsSQiuwz//VhFW5OqNk0OQ+Fsqba0Tsf+NzWRtXYvpgUqwQr4b2zdFZwxHcGvg==}
|
||||||
|
|
||||||
'@tanstack/query-persist-client-core@4.36.1':
|
'@tanstack/query-persist-client-core@5.89.0':
|
||||||
resolution: {integrity: sha512-eocgCeI7D7TRv1IUUBMfVwOI0wdSmMkBIbkKhqEdTrnUHUQEeOaYac8oeZk2cumAWJdycu6P/wB+WqGynTnzXg==}
|
resolution: {integrity: sha512-kxZgQGgD7VqSFTDA/JyajywixHGGhzjMTtkENeVcS6BoTW6CGOkvoZH3L4/ROsaCZ4ibDfrmPzfUCpghID5ENg==}
|
||||||
|
|
||||||
'@tanstack/react-query-devtools@4.36.1':
|
'@tanstack/react-query-devtools@5.89.0':
|
||||||
resolution: {integrity: sha512-WYku83CKP3OevnYSG8Y/QO9g0rT75v1om5IvcWUwiUZJ4LanYGLVCZ8TdFG5jfsq4Ej/lu2wwDAULEUnRIMBSw==}
|
resolution: {integrity: sha512-Syc4UjZeIJCkXCRGyQcWwlnv89JNb98MMg/DAkFCV3rwOcknj98+nG3Nm6xLXM6ne9sK6RZeDJMPLKZUh6NUGA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tanstack/react-query': ^4.36.1
|
'@tanstack/react-query': ^5.89.0
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
react: ^18 || ^19
|
||||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
|
|
||||||
'@tanstack/react-query-persist-client@4.36.1':
|
'@tanstack/react-query-persist-client@5.89.0':
|
||||||
resolution: {integrity: sha512-32I5b9aAu4NCiXZ7Te/KEQLfHbYeTNriVPrKYcvEThnZ9tlW01vLcSoxpUIsMYRsembvJUUAkzYBAiZHLOd6pQ==}
|
resolution: {integrity: sha512-c1RaSID8DPzr7HnO2kfah5ON/lEtN/g0gN4nRsxWPi8gjWQRMfOh9av/KJWxxqWnBMPZ+tMV5Lb1OS38GAIRrw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tanstack/react-query': ^4.36.1
|
'@tanstack/react-query': ^5.89.0
|
||||||
|
react: ^18 || ^19
|
||||||
|
|
||||||
'@tanstack/react-query@4.36.1':
|
'@tanstack/react-query@5.89.0':
|
||||||
resolution: {integrity: sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==}
|
resolution: {integrity: sha512-SXbtWSTSRXyBOe80mszPxpEbaN4XPRUp/i0EfQK1uyj3KCk/c8FuPJNIRwzOVe/OU3rzxrYtiNabsAmk1l714A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
react: ^18 || ^19
|
||||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
react-native: '*'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
react-dom:
|
|
||||||
optional: true
|
|
||||||
react-native:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@tootallnate/once@2.0.0':
|
'@tootallnate/once@2.0.0':
|
||||||
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
|
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
|
||||||
|
|
@ -2525,12 +2517,8 @@ packages:
|
||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
copy-anything@3.0.5:
|
core-js-compat@3.45.1:
|
||||||
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
|
resolution: {integrity: sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==}
|
||||||
engines: {node: '>=12.13'}
|
|
||||||
|
|
||||||
core-js-compat@3.46.0:
|
|
||||||
resolution: {integrity: sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==}
|
|
||||||
|
|
||||||
core-util-is@1.0.2:
|
core-util-is@1.0.2:
|
||||||
resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
|
resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
|
||||||
|
|
@ -3655,10 +3643,6 @@ packages:
|
||||||
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
|
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
is-what@4.1.16:
|
|
||||||
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
|
|
||||||
engines: {node: '>=12.13'}
|
|
||||||
|
|
||||||
isarray@1.0.0:
|
isarray@1.0.0:
|
||||||
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||||
|
|
||||||
|
|
@ -4629,9 +4613,6 @@ packages:
|
||||||
resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==}
|
resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
remove-accents@0.5.0:
|
|
||||||
resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==}
|
|
||||||
|
|
||||||
remove-trailing-separator@1.1.0:
|
remove-trailing-separator@1.1.0:
|
||||||
resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==}
|
resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==}
|
||||||
|
|
||||||
|
|
@ -5156,10 +5137,6 @@ packages:
|
||||||
resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
|
resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
|
||||||
engines: {node: '>= 8.0'}
|
engines: {node: '>= 8.0'}
|
||||||
|
|
||||||
superjson@1.13.3:
|
|
||||||
resolution: {integrity: sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
|
|
||||||
supports-color@7.2.0:
|
supports-color@7.2.0:
|
||||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
@ -6474,7 +6451,7 @@ snapshots:
|
||||||
babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.27.1)
|
babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.27.1)
|
||||||
babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.27.1)
|
babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.27.1)
|
||||||
babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.27.1)
|
babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.27.1)
|
||||||
core-js-compat: 3.46.0
|
core-js-compat: 3.45.1
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
@ -7354,37 +7331,30 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
defer-to-connect: 2.0.1
|
defer-to-connect: 2.0.1
|
||||||
|
|
||||||
'@tanstack/match-sorter-utils@8.19.4':
|
'@tanstack/query-core@5.89.0': {}
|
||||||
dependencies:
|
|
||||||
remove-accents: 0.5.0
|
|
||||||
|
|
||||||
'@tanstack/query-core@4.36.1': {}
|
'@tanstack/query-devtools@5.87.3': {}
|
||||||
|
|
||||||
'@tanstack/query-persist-client-core@4.36.1':
|
'@tanstack/query-persist-client-core@5.89.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tanstack/query-core': 4.36.1
|
'@tanstack/query-core': 5.89.0
|
||||||
|
|
||||||
'@tanstack/react-query-devtools@4.36.1(@tanstack/react-query@4.36.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
'@tanstack/react-query-devtools@5.89.0(@tanstack/react-query@5.89.0(react@19.1.0))(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tanstack/match-sorter-utils': 8.19.4
|
'@tanstack/query-devtools': 5.87.3
|
||||||
'@tanstack/react-query': 4.36.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
'@tanstack/react-query': 5.89.0(react@19.1.0)
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
|
||||||
superjson: 1.13.3
|
|
||||||
use-sync-external-store: 1.5.0(react@19.1.0)
|
|
||||||
|
|
||||||
'@tanstack/react-query-persist-client@4.36.1(@tanstack/react-query@4.36.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))':
|
'@tanstack/react-query-persist-client@5.89.0(@tanstack/react-query@5.89.0(react@19.1.0))(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tanstack/query-persist-client-core': 4.36.1
|
'@tanstack/query-persist-client-core': 5.89.0
|
||||||
'@tanstack/react-query': 4.36.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
'@tanstack/react-query': 5.89.0(react@19.1.0)
|
||||||
|
react: 19.1.0
|
||||||
'@tanstack/react-query@4.36.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
|
||||||
dependencies:
|
'@tanstack/react-query@5.89.0(react@19.1.0)':
|
||||||
'@tanstack/query-core': 4.36.1
|
dependencies:
|
||||||
|
'@tanstack/query-core': 5.89.0
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
use-sync-external-store: 1.5.0(react@19.1.0)
|
|
||||||
optionalDependencies:
|
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
|
||||||
|
|
||||||
'@tootallnate/once@2.0.0': {}
|
'@tootallnate/once@2.0.0': {}
|
||||||
|
|
||||||
|
|
@ -7842,7 +7812,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.27.1
|
'@babel/core': 7.27.1
|
||||||
'@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.27.1)
|
'@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.27.1)
|
||||||
core-js-compat: 3.46.0
|
core-js-compat: 3.45.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
|
@ -8193,11 +8163,7 @@ snapshots:
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
copy-anything@3.0.5:
|
core-js-compat@3.45.1:
|
||||||
dependencies:
|
|
||||||
is-what: 4.1.16
|
|
||||||
|
|
||||||
core-js-compat@3.46.0:
|
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.27.0
|
browserslist: 4.27.0
|
||||||
|
|
||||||
|
|
@ -9597,8 +9563,6 @@ snapshots:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
get-intrinsic: 1.3.0
|
get-intrinsic: 1.3.0
|
||||||
|
|
||||||
is-what@4.1.16: {}
|
|
||||||
|
|
||||||
isarray@1.0.0: {}
|
isarray@1.0.0: {}
|
||||||
|
|
||||||
isarray@2.0.5: {}
|
isarray@2.0.5: {}
|
||||||
|
|
@ -10524,8 +10488,6 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
jsesc: 3.1.0
|
jsesc: 3.1.0
|
||||||
|
|
||||||
remove-accents@0.5.0: {}
|
|
||||||
|
|
||||||
remove-trailing-separator@1.1.0: {}
|
remove-trailing-separator@1.1.0: {}
|
||||||
|
|
||||||
replace-ext@2.0.0: {}
|
replace-ext@2.0.0: {}
|
||||||
|
|
@ -11121,10 +11083,6 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
superjson@1.13.3:
|
|
||||||
dependencies:
|
|
||||||
copy-anything: 3.0.5
|
|
||||||
|
|
||||||
supports-color@7.2.0:
|
supports-color@7.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag: 4.0.0
|
has-flag: 4.0.0
|
||||||
|
|
|
||||||
|
|
@ -163,20 +163,23 @@ export const useVirtualTable = <TFilter extends BaseQuery<any>>({
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const results = (await queryClient.fetchQuery(queryKey, async ({ signal }) => {
|
const results = (await queryClient.fetchQuery({
|
||||||
const res = await queryFn!({
|
queryFn: async ({ signal }) => {
|
||||||
apiClientProps: {
|
const res = await queryFn!({
|
||||||
server,
|
apiClientProps: {
|
||||||
signal,
|
server,
|
||||||
},
|
signal,
|
||||||
query: {
|
},
|
||||||
...properties.filter,
|
query: {
|
||||||
limit,
|
...properties.filter,
|
||||||
startIndex,
|
limit,
|
||||||
},
|
startIndex,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
},
|
||||||
|
queryKey,
|
||||||
})) as BasePaginatedResponse<any>;
|
})) as BasePaginatedResponse<any>;
|
||||||
|
|
||||||
if (isClientSideSort && results?.items) {
|
if (isClientSideSort && results?.items) {
|
||||||
|
|
|
||||||
54
src/renderer/features/albums/api/album-api.ts
Normal file
54
src/renderer/features/albums/api/album-api.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '/@/renderer/api';
|
||||||
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
|
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { getServerById } from '/@/renderer/store';
|
||||||
|
import { AlbumDetailQuery, AlbumListQuery } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
export const albumQueries = {
|
||||||
|
detail: (args: QueryHookArgs<AlbumDetailQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
return api.controller.getAlbumDetail({
|
||||||
|
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.albums.detail(args.serverId, args.query),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
list: (args: QueryHookArgs<AlbumListQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
return api.controller.getAlbumList({
|
||||||
|
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.albums.list(
|
||||||
|
args.serverId,
|
||||||
|
args.query,
|
||||||
|
args.query?.artistIds?.length === 1 ? args.query?.artistIds[0] : undefined,
|
||||||
|
),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
listCount: (args: QueryHookArgs<AlbumListQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
return api.controller.getAlbumListCount({
|
||||||
|
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.albums.count(
|
||||||
|
args.serverId,
|
||||||
|
args.query,
|
||||||
|
args.query?.artistIds?.length === 1 ? args.query?.artistIds[0] : undefined,
|
||||||
|
),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -2,6 +2,7 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/li
|
||||||
|
|
||||||
import { RowDoubleClickedEvent, RowHeightParams, RowNode } from '@ag-grid-community/core';
|
import { RowDoubleClickedEvent, RowHeightParams, RowNode } from '@ag-grid-community/core';
|
||||||
import { useSetState } from '@mantine/hooks';
|
import { useSetState } from '@mantine/hooks';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { MutableRefObject, useCallback, useMemo } from 'react';
|
import { MutableRefObject, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath, useParams } from 'react-router';
|
import { generatePath, useParams } from 'react-router';
|
||||||
|
|
@ -18,8 +19,7 @@ import {
|
||||||
} from '/@/renderer/components/virtual-table';
|
} from '/@/renderer/components/virtual-table';
|
||||||
import { FullWidthDiscCell } from '/@/renderer/components/virtual-table/cells/full-width-disc-cell';
|
import { FullWidthDiscCell } from '/@/renderer/components/virtual-table/cells/full-width-disc-cell';
|
||||||
import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles';
|
import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles';
|
||||||
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
|
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||||
import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query';
|
|
||||||
import {
|
import {
|
||||||
useHandleGeneralContextMenu,
|
useHandleGeneralContextMenu,
|
||||||
useHandleTableContextMenu,
|
useHandleTableContextMenu,
|
||||||
|
|
@ -71,7 +71,14 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { albumId } = useParams() as { albumId: string };
|
const { albumId } = useParams() as { albumId: string };
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const detailQuery = useAlbumDetail({ query: { id: albumId }, serverId: server?.id });
|
const detailQuery = useQuery(
|
||||||
|
albumQueries.detail({ query: { id: albumId }, serverId: server.id }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: detail } = useQuery(
|
||||||
|
albumQueries.detail({ query: { id: albumId }, serverId: server.id }),
|
||||||
|
);
|
||||||
|
|
||||||
const cq = useContainerQuery();
|
const cq = useContainerQuery();
|
||||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||||
const tableConfig = useTableSettings('albumDetail');
|
const tableConfig = useTableSettings('albumDetail');
|
||||||
|
|
@ -99,7 +106,7 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
|
||||||
);
|
);
|
||||||
|
|
||||||
const songsRowData = useMemo(() => {
|
const songsRowData = useMemo(() => {
|
||||||
if (!detailQuery.data?.songs) {
|
if (!detail?.songs) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,7 +116,7 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
|
||||||
const rowData: (QueueSong | { id: string; name: string })[] = [];
|
const rowData: (QueueSong | { id: string; name: string })[] = [];
|
||||||
const discTranslated = t('common.disc', { postProcess: 'upperCase' });
|
const discTranslated = t('common.disc', { postProcess: 'upperCase' });
|
||||||
|
|
||||||
for (const song of detailQuery.data.songs) {
|
for (const song of detail.songs) {
|
||||||
if (song.discNumber !== discNumber || song.discSubtitle !== discSubtitle) {
|
if (song.discNumber !== discNumber || song.discSubtitle !== discSubtitle) {
|
||||||
discNumber = song.discNumber;
|
discNumber = song.discNumber;
|
||||||
discSubtitle = song.discSubtitle;
|
discSubtitle = song.discSubtitle;
|
||||||
|
|
@ -128,7 +135,7 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
|
||||||
}
|
}
|
||||||
|
|
||||||
return rowData;
|
return rowData;
|
||||||
}, [detailQuery.data?.songs, t]);
|
}, [detail?.songs, t]);
|
||||||
|
|
||||||
const [pagination, setPagination] = useSetState({
|
const [pagination, setPagination] = useSetState({
|
||||||
artist: 0,
|
artist: 0,
|
||||||
|
|
@ -152,29 +159,46 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
|
||||||
[pagination, setPagination],
|
[pagination, setPagination],
|
||||||
);
|
);
|
||||||
|
|
||||||
const artistQuery = useAlbumList({
|
const artistQuery = useQuery(
|
||||||
options: {
|
albumQueries.list({
|
||||||
cacheTime: 1000 * 60,
|
query: {
|
||||||
enabled: detailQuery?.data?.albumArtists[0]?.id !== undefined,
|
_custom: {
|
||||||
keepPreviousData: true,
|
jellyfin: {
|
||||||
staleTime: 1000 * 60,
|
ExcludeItemIds: detail?.id,
|
||||||
},
|
},
|
||||||
query: {
|
|
||||||
_custom: {
|
|
||||||
jellyfin: {
|
|
||||||
ExcludeItemIds: detailQuery?.data?.id,
|
|
||||||
},
|
},
|
||||||
|
artistIds: detail?.albumArtists.length ? [detail?.albumArtists[0].id] : undefined,
|
||||||
|
limit: 15,
|
||||||
|
sortBy: AlbumListSort.YEAR,
|
||||||
|
sortOrder: SortOrder.DESC,
|
||||||
|
startIndex: 0,
|
||||||
},
|
},
|
||||||
artistIds: detailQuery?.data?.albumArtists.length
|
serverId: server.id,
|
||||||
? [detailQuery?.data?.albumArtists[0].id]
|
}),
|
||||||
: undefined,
|
);
|
||||||
limit: 15,
|
|
||||||
sortBy: AlbumListSort.YEAR,
|
// const artistQuery = useAlbumList({
|
||||||
sortOrder: SortOrder.DESC,
|
// options: {
|
||||||
startIndex: 0,
|
// enabled: detail?.albumArtists[0]?.id !== undefined,
|
||||||
},
|
// gcTime: 1000 * 60,
|
||||||
serverId: server?.id,
|
// placeholderData: true,
|
||||||
});
|
// },
|
||||||
|
// query: {
|
||||||
|
// _custom: {
|
||||||
|
// jellyfin: {
|
||||||
|
// ExcludeItemIds: detailQuery?.data?.id,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// artistIds: detailQuery?.data?.albumArtists.length
|
||||||
|
// ? [detailQuery?.data?.albumArtists[0].id]
|
||||||
|
// : undefined,
|
||||||
|
// limit: 15,
|
||||||
|
// sortBy: AlbumListSort.YEAR,
|
||||||
|
// sortOrder: SortOrder.DESC,
|
||||||
|
// startIndex: 0,
|
||||||
|
// },
|
||||||
|
// serverId: server?.id,
|
||||||
|
// });
|
||||||
|
|
||||||
const relatedAlbumGenresRequest: AlbumListQuery = {
|
const relatedAlbumGenresRequest: AlbumListQuery = {
|
||||||
genres: detailQuery.data?.genres.length ? [detailQuery.data.genres[0].id] : undefined,
|
genres: detailQuery.data?.genres.length ? [detailQuery.data.genres[0].id] : undefined,
|
||||||
|
|
@ -184,20 +208,21 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const relatedAlbumGenresQuery = useAlbumList({
|
const relatedAlbumGenresQuery = useQuery(
|
||||||
options: {
|
albumQueries.list({
|
||||||
cacheTime: 1000 * 60,
|
options: {
|
||||||
enabled: !!detailQuery?.data?.genres?.[0],
|
enabled: !!detailQuery?.data?.genres?.[0],
|
||||||
queryKey: queryKeys.albums.related(
|
gcTime: 1000 * 60,
|
||||||
server?.id || '',
|
queryKey: queryKeys.albums.related(
|
||||||
albumId,
|
server?.id || '',
|
||||||
relatedAlbumGenresRequest,
|
albumId,
|
||||||
),
|
relatedAlbumGenresRequest,
|
||||||
staleTime: 1000 * 60,
|
),
|
||||||
},
|
},
|
||||||
query: relatedAlbumGenresRequest,
|
query: relatedAlbumGenresRequest,
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const carousels = [
|
const carousels = [
|
||||||
{
|
{
|
||||||
|
|
@ -335,8 +360,8 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
|
||||||
: undefined,
|
: undefined,
|
||||||
}}
|
}}
|
||||||
loading={
|
loading={
|
||||||
createFavoriteMutation.isLoading ||
|
createFavoriteMutation.isPending ||
|
||||||
deleteFavoriteMutation.isLoading
|
deleteFavoriteMutation.isPending
|
||||||
}
|
}
|
||||||
onClick={handleFavorite}
|
onClick={handleFavorite}
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { forwardRef, Fragment, Ref, useCallback, useMemo } from 'react';
|
import { forwardRef, Fragment, Ref, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath, useParams } from 'react-router';
|
import { generatePath, useParams } from 'react-router';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
|
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||||
import { LibraryHeader, useSetRating } from '/@/renderer/features/shared';
|
import { LibraryHeader, useSetRating } from '/@/renderer/features/shared';
|
||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
import { useSongChange } from '/@/renderer/hooks/use-song-change';
|
import { useSongChange } from '/@/renderer/hooks/use-song-change';
|
||||||
|
|
@ -30,7 +31,9 @@ export const AlbumDetailHeader = forwardRef(
|
||||||
({ background }: AlbumDetailHeaderProps, ref: Ref<HTMLDivElement>) => {
|
({ background }: AlbumDetailHeaderProps, ref: Ref<HTMLDivElement>) => {
|
||||||
const { albumId } = useParams() as { albumId: string };
|
const { albumId } = useParams() as { albumId: string };
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const detailQuery = useAlbumDetail({ query: { id: albumId }, serverId: server?.id });
|
const detailQuery = useQuery(
|
||||||
|
albumQueries.detail({ query: { id: albumId }, serverId: server?.id }),
|
||||||
|
);
|
||||||
const cq = useContainerQuery();
|
const cq = useContainerQuery();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
@ -146,7 +149,7 @@ export const AlbumDetailHeader = forwardRef(
|
||||||
onChange={handleUpdateRating}
|
onChange={handleUpdateRating}
|
||||||
readOnly={
|
readOnly={
|
||||||
detailQuery?.isFetching ||
|
detailQuery?.isFetching ||
|
||||||
updateRatingMutation.isLoading
|
updateRatingMutation.isPending
|
||||||
}
|
}
|
||||||
value={detailQuery?.data?.userRating || 0}
|
value={detailQuery?.data?.userRating || 0}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -174,15 +174,17 @@ export const AlbumListGridView = ({ gridRef, itemCount }: any) => {
|
||||||
|
|
||||||
const queryKey = queryKeys.albums.list(server?.id || '', query, id);
|
const queryKey = queryKeys.albums.list(server?.id || '', query, id);
|
||||||
|
|
||||||
const albums = await queryClient.fetchQuery(queryKey, async ({ signal }) =>
|
const albums = await queryClient.fetchQuery({
|
||||||
controller.getAlbumList({
|
queryFn: async ({ signal }) =>
|
||||||
apiClientProps: {
|
controller.getAlbumList({
|
||||||
server,
|
apiClientProps: {
|
||||||
signal,
|
server,
|
||||||
},
|
signal,
|
||||||
query,
|
},
|
||||||
}),
|
query,
|
||||||
);
|
}),
|
||||||
|
queryKey,
|
||||||
|
});
|
||||||
|
|
||||||
return albums;
|
return albums;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
import { openModal } from '@mantine/modals';
|
import { openModal } from '@mantine/modals';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
import { MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
@ -14,7 +14,8 @@ import { useListContext } from '/@/renderer/context/list-context';
|
||||||
import { JellyfinAlbumFilters } from '/@/renderer/features/albums/components/jellyfin-album-filters';
|
import { JellyfinAlbumFilters } from '/@/renderer/features/albums/components/jellyfin-album-filters';
|
||||||
import { NavidromeAlbumFilters } from '/@/renderer/features/albums/components/navidrome-album-filters';
|
import { NavidromeAlbumFilters } from '/@/renderer/features/albums/components/navidrome-album-filters';
|
||||||
import { SubsonicAlbumFilters } from '/@/renderer/features/albums/components/subsonic-album-filters';
|
import { SubsonicAlbumFilters } from '/@/renderer/features/albums/components/subsonic-album-filters';
|
||||||
import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared';
|
import { OrderToggleButton } from '/@/renderer/features/shared';
|
||||||
|
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||||
import { FilterButton } from '/@/renderer/features/shared/components/filter-button';
|
import { FilterButton } from '/@/renderer/features/shared/components/filter-button';
|
||||||
import { FolderButton } from '/@/renderer/features/shared/components/folder-button';
|
import { FolderButton } from '/@/renderer/features/shared/components/folder-button';
|
||||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
|
|
@ -225,7 +226,9 @@ export const AlbumListHeaderFilters = ({
|
||||||
server,
|
server,
|
||||||
});
|
});
|
||||||
|
|
||||||
const musicFoldersQuery = useMusicFolders({ query: null, serverId: server?.id });
|
const musicFoldersQuery = useQuery(
|
||||||
|
sharedQueries.musicFolders({ query: null, serverId: server?.id }),
|
||||||
|
);
|
||||||
|
|
||||||
const sortByLabel =
|
const sortByLabel =
|
||||||
(server?.type &&
|
(server?.type &&
|
||||||
|
|
@ -288,7 +291,7 @@ export const AlbumListHeaderFilters = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefresh = useCallback(() => {
|
const handleRefresh = useCallback(() => {
|
||||||
queryClient.invalidateQueries(queryKeys.albums.list(server?.id || ''));
|
queryClient.invalidateQueries({ queryKey: queryKeys.albums.list(server?.id || '') });
|
||||||
onFilterChange(filter);
|
onFilterChange(filter);
|
||||||
}, [filter, onFilterChange, queryClient, server?.id]);
|
}, [filter, onFilterChange, queryClient, server?.id]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||||
import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
|
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { useGenreList } from '/@/renderer/features/genres';
|
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||||
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
|
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||||
import { AlbumListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
|
import { AlbumListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
|
||||||
import { Divider } from '/@/shared/components/divider/divider';
|
import { Divider } from '/@/shared/components/divider/divider';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
|
|
@ -27,7 +28,7 @@ interface JellyfinAlbumFiltersProps {
|
||||||
disableArtistFilter?: boolean;
|
disableArtistFilter?: boolean;
|
||||||
onFilterChange: (filters: AlbumListFilter) => void;
|
onFilterChange: (filters: AlbumListFilter) => void;
|
||||||
pageKey: string;
|
pageKey: string;
|
||||||
serverId?: string;
|
serverId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const JellyfinAlbumFilters = ({
|
export const JellyfinAlbumFilters = ({
|
||||||
|
|
@ -42,19 +43,21 @@ export const JellyfinAlbumFilters = ({
|
||||||
const { setFilter } = useListStoreActions();
|
const { setFilter } = useListStoreActions();
|
||||||
|
|
||||||
// TODO - eventually replace with /items/filters endpoint to fetch genres and tags specific to the selected library
|
// TODO - eventually replace with /items/filters endpoint to fetch genres and tags specific to the selected library
|
||||||
const genreListQuery = useGenreList({
|
const genreListQuery = useQuery(
|
||||||
options: {
|
genresQueries.list({
|
||||||
cacheTime: 1000 * 60 * 2,
|
options: {
|
||||||
staleTime: 1000 * 60 * 1,
|
gcTime: 1000 * 60 * 2,
|
||||||
},
|
staleTime: 1000 * 60 * 1,
|
||||||
query: {
|
},
|
||||||
musicFolderId: filter?.musicFolderId,
|
query: {
|
||||||
sortBy: GenreListSort.NAME,
|
musicFolderId: filter?.musicFolderId,
|
||||||
sortOrder: SortOrder.ASC,
|
sortBy: GenreListSort.NAME,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.ASC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId,
|
},
|
||||||
});
|
serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const genreList = useMemo(() => {
|
const genreList = useMemo(() => {
|
||||||
if (!genreListQuery?.data) return [];
|
if (!genreListQuery?.data) return [];
|
||||||
|
|
@ -64,17 +67,19 @@ export const JellyfinAlbumFilters = ({
|
||||||
}));
|
}));
|
||||||
}, [genreListQuery.data]);
|
}, [genreListQuery.data]);
|
||||||
|
|
||||||
const tagsQuery = useTagList({
|
const tagsQuery = useQuery(
|
||||||
options: {
|
sharedQueries.tags({
|
||||||
cacheTime: 1000 * 60 * 2,
|
options: {
|
||||||
staleTime: 1000 * 60 * 1,
|
gcTime: 1000 * 60 * 2,
|
||||||
},
|
staleTime: 1000 * 60 * 1,
|
||||||
query: {
|
},
|
||||||
folder: filter?.musicFolderId,
|
query: {
|
||||||
type: LibraryItem.ALBUM,
|
folder: filter?.musicFolderId,
|
||||||
},
|
type: LibraryItem.ALBUM,
|
||||||
serverId,
|
},
|
||||||
});
|
serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const selectedTags = useMemo(() => {
|
const selectedTags = useMemo(() => {
|
||||||
return filter?._custom?.jellyfin?.Tags?.split('|');
|
return filter?._custom?.jellyfin?.Tags?.split('|');
|
||||||
|
|
@ -171,18 +176,20 @@ export const JellyfinAlbumFilters = ({
|
||||||
onFilterChange(updatedFilters);
|
onFilterChange(updatedFilters);
|
||||||
}, 250);
|
}, 250);
|
||||||
|
|
||||||
const albumArtistListQuery = useAlbumArtistList({
|
const albumArtistListQuery = useQuery(
|
||||||
options: {
|
artistsQueries.albumArtistList({
|
||||||
cacheTime: 1000 * 60 * 2,
|
options: {
|
||||||
staleTime: 1000 * 60 * 1,
|
gcTime: 1000 * 60 * 2,
|
||||||
},
|
staleTime: 1000 * 60 * 1,
|
||||||
query: {
|
},
|
||||||
sortBy: AlbumArtistListSort.NAME,
|
query: {
|
||||||
sortOrder: SortOrder.ASC,
|
sortBy: AlbumArtistListSort.NAME,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.ASC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId,
|
},
|
||||||
});
|
serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const selectableAlbumArtists = useMemo(() => {
|
const selectableAlbumArtists = useMemo(() => {
|
||||||
if (!albumArtistListQuery?.data?.items) return [];
|
if (!albumArtistListQuery?.data?.items) return [];
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { ChangeEvent, useMemo } from 'react';
|
import { ChangeEvent, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
@ -6,9 +7,9 @@ import {
|
||||||
MultiSelectWithInvalidData,
|
MultiSelectWithInvalidData,
|
||||||
SelectWithInvalidData,
|
SelectWithInvalidData,
|
||||||
} from '/@/renderer/components/select-with-invalid-data';
|
} from '/@/renderer/components/select-with-invalid-data';
|
||||||
import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
|
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { useGenreList } from '/@/renderer/features/genres';
|
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||||
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
|
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||||
import {
|
import {
|
||||||
AlbumListFilter,
|
AlbumListFilter,
|
||||||
getServerById,
|
getServerById,
|
||||||
|
|
@ -39,7 +40,7 @@ interface NavidromeAlbumFiltersProps {
|
||||||
disableArtistFilter?: boolean;
|
disableArtistFilter?: boolean;
|
||||||
onFilterChange: (filters: AlbumListFilter) => void;
|
onFilterChange: (filters: AlbumListFilter) => void;
|
||||||
pageKey: string;
|
pageKey: string;
|
||||||
serverId?: string;
|
serverId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NavidromeAlbumFilters = ({
|
export const NavidromeAlbumFilters = ({
|
||||||
|
|
@ -54,18 +55,20 @@ export const NavidromeAlbumFilters = ({
|
||||||
const { setFilter } = useListStoreActions();
|
const { setFilter } = useListStoreActions();
|
||||||
const server = getServerById(serverId);
|
const server = getServerById(serverId);
|
||||||
|
|
||||||
const genreListQuery = useGenreList({
|
const genreListQuery = useQuery(
|
||||||
options: {
|
genresQueries.list({
|
||||||
cacheTime: 1000 * 60 * 2,
|
options: {
|
||||||
staleTime: 1000 * 60 * 1,
|
gcTime: 1000 * 60 * 2,
|
||||||
},
|
staleTime: 1000 * 60 * 1,
|
||||||
query: {
|
},
|
||||||
sortBy: GenreListSort.NAME,
|
query: {
|
||||||
sortOrder: SortOrder.ASC,
|
sortBy: GenreListSort.NAME,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.ASC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId,
|
},
|
||||||
});
|
serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const genreList = useMemo(() => {
|
const genreList = useMemo(() => {
|
||||||
if (!genreListQuery?.data) return [];
|
if (!genreListQuery?.data) return [];
|
||||||
|
|
@ -90,16 +93,18 @@ export const NavidromeAlbumFilters = ({
|
||||||
onFilterChange(updatedFilters);
|
onFilterChange(updatedFilters);
|
||||||
}, 250);
|
}, 250);
|
||||||
|
|
||||||
const tagsQuery = useTagList({
|
const tagsQuery = useQuery(
|
||||||
options: {
|
sharedQueries.tags({
|
||||||
cacheTime: 1000 * 60 * 2,
|
options: {
|
||||||
staleTime: 1000 * 60 * 1,
|
gcTime: 1000 * 60 * 2,
|
||||||
},
|
staleTime: 1000 * 60 * 1,
|
||||||
query: {
|
},
|
||||||
type: LibraryItem.ALBUM,
|
query: {
|
||||||
},
|
type: LibraryItem.ALBUM,
|
||||||
serverId,
|
},
|
||||||
});
|
serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const yesNoUndefinedFilters = [
|
const yesNoUndefinedFilters = [
|
||||||
{
|
{
|
||||||
|
|
@ -199,19 +204,21 @@ export const NavidromeAlbumFilters = ({
|
||||||
onFilterChange(updatedFilters);
|
onFilterChange(updatedFilters);
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
const albumArtistListQuery = useAlbumArtistList({
|
const albumArtistListQuery = useQuery(
|
||||||
options: {
|
artistsQueries.albumArtistList({
|
||||||
cacheTime: 1000 * 60 * 2,
|
options: {
|
||||||
staleTime: 1000 * 60 * 1,
|
gcTime: 1000 * 60 * 2,
|
||||||
},
|
staleTime: 1000 * 60 * 1,
|
||||||
query: {
|
},
|
||||||
// searchTerm: debouncedSearchTerm,
|
query: {
|
||||||
sortBy: AlbumArtistListSort.NAME,
|
// searchTerm: debouncedSearchTerm,
|
||||||
sortOrder: SortOrder.ASC,
|
sortBy: AlbumArtistListSort.NAME,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.ASC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId,
|
},
|
||||||
});
|
serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const selectableAlbumArtists = useMemo(() => {
|
const selectableAlbumArtists = useMemo(() => {
|
||||||
if (!albumArtistListQuery?.data?.items) return [];
|
if (!albumArtistListQuery?.data?.items) return [];
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { ChangeEvent, useMemo, useState } from 'react';
|
import { ChangeEvent, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||||
import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
|
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { useGenreList } from '/@/renderer/features/genres';
|
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||||
import { AlbumListFilter, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
|
import { AlbumListFilter, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
|
||||||
import { Divider } from '/@/shared/components/divider/divider';
|
import { Divider } from '/@/shared/components/divider/divider';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
|
|
@ -26,7 +27,7 @@ interface SubsonicAlbumFiltersProps {
|
||||||
disableArtistFilter?: boolean;
|
disableArtistFilter?: boolean;
|
||||||
onFilterChange: (filters: AlbumListFilter) => void;
|
onFilterChange: (filters: AlbumListFilter) => void;
|
||||||
pageKey: string;
|
pageKey: string;
|
||||||
serverId?: string;
|
serverId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SubsonicAlbumFilters = ({
|
export const SubsonicAlbumFilters = ({
|
||||||
|
|
@ -40,18 +41,20 @@ export const SubsonicAlbumFilters = ({
|
||||||
const { setFilter } = useListStoreActions();
|
const { setFilter } = useListStoreActions();
|
||||||
const [albumArtistSearchTerm, setAlbumArtistSearchTerm] = useState<string>('');
|
const [albumArtistSearchTerm, setAlbumArtistSearchTerm] = useState<string>('');
|
||||||
|
|
||||||
const albumArtistListQuery = useAlbumArtistList({
|
const albumArtistListQuery = useQuery(
|
||||||
options: {
|
artistsQueries.albumArtistList({
|
||||||
cacheTime: 1000 * 60 * 2,
|
options: {
|
||||||
staleTime: 1000 * 60 * 1,
|
gcTime: 1000 * 60 * 2,
|
||||||
},
|
staleTime: 1000 * 60 * 1,
|
||||||
query: {
|
},
|
||||||
sortBy: AlbumArtistListSort.NAME,
|
query: {
|
||||||
sortOrder: SortOrder.ASC,
|
sortBy: AlbumArtistListSort.NAME,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.ASC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId,
|
},
|
||||||
});
|
serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const selectableAlbumArtists = useMemo(() => {
|
const selectableAlbumArtists = useMemo(() => {
|
||||||
if (!albumArtistListQuery?.data?.items) return [];
|
if (!albumArtistListQuery?.data?.items) return [];
|
||||||
|
|
@ -73,18 +76,20 @@ export const SubsonicAlbumFilters = ({
|
||||||
onFilterChange(updatedFilters);
|
onFilterChange(updatedFilters);
|
||||||
};
|
};
|
||||||
|
|
||||||
const genreListQuery = useGenreList({
|
const genreListQuery = useQuery(
|
||||||
options: {
|
genresQueries.list({
|
||||||
cacheTime: 1000 * 60 * 2,
|
options: {
|
||||||
staleTime: 1000 * 60 * 1,
|
gcTime: 1000 * 60 * 2,
|
||||||
},
|
staleTime: 1000 * 60 * 1,
|
||||||
query: {
|
},
|
||||||
sortBy: GenreListSort.NAME,
|
query: {
|
||||||
sortOrder: SortOrder.ASC,
|
sortBy: GenreListSort.NAME,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.ASC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId,
|
},
|
||||||
});
|
serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const genreList = useMemo(() => {
|
const genreList = useMemo(() => {
|
||||||
if (!genreListQuery?.data) return [];
|
if (!genreListQuery?.data) return [];
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import type { AlbumDetailQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { controller } from '/@/renderer/api/controller';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const useAlbumDetail = (args: QueryHookArgs<AlbumDetailQuery>) => {
|
|
||||||
const { options, query, serverId } = args;
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return controller.getAlbumDetail({ apiClientProps: { server, signal }, query });
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.albums.detail(server?.id || '', query),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import type { AlbumListQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const useAlbumListCount = (args: QueryHookArgs<AlbumListQuery>) => {
|
|
||||||
const { options, query, serverId } = args;
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!serverId,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getAlbumListCount({
|
|
||||||
apiClientProps: {
|
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
},
|
|
||||||
query,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.albums.count(
|
|
||||||
serverId || '',
|
|
||||||
Object.keys(query).length === 0 ? undefined : query,
|
|
||||||
),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import type { AlbumListQuery, AlbumListResponse } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { controller } from '/@/renderer/api/controller';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const useAlbumList = (args: QueryHookArgs<AlbumListQuery>) => {
|
|
||||||
const { options, query, serverId } = args;
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!serverId,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return controller.getAlbumList({
|
|
||||||
apiClientProps: {
|
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
},
|
|
||||||
query,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.albums.list(
|
|
||||||
serverId || '',
|
|
||||||
query,
|
|
||||||
query?.artistIds?.length === 1 ? query?.artistIds[0] : undefined,
|
|
||||||
),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useAlbumListInfinite = (args: QueryHookArgs<AlbumListQuery>) => {
|
|
||||||
const { options, query, serverId } = args;
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useInfiniteQuery({
|
|
||||||
enabled: !!serverId,
|
|
||||||
getNextPageParam: (lastPage: AlbumListResponse | undefined, pages) => {
|
|
||||||
if (!lastPage?.items) return undefined;
|
|
||||||
if (lastPage?.items?.length >= (query?.limit || 50)) {
|
|
||||||
return pages?.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
queryFn: ({ pageParam = 0, signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getAlbumList({
|
|
||||||
apiClientProps: {
|
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
...query,
|
|
||||||
limit: query.limit || 50,
|
|
||||||
startIndex: pageParam * (query.limit || 50),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.albums.list(server?.id || '', query),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area';
|
import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area';
|
||||||
|
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||||
import { AlbumDetailContent } from '/@/renderer/features/albums/components/album-detail-content';
|
import { AlbumDetailContent } from '/@/renderer/features/albums/components/album-detail-content';
|
||||||
import { AlbumDetailHeader } from '/@/renderer/features/albums/components/album-detail-header';
|
import { AlbumDetailHeader } from '/@/renderer/features/albums/components/album-detail-header';
|
||||||
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
|
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
|
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||||
import { useFastAverageColor } from '/@/renderer/hooks';
|
import { useFastAverageColor } from '/@/renderer/hooks';
|
||||||
|
|
@ -22,7 +23,9 @@ const AlbumDetailRoute = () => {
|
||||||
|
|
||||||
const { albumId } = useParams() as { albumId: string };
|
const { albumId } = useParams() as { albumId: string };
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const detailQuery = useAlbumDetail({ query: { id: albumId }, serverId: server?.id });
|
const detailQuery = useQuery(
|
||||||
|
albumQueries.detail({ query: { id: albumId }, serverId: server?.id }),
|
||||||
|
);
|
||||||
const { background: backgroundColor, colorId } = useFastAverageColor({
|
const { background: backgroundColor, colorId } = useFastAverageColor({
|
||||||
id: albumId,
|
id: albumId,
|
||||||
src: detailQuery.data?.imageUrl,
|
src: detailQuery.data?.imageUrl,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { useCallback, useMemo, useRef } from 'react';
|
import { useCallback, useMemo, useRef } from 'react';
|
||||||
import { useParams, useSearchParams } from 'react-router-dom';
|
import { useParams, useSearchParams } from 'react-router-dom';
|
||||||
|
|
@ -8,10 +9,10 @@ import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { ListContext } from '/@/renderer/context/list-context';
|
import { ListContext } from '/@/renderer/context/list-context';
|
||||||
|
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||||
import { AlbumListContent } from '/@/renderer/features/albums/components/album-list-content';
|
import { AlbumListContent } from '/@/renderer/features/albums/components/album-list-content';
|
||||||
import { AlbumListHeader } from '/@/renderer/features/albums/components/album-list-header';
|
import { AlbumListHeader } from '/@/renderer/features/albums/components/album-list-header';
|
||||||
import { useAlbumListCount } from '/@/renderer/features/albums/queries/album-list-count-query';
|
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||||
import { useGenreList } from '/@/renderer/features/genres';
|
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
import { queryClient } from '/@/renderer/lib/react-query';
|
import { queryClient } from '/@/renderer/lib/react-query';
|
||||||
|
|
@ -53,18 +54,20 @@ const AlbumListRoute = () => {
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
const genreList = useGenreList({
|
const genreList = useQuery(
|
||||||
options: {
|
genresQueries.list({
|
||||||
cacheTime: 1000 * 60 * 60,
|
options: {
|
||||||
enabled: !!genreId,
|
enabled: !!genreId,
|
||||||
},
|
gcTime: 1000 * 60 * 60,
|
||||||
query: {
|
},
|
||||||
sortBy: GenreListSort.NAME,
|
query: {
|
||||||
sortOrder: SortOrder.ASC,
|
sortBy: GenreListSort.NAME,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.ASC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId: server?.id,
|
},
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const genreTitle = useMemo(() => {
|
const genreTitle = useMemo(() => {
|
||||||
if (!genreList.data) return '';
|
if (!genreList.data) return '';
|
||||||
|
|
@ -75,16 +78,18 @@ const AlbumListRoute = () => {
|
||||||
return genre?.name;
|
return genre?.name;
|
||||||
}, [genreId, genreList.data]);
|
}, [genreId, genreList.data]);
|
||||||
|
|
||||||
const itemCountCheck = useAlbumListCount({
|
const itemCountCheck = useQuery(
|
||||||
options: {
|
albumQueries.listCount({
|
||||||
cacheTime: 1000 * 60,
|
options: {
|
||||||
staleTime: 1000 * 60,
|
gcTime: 1000 * 60,
|
||||||
},
|
staleTime: 1000 * 60,
|
||||||
query: {
|
},
|
||||||
...albumListFilter,
|
query: {
|
||||||
},
|
...albumListFilter,
|
||||||
serverId: server?.id,
|
},
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const itemCount = itemCountCheck.data === null ? undefined : itemCountCheck.data;
|
const itemCount = itemCountCheck.data === null ? undefined : itemCountCheck.data;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -183,8 +183,8 @@ const DummyAlbumDetailRoute = () => {
|
||||||
fill: detailQuery?.data?.userFavorite ? 'primary' : undefined,
|
fill: detailQuery?.data?.userFavorite ? 'primary' : undefined,
|
||||||
}}
|
}}
|
||||||
loading={
|
loading={
|
||||||
createFavoriteMutation.isLoading ||
|
createFavoriteMutation.isPending ||
|
||||||
deleteFavoriteMutation.isLoading
|
deleteFavoriteMutation.isPending
|
||||||
}
|
}
|
||||||
onClick={handleFavorite}
|
onClick={handleFavorite}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
|
|
|
||||||
81
src/renderer/features/artists/api/artists-api.ts
Normal file
81
src/renderer/features/artists/api/artists-api.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '/@/renderer/api';
|
||||||
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
|
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { getServerById } from '/@/renderer/store';
|
||||||
|
import {
|
||||||
|
AlbumArtistDetailQuery,
|
||||||
|
AlbumArtistListQuery,
|
||||||
|
ArtistListQuery,
|
||||||
|
TopSongListQuery,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
export const artistsQueries = {
|
||||||
|
albumArtistDetail: (args: QueryHookArgs<AlbumArtistDetailQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
return api.controller.getAlbumArtistDetail({
|
||||||
|
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.albumArtists.detail(args.serverId || '', args.query),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
albumArtistList: (args: QueryHookArgs<AlbumArtistListQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
return api.controller.getAlbumArtistList({
|
||||||
|
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.albumArtists.list(args.serverId || '', args.query),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
albumArtistListCount: (args: QueryHookArgs<AlbumArtistListQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
return api.controller.getAlbumArtistListCount({
|
||||||
|
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.albumArtists.count(
|
||||||
|
args.serverId || '',
|
||||||
|
Object.keys(args.query).length === 0 ? undefined : args.query,
|
||||||
|
),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
artistListCount: (args: QueryHookArgs<ArtistListQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
return api.controller.getArtistListCount({
|
||||||
|
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.albumArtists.count(
|
||||||
|
args.serverId || '',
|
||||||
|
Object.keys(args.query).length === 0 ? undefined : args.query,
|
||||||
|
),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
topSongs: (args: QueryHookArgs<TopSongListQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
return api.controller.getTopSongs({
|
||||||
|
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.albumArtists.topSongs(args.serverId || '', args.query),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
|
import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath, useParams } from 'react-router';
|
import { generatePath, useParams } from 'react-router';
|
||||||
|
|
@ -8,9 +9,8 @@ import styles from './album-artist-detail-content.module.css';
|
||||||
|
|
||||||
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel/grid-carousel';
|
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel/grid-carousel';
|
||||||
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
|
import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table';
|
||||||
import { useAlbumList } from '/@/renderer/features/albums/queries/album-list-query';
|
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||||
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
|
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { useTopSongsList } from '/@/renderer/features/artists/queries/top-songs-list-query';
|
|
||||||
import {
|
import {
|
||||||
useHandleGeneralContextMenu,
|
useHandleGeneralContextMenu,
|
||||||
useHandleTableContextMenu,
|
useHandleTableContextMenu,
|
||||||
|
|
@ -75,10 +75,12 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
||||||
return [enabled, order];
|
return [enabled, order];
|
||||||
}, [artistItems]);
|
}, [artistItems]);
|
||||||
|
|
||||||
const detailQuery = useAlbumArtistDetail({
|
const detailQuery = useQuery(
|
||||||
query: { id: routeId },
|
artistsQueries.albumArtistDetail({
|
||||||
serverId: server?.id,
|
query: { id: routeId },
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const artistDiscographyLink = `${generatePath(
|
const artistDiscographyLink = `${generatePath(
|
||||||
AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_DISCOGRAPHY,
|
AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_DISCOGRAPHY,
|
||||||
|
|
@ -97,46 +99,52 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
||||||
artistName: detailQuery?.data?.name || '',
|
artistName: detailQuery?.data?.name || '',
|
||||||
})}`;
|
})}`;
|
||||||
|
|
||||||
const recentAlbumsQuery = useAlbumList({
|
const recentAlbumsQuery = useQuery(
|
||||||
options: {
|
albumQueries.list({
|
||||||
enabled: enabledItem.recentAlbums,
|
options: {
|
||||||
},
|
enabled: enabledItem.recentAlbums,
|
||||||
query: {
|
},
|
||||||
artistIds: [routeId],
|
query: {
|
||||||
compilation: false,
|
artistIds: [routeId],
|
||||||
limit: 15,
|
compilation: false,
|
||||||
sortBy: AlbumListSort.RELEASE_DATE,
|
limit: 15,
|
||||||
sortOrder: SortOrder.DESC,
|
sortBy: AlbumListSort.RELEASE_DATE,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.DESC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId: server?.id,
|
},
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const compilationAlbumsQuery = useAlbumList({
|
const compilationAlbumsQuery = useQuery(
|
||||||
options: {
|
albumQueries.list({
|
||||||
enabled: enabledItem.compilations && server?.type !== ServerType.SUBSONIC,
|
options: {
|
||||||
},
|
enabled: enabledItem.compilations && server?.type !== ServerType.SUBSONIC,
|
||||||
query: {
|
},
|
||||||
artistIds: [routeId],
|
query: {
|
||||||
compilation: true,
|
artistIds: [routeId],
|
||||||
limit: 15,
|
compilation: true,
|
||||||
sortBy: AlbumListSort.RELEASE_DATE,
|
limit: 15,
|
||||||
sortOrder: SortOrder.DESC,
|
sortBy: AlbumListSort.RELEASE_DATE,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.DESC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId: server?.id,
|
},
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const topSongsQuery = useTopSongsList({
|
const topSongsQuery = useQuery(
|
||||||
options: {
|
artistsQueries.topSongs({
|
||||||
enabled: !!detailQuery?.data?.name && enabledItem.topSongs,
|
options: {
|
||||||
},
|
enabled: !!detailQuery?.data?.name && enabledItem.topSongs,
|
||||||
query: {
|
},
|
||||||
artist: detailQuery?.data?.name || '',
|
query: {
|
||||||
artistId: routeId,
|
artist: detailQuery?.data?.name || '',
|
||||||
},
|
artistId: routeId,
|
||||||
serverId: server?.id,
|
},
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const topSongsColumnDefs: ColDef[] = useMemo(
|
const topSongsColumnDefs: ColDef[] = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|
@ -364,7 +372,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
|
||||||
fill: detailQuery?.data?.userFavorite ? 'primary' : undefined,
|
fill: detailQuery?.data?.userFavorite ? 'primary' : undefined,
|
||||||
}}
|
}}
|
||||||
loading={
|
loading={
|
||||||
createFavoriteMutation.isLoading || deleteFavoriteMutation.isLoading
|
createFavoriteMutation.isPending || deleteFavoriteMutation.isPending
|
||||||
}
|
}
|
||||||
onClick={handleFavorite}
|
onClick={handleFavorite}
|
||||||
size="lg"
|
size="lg"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { forwardRef, Fragment, Ref } from 'react';
|
import { forwardRef, Fragment, Ref } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
|
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { LibraryHeader, useSetRating } from '/@/renderer/features/shared';
|
import { LibraryHeader, useSetRating } from '/@/renderer/features/shared';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
|
|
@ -30,10 +31,12 @@ export const AlbumArtistDetailHeader = forwardRef(
|
||||||
const routeId = (artistId || albumArtistId) as string;
|
const routeId = (artistId || albumArtistId) as string;
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const detailQuery = useAlbumArtistDetail({
|
const detailQuery = useQuery(
|
||||||
query: { id: routeId },
|
artistsQueries.albumArtistDetail({
|
||||||
serverId: server?.id,
|
query: { id: routeId },
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const albumCount = detailQuery?.data?.albumCount;
|
const albumCount = detailQuery?.data?.albumCount;
|
||||||
const songCount = detailQuery?.data?.songCount;
|
const songCount = detailQuery?.data?.songCount;
|
||||||
|
|
@ -101,7 +104,7 @@ export const AlbumArtistDetailHeader = forwardRef(
|
||||||
<Rating
|
<Rating
|
||||||
onChange={handleUpdateRating}
|
onChange={handleUpdateRating}
|
||||||
readOnly={
|
readOnly={
|
||||||
detailQuery?.isFetching || updateRatingMutation.isLoading
|
detailQuery?.isFetching || updateRatingMutation.isPending
|
||||||
}
|
}
|
||||||
value={detailQuery?.data?.userRating || 0}
|
value={detailQuery?.data?.userRating || 0}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -83,9 +83,9 @@ export const AlbumArtistListGridView = ({ gridRef, itemCount }: AlbumArtistListG
|
||||||
|
|
||||||
const queryKey = queryKeys.albumArtists.list(server?.id || '', query);
|
const queryKey = queryKeys.albumArtists.list(server?.id || '', query);
|
||||||
|
|
||||||
const albumArtistsRes = await queryClient.fetchQuery(
|
const albumArtistsRes = await queryClient.fetchQuery({
|
||||||
queryKey,
|
gcTime: 1000 * 60 * 1,
|
||||||
async ({ signal }) =>
|
queryFn: async ({ signal }) =>
|
||||||
api.controller.getAlbumArtistList({
|
api.controller.getAlbumArtistList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
server,
|
server,
|
||||||
|
|
@ -93,8 +93,8 @@ export const AlbumArtistListGridView = ({ gridRef, itemCount }: AlbumArtistListG
|
||||||
},
|
},
|
||||||
query,
|
query,
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
queryKey,
|
||||||
);
|
});
|
||||||
|
|
||||||
return albumArtistsRes;
|
return albumArtistsRes;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
import { IDatasource } from '@ag-grid-community/core';
|
import { IDatasource } from '@ag-grid-community/core';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
import { MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
@ -12,7 +12,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
import { useListContext } from '/@/renderer/context/list-context';
|
import { useListContext } from '/@/renderer/context/list-context';
|
||||||
import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared';
|
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||||
import { FolderButton } from '/@/renderer/features/shared/components/folder-button';
|
import { FolderButton } from '/@/renderer/features/shared/components/folder-button';
|
||||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import { MoreButton } from '/@/renderer/features/shared/components/more-button';
|
import { MoreButton } from '/@/renderer/features/shared/components/more-button';
|
||||||
|
|
@ -146,7 +146,9 @@ export const AlbumArtistListHeaderFilters = ({
|
||||||
const cq = useContainerQuery();
|
const cq = useContainerQuery();
|
||||||
|
|
||||||
const isGrid = display === ListDisplayType.CARD || display === ListDisplayType.GRID;
|
const isGrid = display === ListDisplayType.CARD || display === ListDisplayType.GRID;
|
||||||
const musicFoldersQuery = useMusicFolders({ query: null, serverId: server?.id });
|
const musicFoldersQuery = useQuery(
|
||||||
|
sharedQueries.musicFolders({ query: null, serverId: server?.id }),
|
||||||
|
);
|
||||||
|
|
||||||
const sortByLabel =
|
const sortByLabel =
|
||||||
(server?.type &&
|
(server?.type &&
|
||||||
|
|
@ -176,9 +178,9 @@ export const AlbumArtistListHeaderFilters = ({
|
||||||
...filters,
|
...filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
const albums = await queryClient.fetchQuery(
|
const albums = await queryClient.fetchQuery({
|
||||||
queryKey,
|
gcTime: 1000 * 60 * 1,
|
||||||
async ({ signal }) =>
|
queryFn: async ({ signal }) =>
|
||||||
api.controller.getAlbumArtistList({
|
api.controller.getAlbumArtistList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
server,
|
server,
|
||||||
|
|
@ -190,8 +192,8 @@ export const AlbumArtistListHeaderFilters = ({
|
||||||
...filters,
|
...filters,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
queryKey,
|
||||||
);
|
});
|
||||||
|
|
||||||
return albums;
|
return albums;
|
||||||
},
|
},
|
||||||
|
|
@ -212,9 +214,9 @@ export const AlbumArtistListHeaderFilters = ({
|
||||||
...filters,
|
...filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
const albumArtistsRes = await queryClient.fetchQuery(
|
const albumArtistsRes = await queryClient.fetchQuery({
|
||||||
queryKey,
|
gcTime: 1000 * 60 * 1,
|
||||||
async ({ signal }) =>
|
queryFn: async ({ signal }) =>
|
||||||
api.controller.getAlbumArtistList({
|
api.controller.getAlbumArtistList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
server,
|
server,
|
||||||
|
|
@ -226,8 +228,8 @@ export const AlbumArtistListHeaderFilters = ({
|
||||||
...filters,
|
...filters,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
queryKey,
|
||||||
);
|
});
|
||||||
|
|
||||||
params.successCallback(
|
params.successCallback(
|
||||||
albumArtistsRes?.items || [],
|
albumArtistsRes?.items || [],
|
||||||
|
|
@ -362,7 +364,7 @@ export const AlbumArtistListHeaderFilters = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefresh = useCallback(() => {
|
const handleRefresh = useCallback(() => {
|
||||||
queryClient.invalidateQueries(queryKeys.albumArtists.list(server?.id || ''));
|
queryClient.invalidateQueries({ queryKey: queryKeys.albumArtists.list(server?.id || '') });
|
||||||
handleFilterChange(filter);
|
handleFilterChange(filter);
|
||||||
}, [filter, handleFilterChange, queryClient, server?.id]);
|
}, [filter, handleFilterChange, queryClient, server?.id]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,9 +84,9 @@ export const ArtistListGridView = ({ gridRef, itemCount }: ArtistListGridViewPro
|
||||||
|
|
||||||
const queryKey = queryKeys.artists.list(server?.id || '', query);
|
const queryKey = queryKeys.artists.list(server?.id || '', query);
|
||||||
|
|
||||||
const artistsRes = await queryClient.fetchQuery(
|
const artistsRes = await queryClient.fetchQuery({
|
||||||
queryKey,
|
gcTime: 1000 * 60 * 1,
|
||||||
async ({ signal }) =>
|
queryFn: async ({ signal }) =>
|
||||||
api.controller.getArtistList({
|
api.controller.getArtistList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
server,
|
server,
|
||||||
|
|
@ -94,8 +94,8 @@ export const ArtistListGridView = ({ gridRef, itemCount }: ArtistListGridViewPro
|
||||||
},
|
},
|
||||||
query,
|
query,
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
queryKey,
|
||||||
);
|
});
|
||||||
|
|
||||||
return artistsRes;
|
return artistsRes;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
import { IDatasource } from '@ag-grid-community/core';
|
import { IDatasource } from '@ag-grid-community/core';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { MouseEvent, MutableRefObject, useCallback } from 'react';
|
import { MouseEvent, MutableRefObject, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
@ -12,8 +12,8 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
import { useListContext } from '/@/renderer/context/list-context';
|
import { useListContext } from '/@/renderer/context/list-context';
|
||||||
import { useRoles } from '/@/renderer/features/artists/queries/roles-query';
|
import { OrderToggleButton } from '/@/renderer/features/shared';
|
||||||
import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared';
|
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 { MoreButton } from '/@/renderer/features/shared/components/more-button';
|
import { MoreButton } from '/@/renderer/features/shared/components/more-button';
|
||||||
import { RefreshButton } from '/@/renderer/features/shared/components/refresh-button';
|
import { RefreshButton } from '/@/renderer/features/shared/components/refresh-button';
|
||||||
|
|
@ -142,17 +142,21 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
|
||||||
const { setDisplayType, setFilter, setGrid, setTable, setTablePagination } =
|
const { setDisplayType, setFilter, setGrid, setTable, setTablePagination } =
|
||||||
useListStoreActions();
|
useListStoreActions();
|
||||||
const cq = useContainerQuery();
|
const cq = useContainerQuery();
|
||||||
const roles = useRoles({
|
const roles = useQuery(
|
||||||
options: {
|
sharedQueries.roles({
|
||||||
cacheTime: 1000 * 60 * 60 * 2,
|
options: {
|
||||||
staleTime: 1000 * 60 * 60 * 2,
|
gcTime: 1000 * 60 * 60 * 2,
|
||||||
},
|
staleTime: 1000 * 60 * 60 * 2,
|
||||||
query: {},
|
},
|
||||||
serverId: server?.id,
|
query: {},
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const isGrid = display === ListDisplayType.CARD || display === ListDisplayType.GRID;
|
const isGrid = display === ListDisplayType.CARD || display === ListDisplayType.GRID;
|
||||||
const musicFoldersQuery = useMusicFolders({ query: null, serverId: server?.id });
|
const musicFoldersQuery = useQuery(
|
||||||
|
sharedQueries.musicFolders({ query: null, serverId: server?.id }),
|
||||||
|
);
|
||||||
|
|
||||||
const sortByLabel =
|
const sortByLabel =
|
||||||
(server?.type &&
|
(server?.type &&
|
||||||
|
|
@ -182,9 +186,9 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
|
||||||
...filters,
|
...filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
const albums = await queryClient.fetchQuery(
|
const albums = await queryClient.fetchQuery({
|
||||||
queryKey,
|
gcTime: 1000 * 60 * 1,
|
||||||
async ({ signal }) =>
|
queryFn: async ({ signal }) =>
|
||||||
api.controller.getArtistList({
|
api.controller.getArtistList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
server,
|
server,
|
||||||
|
|
@ -196,8 +200,8 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
|
||||||
...filters,
|
...filters,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
queryKey,
|
||||||
);
|
});
|
||||||
|
|
||||||
return albums;
|
return albums;
|
||||||
},
|
},
|
||||||
|
|
@ -218,9 +222,9 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
|
||||||
...filters,
|
...filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
const artistsRes = await queryClient.fetchQuery(
|
const artistsRes = await queryClient.fetchQuery({
|
||||||
queryKey,
|
gcTime: 1000 * 60 * 1,
|
||||||
async ({ signal }) =>
|
queryFn: async ({ signal }) =>
|
||||||
api.controller.getArtistList({
|
api.controller.getArtistList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
server,
|
server,
|
||||||
|
|
@ -232,8 +236,8 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
|
||||||
...filters,
|
...filters,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{ cacheTime: 1000 * 60 * 1 },
|
queryKey,
|
||||||
);
|
});
|
||||||
|
|
||||||
params.successCallback(
|
params.successCallback(
|
||||||
artistsRes?.items || [],
|
artistsRes?.items || [],
|
||||||
|
|
@ -368,7 +372,7 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefresh = useCallback(() => {
|
const handleRefresh = useCallback(() => {
|
||||||
queryClient.invalidateQueries(queryKeys.artists.list(server?.id || ''));
|
queryClient.invalidateQueries({ queryKey: queryKeys.artists.list(server?.id || '') });
|
||||||
handleFilterChange(filter);
|
handleFilterChange(filter);
|
||||||
}, [filter, handleFilterChange, queryClient, server?.id]);
|
}, [filter, handleFilterChange, queryClient, server?.id]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import type { AlbumArtistDetailQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const useAlbumArtistDetail = (args: QueryHookArgs<AlbumArtistDetailQuery>) => {
|
|
||||||
const { options, query, serverId } = args || {};
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!server?.id && !!query.id,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getAlbumArtistDetail({
|
|
||||||
apiClientProps: { server, signal },
|
|
||||||
query,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.albumArtists.detail(server?.id || '', query),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
import { AlbumArtistListQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
export const useAlbumArtistListCount = (args: QueryHookArgs<AlbumArtistListQuery>) => {
|
|
||||||
const { options, query, serverId } = args;
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!serverId,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getAlbumArtistListCount({
|
|
||||||
apiClientProps: {
|
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
},
|
|
||||||
query,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.albumArtists.count(
|
|
||||||
serverId || '',
|
|
||||||
Object.keys(query).length === 0 ? undefined : query,
|
|
||||||
),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import type { AlbumArtistListQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const useAlbumArtistList = (args: QueryHookArgs<AlbumArtistListQuery>) => {
|
|
||||||
const { options, query, serverId } = args || {};
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!server?.id,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getAlbumArtistList({ apiClientProps: { server, signal }, query });
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.albumArtists.list(server?.id || '', query),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import type { AlbumArtistDetailQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const useAlbumArtistInfo = (args: QueryHookArgs<AlbumArtistDetailQuery>) => {
|
|
||||||
const { options, query, serverId } = args || {};
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!server?.id && !!query.id,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getAlbumArtistDetail({
|
|
||||||
apiClientProps: { server, signal },
|
|
||||||
query,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.albumArtists.detail(server?.id || '', query),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
import { ArtistListQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
export const useArtistListCount = (args: QueryHookArgs<ArtistListQuery>) => {
|
|
||||||
const { options, query, serverId } = args;
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!serverId,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getArtistListCount({
|
|
||||||
apiClientProps: {
|
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
},
|
|
||||||
query,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.albumArtists.count(
|
|
||||||
serverId || '',
|
|
||||||
Object.keys(query).length === 0 ? undefined : query,
|
|
||||||
),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const useRoles = (args: QueryHookArgs<object>) => {
|
|
||||||
const { options, serverId } = args;
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!serverId,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getRoles({
|
|
||||||
apiClientProps: {
|
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.roles.list(serverId || ''),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import type { TopSongListQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const useTopSongsList = (args: QueryHookArgs<TopSongListQuery>) => {
|
|
||||||
const { options, query, serverId } = args || {};
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!server?.id,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getTopSongs({ apiClientProps: { server, signal }, query });
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.albumArtists.topSongs(server?.id || '', query),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area';
|
import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area';
|
||||||
|
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { AlbumArtistDetailContent } from '/@/renderer/features/artists/components/album-artist-detail-content';
|
import { AlbumArtistDetailContent } from '/@/renderer/features/artists/components/album-artist-detail-content';
|
||||||
import { AlbumArtistDetailHeader } from '/@/renderer/features/artists/components/album-artist-detail-header';
|
import { AlbumArtistDetailHeader } from '/@/renderer/features/artists/components/album-artist-detail-header';
|
||||||
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
|
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
|
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||||
import { useFastAverageColor } from '/@/renderer/hooks';
|
import { useFastAverageColor } from '/@/renderer/hooks';
|
||||||
|
|
@ -27,10 +28,12 @@ const AlbumArtistDetailRoute = () => {
|
||||||
|
|
||||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||||
const playButtonBehavior = usePlayButtonBehavior();
|
const playButtonBehavior = usePlayButtonBehavior();
|
||||||
const detailQuery = useAlbumArtistDetail({
|
const detailQuery = useQuery(
|
||||||
query: { id: routeId },
|
artistsQueries.albumArtistDetail({
|
||||||
serverId: server?.id,
|
query: { id: routeId },
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const { background: backgroundColor, colorId } = useFastAverageColor({
|
const { background: backgroundColor, colorId } = useFastAverageColor({
|
||||||
id: artistId,
|
id: artistId,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
import { ListContext } from '/@/renderer/context/list-context';
|
import { ListContext } from '/@/renderer/context/list-context';
|
||||||
|
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { AlbumArtistDetailTopSongsListContent } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-content';
|
import { AlbumArtistDetailTopSongsListContent } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-content';
|
||||||
import { AlbumArtistDetailTopSongsListHeader } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-header';
|
import { AlbumArtistDetailTopSongsListHeader } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-header';
|
||||||
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
|
|
||||||
import { useTopSongsList } from '/@/renderer/features/artists/queries/top-songs-list-query';
|
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
import { useCurrentServer } from '/@/renderer/store/auth.store';
|
import { useCurrentServer } from '/@/renderer/store/auth.store';
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
@ -22,16 +22,20 @@ const AlbumArtistDetailTopSongsListRoute = () => {
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const pageKey = LibraryItem.SONG;
|
const pageKey = LibraryItem.SONG;
|
||||||
|
|
||||||
const detailQuery = useAlbumArtistDetail({
|
const detailQuery = useQuery(
|
||||||
query: { id: routeId },
|
artistsQueries.albumArtistDetail({
|
||||||
serverId: server?.id,
|
query: { id: routeId },
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const topSongsQuery = useTopSongsList({
|
const topSongsQuery = useQuery(
|
||||||
options: { enabled: !!detailQuery?.data?.name },
|
artistsQueries.topSongs({
|
||||||
query: { artist: detailQuery?.data?.name || '', artistId: routeId },
|
options: { enabled: !!detailQuery?.data?.name },
|
||||||
serverId: server?.id,
|
query: { artist: detailQuery?.data?.name || '', artistId: routeId },
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const itemCount = topSongsQuery?.data?.items?.length || 0;
|
const itemCount = topSongsQuery?.data?.items?.length || 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
|
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { ListContext } from '/@/renderer/context/list-context';
|
import { ListContext } from '/@/renderer/context/list-context';
|
||||||
|
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { AlbumArtistListContent } from '/@/renderer/features/artists/components/album-artist-list-content';
|
import { AlbumArtistListContent } from '/@/renderer/features/artists/components/album-artist-list-content';
|
||||||
import { AlbumArtistListHeader } from '/@/renderer/features/artists/components/album-artist-list-header';
|
import { AlbumArtistListHeader } from '/@/renderer/features/artists/components/album-artist-list-header';
|
||||||
import { useAlbumArtistListCount } from '/@/renderer/features/artists/queries/album-artist-list-count-query';
|
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
import { useCurrentServer } from '/@/renderer/store/auth.store';
|
import { useCurrentServer } from '/@/renderer/store/auth.store';
|
||||||
import { useListFilterByKey } from '/@/renderer/store/list.store';
|
import { useListFilterByKey } from '/@/renderer/store/list.store';
|
||||||
|
|
@ -20,14 +21,15 @@ const AlbumArtistListRoute = () => {
|
||||||
|
|
||||||
const albumArtistListFilter = useListFilterByKey<AlbumArtistListQuery>({ key: pageKey });
|
const albumArtistListFilter = useListFilterByKey<AlbumArtistListQuery>({ key: pageKey });
|
||||||
|
|
||||||
const itemCountCheck = useAlbumArtistListCount({
|
const itemCountCheck = useQuery(
|
||||||
options: {
|
artistsQueries.albumArtistListCount({
|
||||||
cacheTime: 1000 * 60,
|
options: {
|
||||||
staleTime: 1000 * 60,
|
gcTime: 1000 * 60,
|
||||||
},
|
},
|
||||||
query: albumArtistListFilter,
|
query: albumArtistListFilter,
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const itemCount = itemCountCheck.data === null ? undefined : itemCountCheck.data;
|
const itemCount = itemCountCheck.data === null ? undefined : itemCountCheck.data;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
|
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { ListContext } from '/@/renderer/context/list-context';
|
import { ListContext } from '/@/renderer/context/list-context';
|
||||||
|
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { ArtistListContent } from '/@/renderer/features/artists/components/artist-list-content';
|
import { ArtistListContent } from '/@/renderer/features/artists/components/artist-list-content';
|
||||||
import { ArtistListHeader } from '/@/renderer/features/artists/components/artist-list-header';
|
import { ArtistListHeader } from '/@/renderer/features/artists/components/artist-list-header';
|
||||||
import { useArtistListCount } from '/@/renderer/features/artists/queries/artist-list-count-query';
|
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
import { useCurrentServer } from '/@/renderer/store/auth.store';
|
import { useCurrentServer } from '/@/renderer/store/auth.store';
|
||||||
import { useListFilterByKey } from '/@/renderer/store/list.store';
|
import { useListFilterByKey } from '/@/renderer/store/list.store';
|
||||||
|
|
@ -20,14 +21,16 @@ const ArtistListRoute = () => {
|
||||||
|
|
||||||
const artistListFilter = useListFilterByKey<ArtistListQuery>({ key: pageKey });
|
const artistListFilter = useListFilterByKey<ArtistListQuery>({ key: pageKey });
|
||||||
|
|
||||||
const itemCountCheck = useArtistListCount({
|
const itemCountCheck = useQuery(
|
||||||
options: {
|
artistsQueries.artistListCount({
|
||||||
cacheTime: 1000 * 60,
|
options: {
|
||||||
staleTime: 1000 * 60,
|
gcTime: 1000 * 60,
|
||||||
},
|
staleTime: 1000 * 60,
|
||||||
query: artistListFilter,
|
},
|
||||||
serverId: server?.id,
|
query: artistListFilter,
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const itemCount = itemCountCheck.data === null ? undefined : itemCountCheck.data;
|
const itemCount = itemCountCheck.data === null ? undefined : itemCountCheck.data;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -549,7 +549,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||||
|
|
||||||
openModal({
|
openModal({
|
||||||
children: (
|
children: (
|
||||||
<ConfirmModal loading={removeFromPlaylistMutation.isLoading} onConfirm={confirm}>
|
<ConfirmModal loading={removeFromPlaylistMutation.isPending} onConfirm={confirm}>
|
||||||
{t('common.areYouSure', { postProcess: 'sentenceCase' })}
|
{t('common.areYouSure', { postProcess: 'sentenceCase' })}
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
),
|
),
|
||||||
|
|
|
||||||
25
src/renderer/features/genres/api/genres-api.ts
Normal file
25
src/renderer/features/genres/api/genres-api.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '/@/renderer/api';
|
||||||
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
|
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { getServerById } from '/@/renderer/store';
|
||||||
|
import { GenreListQuery } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
export const genresQueries = {
|
||||||
|
list: (args: QueryHookArgs<GenreListQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
gcTime: 1000 * 5,
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
const server = getServerById(args.serverId);
|
||||||
|
if (!server) throw new Error('Server not found');
|
||||||
|
return api.controller.getGenreList({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.genres.list(args.serverId || '', args.query),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
import { MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
@ -10,7 +10,8 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { GENRE_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
import { GENRE_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
import { useListContext } from '/@/renderer/context/list-context';
|
import { useListContext } from '/@/renderer/context/list-context';
|
||||||
import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared';
|
import { OrderToggleButton } from '/@/renderer/features/shared';
|
||||||
|
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||||
import { FolderButton } from '/@/renderer/features/shared/components/folder-button';
|
import { FolderButton } from '/@/renderer/features/shared/components/folder-button';
|
||||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import { MoreButton } from '/@/renderer/features/shared/components/more-button';
|
import { MoreButton } from '/@/renderer/features/shared/components/more-button';
|
||||||
|
|
@ -93,7 +94,9 @@ export const GenreListHeaderFilters = ({
|
||||||
server,
|
server,
|
||||||
});
|
});
|
||||||
|
|
||||||
const musicFoldersQuery = useMusicFolders({ query: null, serverId: server?.id });
|
const musicFoldersQuery = useQuery(
|
||||||
|
sharedQueries.musicFolders({ query: null, serverId: server?.id }),
|
||||||
|
);
|
||||||
|
|
||||||
const sortByLabel =
|
const sortByLabel =
|
||||||
(server?.type &&
|
(server?.type &&
|
||||||
|
|
@ -121,7 +124,7 @@ export const GenreListHeaderFilters = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleRefresh = useCallback(() => {
|
const handleRefresh = useCallback(() => {
|
||||||
queryClient.invalidateQueries(queryKeys.genres.list(server?.id || ''));
|
queryClient.invalidateQueries({ queryKey: queryKeys.genres.list(server?.id || '') });
|
||||||
onFilterChange(filter);
|
onFilterChange(filter);
|
||||||
}, [filter, onFilterChange, queryClient, server?.id]);
|
}, [filter, onFilterChange, queryClient, server?.id]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import type { GenreListQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const useGenreList = (args: QueryHookArgs<GenreListQuery>) => {
|
|
||||||
const { options, query, serverId } = args || {};
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!server,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getGenreList({ apiClientProps: { server, signal }, query });
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.genres.list(server?.id || '', query),
|
|
||||||
staleTime: 1000 * 5,
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
|
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { ListContext } from '/@/renderer/context/list-context';
|
import { ListContext } from '/@/renderer/context/list-context';
|
||||||
|
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||||
import { GenreListContent } from '/@/renderer/features/genres/components/genre-list-content';
|
import { GenreListContent } from '/@/renderer/features/genres/components/genre-list-content';
|
||||||
import { GenreListHeader } from '/@/renderer/features/genres/components/genre-list-header';
|
import { GenreListHeader } from '/@/renderer/features/genres/components/genre-list-header';
|
||||||
import { useGenreList } from '/@/renderer/features/genres/queries/genre-list-query';
|
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
import { useListStoreByKey } from '/@/renderer/store/list.store';
|
import { useListStoreByKey } from '/@/renderer/store/list.store';
|
||||||
|
|
@ -19,14 +20,16 @@ const GenreListRoute = () => {
|
||||||
const pageKey = 'genre';
|
const pageKey = 'genre';
|
||||||
const { filter } = useListStoreByKey<GenreListQuery>({ key: pageKey });
|
const { filter } = useListStoreByKey<GenreListQuery>({ key: pageKey });
|
||||||
|
|
||||||
const itemCountCheck = useGenreList({
|
const itemCountCheck = useQuery(
|
||||||
query: {
|
genresQueries.list({
|
||||||
...filter,
|
query: {
|
||||||
limit: 1,
|
...filter,
|
||||||
startIndex: 0,
|
limit: 1,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId: server?.id,
|
},
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const itemCount =
|
const itemCount =
|
||||||
itemCountCheck.data?.totalRecordCount === null
|
itemCountCheck.data?.totalRecordCount === null
|
||||||
|
|
|
||||||
32
src/renderer/features/home/api/home-api.ts
Normal file
32
src/renderer/features/home/api/home-api.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '/@/renderer/api';
|
||||||
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
|
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { getServerById } from '/@/renderer/store';
|
||||||
|
import { AlbumListQuery, AlbumListSort, SortOrder } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
export const homeQueries = {
|
||||||
|
recentlyPlayed: (args: QueryHookArgs<Partial<AlbumListQuery>>) => {
|
||||||
|
const requestQuery: AlbumListQuery = {
|
||||||
|
limit: 5,
|
||||||
|
sortBy: AlbumListSort.RECENTLY_PLAYED,
|
||||||
|
sortOrder: SortOrder.ASC,
|
||||||
|
startIndex: 0,
|
||||||
|
...args.query,
|
||||||
|
};
|
||||||
|
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
const server = getServerById(args.serverId);
|
||||||
|
if (!server) throw new Error('Server not found');
|
||||||
|
return api.controller.getAlbumList({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query: requestQuery,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.albums.list(args.serverId || '', requestQuery),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
import { AlbumListQuery, AlbumListSort, SortOrder } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
export const useRecentlyPlayed = (args: QueryHookArgs<Partial<AlbumListQuery>>) => {
|
|
||||||
const { options, query, serverId } = args;
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
const requestQuery: AlbumListQuery = {
|
|
||||||
limit: 5,
|
|
||||||
sortBy: AlbumListSort.RECENTLY_PLAYED,
|
|
||||||
sortOrder: SortOrder.ASC,
|
|
||||||
startIndex: 0,
|
|
||||||
...query,
|
|
||||||
};
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!server?.id,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getAlbumList({
|
|
||||||
apiClientProps: {
|
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
},
|
|
||||||
query: requestQuery,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
queryKey: queryKeys.albums.list(server?.id || '', requestQuery),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { FeatureCarousel } from '/@/renderer/components/feature-carousel/feature-carousel';
|
import { FeatureCarousel } from '/@/renderer/components/feature-carousel/feature-carousel';
|
||||||
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel/grid-carousel';
|
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel/grid-carousel';
|
||||||
import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area';
|
import { NativeScrollArea } from '/@/renderer/components/native-scroll-area/native-scroll-area';
|
||||||
import { useAlbumList } from '/@/renderer/features/albums';
|
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||||
import { useRecentlyPlayed } from '/@/renderer/features/home/queries/recently-played-query';
|
import { homeQueries } from '/@/renderer/features/home/api/home-api';
|
||||||
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
|
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||||
import { useSongList } from '/@/renderer/features/songs';
|
import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import {
|
import {
|
||||||
HomeItem,
|
HomeItem,
|
||||||
|
|
@ -43,20 +44,22 @@ const HomeRoute = () => {
|
||||||
const { windowBarStyle } = useWindowSettings();
|
const { windowBarStyle } = useWindowSettings();
|
||||||
const { homeFeature, homeItems } = useGeneralSettings();
|
const { homeFeature, homeItems } = useGeneralSettings();
|
||||||
|
|
||||||
const feature = useAlbumList({
|
const feature = useQuery(
|
||||||
options: {
|
albumQueries.list({
|
||||||
cacheTime: 1000 * 60,
|
options: {
|
||||||
enabled: homeFeature,
|
enabled: homeFeature,
|
||||||
staleTime: 1000 * 60,
|
gcTime: 1000 * 60,
|
||||||
},
|
staleTime: 1000 * 60,
|
||||||
query: {
|
},
|
||||||
limit: 20,
|
query: {
|
||||||
sortBy: AlbumListSort.RANDOM,
|
limit: 20,
|
||||||
sortOrder: SortOrder.DESC,
|
sortBy: AlbumListSort.RANDOM,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.DESC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId: server?.id,
|
},
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const isJellyfin = server?.type === ServerType.JELLYFIN;
|
const isJellyfin = server?.type === ServerType.JELLYFIN;
|
||||||
|
|
||||||
|
|
@ -74,80 +77,100 @@ const HomeRoute = () => {
|
||||||
);
|
);
|
||||||
}, [homeItems]);
|
}, [homeItems]);
|
||||||
|
|
||||||
const random = useAlbumList({
|
const random = useQuery(
|
||||||
options: {
|
albumQueries.list({
|
||||||
enabled: queriesEnabled[HomeItem.RANDOM],
|
|
||||||
staleTime: 1000 * 60 * 5,
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
...BASE_QUERY_ARGS,
|
|
||||||
sortBy: AlbumListSort.RANDOM,
|
|
||||||
},
|
|
||||||
serverId: server?.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const recentlyPlayed = useRecentlyPlayed({
|
|
||||||
options: {
|
|
||||||
enabled: queriesEnabled[HomeItem.RECENTLY_PLAYED] && !isJellyfin,
|
|
||||||
staleTime: 0,
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
...BASE_QUERY_ARGS,
|
|
||||||
sortBy: AlbumListSort.RECENTLY_PLAYED,
|
|
||||||
},
|
|
||||||
serverId: server?.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const recentlyAdded = useAlbumList({
|
|
||||||
options: {
|
|
||||||
enabled: queriesEnabled[HomeItem.RECENTLY_ADDED],
|
|
||||||
staleTime: 1000 * 60 * 5,
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
...BASE_QUERY_ARGS,
|
|
||||||
sortBy: AlbumListSort.RECENTLY_ADDED,
|
|
||||||
},
|
|
||||||
serverId: server?.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mostPlayedAlbums = useAlbumList({
|
|
||||||
options: {
|
|
||||||
enabled: !isJellyfin && queriesEnabled[HomeItem.MOST_PLAYED],
|
|
||||||
staleTime: 1000 * 60 * 5,
|
|
||||||
},
|
|
||||||
query: {
|
|
||||||
...BASE_QUERY_ARGS,
|
|
||||||
sortBy: AlbumListSort.PLAY_COUNT,
|
|
||||||
},
|
|
||||||
serverId: server?.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mostPlayedSongs = useSongList(
|
|
||||||
{
|
|
||||||
options: {
|
options: {
|
||||||
enabled: isJellyfin && queriesEnabled[HomeItem.MOST_PLAYED],
|
|
||||||
staleTime: 1000 * 60 * 5,
|
staleTime: 1000 * 60 * 5,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
...BASE_QUERY_ARGS,
|
...BASE_QUERY_ARGS,
|
||||||
sortBy: SongListSort.PLAY_COUNT,
|
sortBy: AlbumListSort.RANDOM,
|
||||||
|
sortOrder: SortOrder.ASC,
|
||||||
|
startIndex: 0,
|
||||||
},
|
},
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
},
|
}),
|
||||||
300,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const recentlyReleased = useAlbumList({
|
const recentlyPlayed = useQuery(
|
||||||
options: {
|
homeQueries.recentlyPlayed({
|
||||||
enabled: queriesEnabled[HomeItem.RECENTLY_RELEASED],
|
options: {
|
||||||
staleTime: 1000 * 60 * 5,
|
staleTime: 0,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
...BASE_QUERY_ARGS,
|
...BASE_QUERY_ARGS,
|
||||||
sortBy: AlbumListSort.RELEASE_DATE,
|
sortBy: AlbumListSort.RECENTLY_PLAYED,
|
||||||
},
|
sortOrder: SortOrder.DESC,
|
||||||
serverId: server?.id,
|
startIndex: 0,
|
||||||
});
|
},
|
||||||
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const recentlyAdded = useQuery(
|
||||||
|
albumQueries.list({
|
||||||
|
options: {
|
||||||
|
staleTime: 1000 * 60 * 5,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
...BASE_QUERY_ARGS,
|
||||||
|
sortBy: AlbumListSort.RECENTLY_ADDED,
|
||||||
|
sortOrder: SortOrder.DESC,
|
||||||
|
startIndex: 0,
|
||||||
|
},
|
||||||
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const mostPlayedAlbums = useQuery(
|
||||||
|
albumQueries.list({
|
||||||
|
options: {
|
||||||
|
enabled:
|
||||||
|
server?.type === ServerType.SUBSONIC || server?.type === ServerType.NAVIDROME,
|
||||||
|
staleTime: 1000 * 60 * 5,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
...BASE_QUERY_ARGS,
|
||||||
|
sortBy: AlbumListSort.PLAY_COUNT,
|
||||||
|
sortOrder: SortOrder.DESC,
|
||||||
|
startIndex: 0,
|
||||||
|
},
|
||||||
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const mostPlayedSongs = useQuery(
|
||||||
|
songsQueries.list(
|
||||||
|
{
|
||||||
|
options: {
|
||||||
|
enabled: server?.type === ServerType.JELLYFIN,
|
||||||
|
staleTime: 1000 * 60 * 5,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
...BASE_QUERY_ARGS,
|
||||||
|
sortBy: SongListSort.PLAY_COUNT,
|
||||||
|
sortOrder: SortOrder.DESC,
|
||||||
|
startIndex: 0,
|
||||||
|
},
|
||||||
|
serverId: server?.id,
|
||||||
|
},
|
||||||
|
300,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const recentlyReleased = useQuery(
|
||||||
|
albumQueries.list({
|
||||||
|
options: {
|
||||||
|
enabled: queriesEnabled[HomeItem.RECENTLY_RELEASED],
|
||||||
|
staleTime: 1000 * 60 * 5,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
...BASE_QUERY_ARGS,
|
||||||
|
sortBy: AlbumListSort.RELEASE_DATE,
|
||||||
|
},
|
||||||
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const isLoading =
|
const isLoading =
|
||||||
(random.isLoading && queriesEnabled[HomeItem.RANDOM]) ||
|
(random.isLoading && queriesEnabled[HomeItem.RANDOM]) ||
|
||||||
|
|
|
||||||
200
src/renderer/features/lyrics/api/lyrics-api.ts
Normal file
200
src/renderer/features/lyrics/api/lyrics-api.ts
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
import isElectron from 'is-electron';
|
||||||
|
|
||||||
|
import { api } from '/@/renderer/api';
|
||||||
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
|
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { getServerById, useSettingsStore } from '/@/renderer/store';
|
||||||
|
import { hasFeature } from '/@/shared/api/utils';
|
||||||
|
import {
|
||||||
|
FullLyricsMetadata,
|
||||||
|
InternetProviderLyricResponse,
|
||||||
|
InternetProviderLyricSearchResponse,
|
||||||
|
LyricGetQuery,
|
||||||
|
LyricSearchQuery,
|
||||||
|
LyricsQuery,
|
||||||
|
QueueSong,
|
||||||
|
ServerType,
|
||||||
|
StructuredLyric,
|
||||||
|
SynchronizedLyricsArray,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
|
import { LyricSource } from '/@/shared/types/domain-types';
|
||||||
|
import { ServerFeature } from '/@/shared/types/features-types';
|
||||||
|
|
||||||
|
const lyricsIpc = isElectron() ? window.api.lyrics : null;
|
||||||
|
|
||||||
|
// Match LRC lyrics format by https://github.com/ustbhuangyi/lyric-parser
|
||||||
|
// [mm:ss.SSS] text
|
||||||
|
const timeExp = /\[(\d{2,}):(\d{2})(?:\.(\d{2,3}))?]([^\n]+)(\n|$)/g;
|
||||||
|
|
||||||
|
// Match karaoke lyrics format returned by NetEase
|
||||||
|
// [SSS,???] text
|
||||||
|
const alternateTimeExp = /\[(\d*),(\d*)]([^\n]+)(\n|$)/g;
|
||||||
|
|
||||||
|
const formatLyrics = (lyrics: string) => {
|
||||||
|
const synchronizedLines = lyrics.matchAll(timeExp);
|
||||||
|
const formattedLyrics: SynchronizedLyricsArray = [];
|
||||||
|
|
||||||
|
for (const line of synchronizedLines) {
|
||||||
|
const [, minute, sec, ms, text] = line;
|
||||||
|
const minutes = parseInt(minute, 10);
|
||||||
|
const seconds = parseInt(sec, 10);
|
||||||
|
const milis = ms?.length === 3 ? parseInt(ms, 10) : parseInt(ms, 10) * 10;
|
||||||
|
|
||||||
|
const timeInMilis = (minutes * 60 + seconds) * 1000 + milis;
|
||||||
|
|
||||||
|
formattedLyrics.push([timeInMilis, text]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formattedLyrics.length > 0) return formattedLyrics;
|
||||||
|
|
||||||
|
const alternateSynchronizedLines = lyrics.matchAll(alternateTimeExp);
|
||||||
|
for (const line of alternateSynchronizedLines) {
|
||||||
|
const [, timeInMilis, , text] = line;
|
||||||
|
const cleanText = text
|
||||||
|
.replaceAll(/\(\d+,\d+\)/g, '')
|
||||||
|
.replaceAll(/\s,/g, ',')
|
||||||
|
.replaceAll(/\s\./g, '.');
|
||||||
|
formattedLyrics.push([Number(timeInMilis), cleanText]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formattedLyrics.length > 0) return formattedLyrics;
|
||||||
|
|
||||||
|
// If no synchronized lyrics were found, return the original lyrics
|
||||||
|
return lyrics;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const lyricsQueries = {
|
||||||
|
search: (args: Omit<QueryHookArgs<LyricSearchQuery>, 'serverId'>) => {
|
||||||
|
return queryOptions({
|
||||||
|
gcTime: 1000 * 60 * 1,
|
||||||
|
queryFn: () => {
|
||||||
|
if (lyricsIpc) {
|
||||||
|
return lyricsIpc.searchRemoteLyrics(args.query);
|
||||||
|
}
|
||||||
|
return {} as Record<LyricSource, InternetProviderLyricSearchResponse[]>;
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.songs.lyricsSearch(args.query),
|
||||||
|
staleTime: 1000 * 60 * 1,
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
serverLyrics: (args: QueryHookArgs<LyricsQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
const server = getServerById(args.serverId);
|
||||||
|
if (!server) throw new Error('Server not found');
|
||||||
|
// This should only be called for Jellyfin. Return null to ignore errors
|
||||||
|
if (server.type !== ServerType.JELLYFIN) return null;
|
||||||
|
return api.controller.getLyrics({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.songs.lyrics(args.serverId || '', args.query),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
songLyrics: (args: QueryHookArgs<LyricsQuery>, song: QueueSong | undefined) => {
|
||||||
|
return queryOptions({
|
||||||
|
gcTime: Infinity,
|
||||||
|
queryFn: async ({ signal }): Promise<FullLyricsMetadata | null | StructuredLyric[]> => {
|
||||||
|
const server = getServerById(song?.serverId);
|
||||||
|
if (!server) throw new Error('Server not found');
|
||||||
|
if (!song) return null;
|
||||||
|
|
||||||
|
const { preferLocalLyrics } = useSettingsStore.getState().lyrics;
|
||||||
|
|
||||||
|
let localLyrics: FullLyricsMetadata | null | StructuredLyric[] = null;
|
||||||
|
let remoteLyrics: FullLyricsMetadata | null | StructuredLyric[] = null;
|
||||||
|
|
||||||
|
if (hasFeature(server, ServerFeature.LYRICS_MULTIPLE_STRUCTURED)) {
|
||||||
|
const subsonicLyrics = await api.controller
|
||||||
|
.getStructuredLyrics({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query: { songId: song.id },
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
|
||||||
|
if (subsonicLyrics?.length) {
|
||||||
|
localLyrics = subsonicLyrics;
|
||||||
|
}
|
||||||
|
} else if (hasFeature(server, ServerFeature.LYRICS_SINGLE_STRUCTURED)) {
|
||||||
|
const jfLyrics = await api.controller
|
||||||
|
.getLyrics({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query: { songId: song.id },
|
||||||
|
})
|
||||||
|
.catch((err) => console.error(err));
|
||||||
|
|
||||||
|
if (jfLyrics) {
|
||||||
|
localLyrics = {
|
||||||
|
artist: song.artists?.[0]?.name,
|
||||||
|
lyrics: jfLyrics,
|
||||||
|
name: song.name,
|
||||||
|
remote: false,
|
||||||
|
source: server?.name ?? 'music server',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (song.lyrics) {
|
||||||
|
localLyrics = {
|
||||||
|
artist: song.artists?.[0]?.name,
|
||||||
|
lyrics: formatLyrics(song.lyrics),
|
||||||
|
name: song.name,
|
||||||
|
remote: false,
|
||||||
|
source: server?.name ?? 'music server',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferLocalLyrics && localLyrics) {
|
||||||
|
return localLyrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { fetch } = useSettingsStore.getState().lyrics;
|
||||||
|
|
||||||
|
if (fetch) {
|
||||||
|
const remoteLyricsResult: InternetProviderLyricResponse | null =
|
||||||
|
await lyricsIpc?.getRemoteLyricsBySong(song);
|
||||||
|
|
||||||
|
if (remoteLyricsResult) {
|
||||||
|
remoteLyrics = {
|
||||||
|
...remoteLyricsResult,
|
||||||
|
lyrics: formatLyrics(remoteLyricsResult.lyrics),
|
||||||
|
remote: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remoteLyrics) {
|
||||||
|
return remoteLyrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localLyrics) {
|
||||||
|
return localLyrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.songs.lyrics(args.serverId || '', args.query),
|
||||||
|
staleTime: Infinity,
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
songLyricsByRemoteId: (args: QueryHookArgs<Partial<LyricGetQuery>>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: async () => {
|
||||||
|
const remoteLyricsResult = await lyricsIpc?.getRemoteLyricsByRemoteId(
|
||||||
|
args.query as any,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (remoteLyricsResult) {
|
||||||
|
return formatLyrics(remoteLyricsResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.songs.lyricsByRemoteId(args.query),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { useDebouncedValue } from '@mantine/hooks';
|
import { useDebouncedValue } from '@mantine/hooks';
|
||||||
import { openModal } from '@mantine/modals';
|
import { openModal } from '@mantine/modals';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import orderBy from 'lodash/orderBy';
|
import orderBy from 'lodash/orderBy';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
@ -8,7 +9,7 @@ import { useTranslation } from 'react-i18next';
|
||||||
import styles from './lyrics-search-form.module.css';
|
import styles from './lyrics-search-form.module.css';
|
||||||
|
|
||||||
import i18n from '/@/i18n/i18n';
|
import i18n from '/@/i18n/i18n';
|
||||||
import { useLyricSearch } from '/@/renderer/features/lyrics/queries/lyric-search-query';
|
import { lyricsQueries } from '/@/renderer/features/lyrics/api/lyrics-api';
|
||||||
import { Divider } from '/@/shared/components/divider/divider';
|
import { Divider } from '/@/shared/components/divider/divider';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
|
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
|
||||||
|
|
@ -75,9 +76,11 @@ export const LyricsSearchForm = ({ artist, name, onSearchOverride }: LyricSearch
|
||||||
const [debouncedArtist] = useDebouncedValue(form.values.artist, 500);
|
const [debouncedArtist] = useDebouncedValue(form.values.artist, 500);
|
||||||
const [debouncedName] = useDebouncedValue(form.values.name, 500);
|
const [debouncedName] = useDebouncedValue(form.values.name, 500);
|
||||||
|
|
||||||
const { data, isInitialLoading } = useLyricSearch({
|
const { data, isInitialLoading } = useQuery(
|
||||||
query: { artist: debouncedArtist, name: debouncedName },
|
lyricsQueries.search({
|
||||||
});
|
query: { artist: debouncedArtist, name: debouncedName },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const searchResults = useMemo(() => {
|
const searchResults = useMemo(() => {
|
||||||
if (!data) return [];
|
if (!data) return [];
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { AnimatePresence, motion } from 'motion/react';
|
import { AnimatePresence, motion } from 'motion/react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
|
@ -7,12 +8,9 @@ import styles from './lyrics.module.css';
|
||||||
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { ErrorFallback } from '/@/renderer/features/action-required';
|
import { ErrorFallback } from '/@/renderer/features/action-required';
|
||||||
|
import { translateLyrics } from '/@/renderer/features/lyrics/api/lyric-translate';
|
||||||
|
import { lyricsQueries } from '/@/renderer/features/lyrics/api/lyrics-api';
|
||||||
import { LyricsActions } from '/@/renderer/features/lyrics/lyrics-actions';
|
import { LyricsActions } from '/@/renderer/features/lyrics/lyrics-actions';
|
||||||
import {
|
|
||||||
useSongLyricsByRemoteId,
|
|
||||||
useSongLyricsBySong,
|
|
||||||
} from '/@/renderer/features/lyrics/queries/lyric-query';
|
|
||||||
import { translateLyrics } from '/@/renderer/features/lyrics/queries/lyric-translate';
|
|
||||||
import {
|
import {
|
||||||
SynchronizedLyrics,
|
SynchronizedLyrics,
|
||||||
SynchronizedLyricsProps,
|
SynchronizedLyricsProps,
|
||||||
|
|
@ -43,12 +41,14 @@ export const Lyrics = () => {
|
||||||
const [translatedLyrics, setTranslatedLyrics] = useState<null | string>(null);
|
const [translatedLyrics, setTranslatedLyrics] = useState<null | string>(null);
|
||||||
const [showTranslation, setShowTranslation] = useState(false);
|
const [showTranslation, setShowTranslation] = useState(false);
|
||||||
|
|
||||||
const { data, isInitialLoading } = useSongLyricsBySong(
|
const { data, isInitialLoading } = useQuery(
|
||||||
{
|
lyricsQueries.songLyrics(
|
||||||
query: { songId: currentSong?.id || '' },
|
{
|
||||||
serverId: currentSong?.serverId || '',
|
query: { songId: currentSong?.id || '' },
|
||||||
},
|
serverId: currentSong?.serverId || '',
|
||||||
currentSong,
|
},
|
||||||
|
currentSong,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [override, setOverride] = useState<LyricsOverride | undefined>(undefined);
|
const [override, setOverride] = useState<LyricsOverride | undefined>(undefined);
|
||||||
|
|
@ -116,17 +116,19 @@ export const Lyrics = () => {
|
||||||
await fetchTranslation();
|
await fetchTranslation();
|
||||||
}, [translatedLyrics, showTranslation, fetchTranslation]);
|
}, [translatedLyrics, showTranslation, fetchTranslation]);
|
||||||
|
|
||||||
const { isInitialLoading: isOverrideLoading } = useSongLyricsByRemoteId({
|
const { isInitialLoading: isOverrideLoading } = useQuery(
|
||||||
options: {
|
lyricsQueries.songLyricsByRemoteId({
|
||||||
enabled: !!override,
|
options: {
|
||||||
},
|
enabled: !!override,
|
||||||
query: {
|
},
|
||||||
remoteSongId: override?.id,
|
query: {
|
||||||
remoteSource: override?.source as LyricSource | undefined,
|
remoteSongId: override?.id,
|
||||||
song: currentSong,
|
remoteSource: override?.source as LyricSource | undefined,
|
||||||
},
|
song: currentSong,
|
||||||
serverId: currentSong?.serverId,
|
},
|
||||||
});
|
serverId: currentSong?.serverId || '',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubSongChange = usePlayerStore.subscribe(
|
const unsubSongChange = usePlayerStore.subscribe(
|
||||||
|
|
|
||||||
|
|
@ -1,211 +0,0 @@
|
||||||
import { useQuery, useQueryClient, UseQueryResult } from '@tanstack/react-query';
|
|
||||||
import isElectron from 'is-electron';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import { getServerById, useLyricsSettings } from '/@/renderer/store';
|
|
||||||
import { hasFeature } from '/@/shared/api/utils';
|
|
||||||
import {
|
|
||||||
FullLyricsMetadata,
|
|
||||||
InternetProviderLyricResponse,
|
|
||||||
LyricGetQuery,
|
|
||||||
LyricsQuery,
|
|
||||||
QueueSong,
|
|
||||||
ServerType,
|
|
||||||
StructuredLyric,
|
|
||||||
SynchronizedLyricsArray,
|
|
||||||
} from '/@/shared/types/domain-types';
|
|
||||||
import { ServerFeature } from '/@/shared/types/features-types';
|
|
||||||
|
|
||||||
const lyricsIpc = isElectron() ? window.api.lyrics : null;
|
|
||||||
|
|
||||||
// Match LRC lyrics format by https://github.com/ustbhuangyi/lyric-parser
|
|
||||||
// [mm:ss.SSS] text
|
|
||||||
const timeExp = /\[(\d{2,}):(\d{2})(?:\.(\d{2,3}))?]([^\n]+)(\n|$)/g;
|
|
||||||
|
|
||||||
// Match karaoke lyrics format returned by NetEase
|
|
||||||
// [SSS,???] text
|
|
||||||
const alternateTimeExp = /\[(\d*),(\d*)]([^\n]+)(\n|$)/g;
|
|
||||||
|
|
||||||
const formatLyrics = (lyrics: string) => {
|
|
||||||
const synchronizedLines = lyrics.matchAll(timeExp);
|
|
||||||
const formattedLyrics: SynchronizedLyricsArray = [];
|
|
||||||
|
|
||||||
for (const line of synchronizedLines) {
|
|
||||||
const [, minute, sec, ms, text] = line;
|
|
||||||
const minutes = parseInt(minute, 10);
|
|
||||||
const seconds = parseInt(sec, 10);
|
|
||||||
const milis = ms?.length === 3 ? parseInt(ms, 10) : parseInt(ms, 10) * 10;
|
|
||||||
|
|
||||||
const timeInMilis = (minutes * 60 + seconds) * 1000 + milis;
|
|
||||||
|
|
||||||
formattedLyrics.push([timeInMilis, text]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formattedLyrics.length > 0) return formattedLyrics;
|
|
||||||
|
|
||||||
const alternateSynchronizedLines = lyrics.matchAll(alternateTimeExp);
|
|
||||||
for (const line of alternateSynchronizedLines) {
|
|
||||||
const [, timeInMilis, , text] = line;
|
|
||||||
const cleanText = text
|
|
||||||
.replaceAll(/\(\d+,\d+\)/g, '')
|
|
||||||
.replaceAll(/\s,/g, ',')
|
|
||||||
.replaceAll(/\s\./g, '.');
|
|
||||||
formattedLyrics.push([Number(timeInMilis), cleanText]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formattedLyrics.length > 0) return formattedLyrics;
|
|
||||||
|
|
||||||
// If no synchronized lyrics were found, return the original lyrics
|
|
||||||
return lyrics;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useServerLyrics = (
|
|
||||||
args: QueryHookArgs<LyricsQuery>,
|
|
||||||
): UseQueryResult<null | string> => {
|
|
||||||
const { query, serverId } = args;
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
// Note: This currently fetches for every song, even if it shouldn't have
|
|
||||||
// lyrics, because for some reason HasLyrics is not exposed. Thus, ignore the error
|
|
||||||
onError: () => {},
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
// This should only be called for Jellyfin. Return null to ignore errors
|
|
||||||
if (server.type !== ServerType.JELLYFIN) return null;
|
|
||||||
return api.controller.getLyrics({ apiClientProps: { server, signal }, query });
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.songs.lyrics(server?.id || '', query),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSongLyricsBySong = (
|
|
||||||
args: QueryHookArgs<LyricsQuery>,
|
|
||||||
song: QueueSong | undefined,
|
|
||||||
): UseQueryResult<FullLyricsMetadata | StructuredLyric[]> => {
|
|
||||||
const { query } = args;
|
|
||||||
const { fetch, preferLocalLyrics } = useLyricsSettings();
|
|
||||||
const server = getServerById(song?.serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
cacheTime: Infinity,
|
|
||||||
enabled: !!song && !!server,
|
|
||||||
onError: () => {},
|
|
||||||
queryFn: async ({ signal }): Promise<FullLyricsMetadata | null | StructuredLyric[]> => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
if (!song) return null;
|
|
||||||
|
|
||||||
let localLyrics: FullLyricsMetadata | null | StructuredLyric[] = null;
|
|
||||||
let remoteLyrics: FullLyricsMetadata | null | StructuredLyric[] = null;
|
|
||||||
|
|
||||||
if (hasFeature(server, ServerFeature.LYRICS_MULTIPLE_STRUCTURED)) {
|
|
||||||
const subsonicLyrics = await api.controller
|
|
||||||
.getStructuredLyrics({
|
|
||||||
apiClientProps: { server, signal },
|
|
||||||
query: { songId: song.id },
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
|
|
||||||
if (subsonicLyrics?.length) {
|
|
||||||
localLyrics = subsonicLyrics;
|
|
||||||
}
|
|
||||||
} else if (hasFeature(server, ServerFeature.LYRICS_SINGLE_STRUCTURED)) {
|
|
||||||
const jfLyrics = await api.controller
|
|
||||||
.getLyrics({
|
|
||||||
apiClientProps: { server, signal },
|
|
||||||
query: { songId: song.id },
|
|
||||||
})
|
|
||||||
.catch((err) => console.error(err));
|
|
||||||
|
|
||||||
if (jfLyrics) {
|
|
||||||
localLyrics = {
|
|
||||||
artist: song.artists?.[0]?.name,
|
|
||||||
lyrics: jfLyrics,
|
|
||||||
name: song.name,
|
|
||||||
remote: false,
|
|
||||||
source: server?.name ?? 'music server',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (song.lyrics) {
|
|
||||||
localLyrics = {
|
|
||||||
artist: song.artists?.[0]?.name,
|
|
||||||
lyrics: formatLyrics(song.lyrics),
|
|
||||||
name: song.name,
|
|
||||||
remote: false,
|
|
||||||
source: server?.name ?? 'music server',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preferLocalLyrics && localLyrics) {
|
|
||||||
return localLyrics;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fetch) {
|
|
||||||
const remoteLyricsResult: InternetProviderLyricResponse | null =
|
|
||||||
await lyricsIpc?.getRemoteLyricsBySong(song);
|
|
||||||
|
|
||||||
if (remoteLyricsResult) {
|
|
||||||
remoteLyrics = {
|
|
||||||
...remoteLyricsResult,
|
|
||||||
lyrics: formatLyrics(remoteLyricsResult.lyrics),
|
|
||||||
remote: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remoteLyrics) {
|
|
||||||
return remoteLyrics;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (localLyrics) {
|
|
||||||
return localLyrics;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.songs.lyrics(server?.id || '', query),
|
|
||||||
staleTime: Infinity,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSongLyricsByRemoteId = (
|
|
||||||
args: QueryHookArgs<Partial<LyricGetQuery>>,
|
|
||||||
): UseQueryResult<null | string> => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const { query, serverId } = args;
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!query.remoteSongId && !!query.remoteSource,
|
|
||||||
onError: () => {},
|
|
||||||
onSuccess: (data) => {
|
|
||||||
if (!data || !query.song) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lyricsResult = {
|
|
||||||
artist: query.song.artists?.[0]?.name,
|
|
||||||
lyrics: data,
|
|
||||||
name: query.song.name,
|
|
||||||
remote: false,
|
|
||||||
source: query.remoteSource,
|
|
||||||
};
|
|
||||||
|
|
||||||
queryClient.setQueryData(
|
|
||||||
queryKeys.songs.lyrics(serverId, { songId: query.song.id }),
|
|
||||||
lyricsResult,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
queryFn: async () => {
|
|
||||||
const remoteLyricsResult = await lyricsIpc?.getRemoteLyricsByRemoteId(query as any);
|
|
||||||
|
|
||||||
if (remoteLyricsResult) {
|
|
||||||
return formatLyrics(remoteLyricsResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.songs.lyricsByRemoteId(query),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import isElectron from 'is-electron';
|
|
||||||
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import {
|
|
||||||
InternetProviderLyricSearchResponse,
|
|
||||||
LyricSearchQuery,
|
|
||||||
LyricSource,
|
|
||||||
} from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
const lyricsIpc = isElectron() ? window.api.lyrics : null;
|
|
||||||
|
|
||||||
export const useLyricSearch = (args: Omit<QueryHookArgs<LyricSearchQuery>, 'serverId'>) => {
|
|
||||||
const { options, query } = args;
|
|
||||||
|
|
||||||
return useQuery<Record<LyricSource, InternetProviderLyricSearchResponse[]>>({
|
|
||||||
cacheTime: 1000 * 60 * 1,
|
|
||||||
enabled: !!query.artist || !!query.name,
|
|
||||||
queryFn: () => {
|
|
||||||
if (lyricsIpc) {
|
|
||||||
return lyricsIpc.searchRemoteLyrics(query);
|
|
||||||
}
|
|
||||||
return {} as Record<LyricSource, InternetProviderLyricSearchResponse[]>;
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.songs.lyricsSearch(query),
|
|
||||||
staleTime: 1000 * 60 * 1,
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -94,7 +94,7 @@ export const ShuffleAllModal = ({
|
||||||
|
|
||||||
const handlePlay = async (playType: Play) => {
|
const handlePlay = async (playType: Play) => {
|
||||||
const res = await queryClient.fetchQuery({
|
const res = await queryClient.fetchQuery({
|
||||||
cacheTime: 0,
|
gcTime: 0,
|
||||||
queryFn: ({ signal }) =>
|
queryFn: ({ signal }) =>
|
||||||
api.controller.getRandomSongList({
|
api.controller.getRandomSongList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
|
|
@ -253,7 +253,7 @@ export const openShuffleAllModal = async (
|
||||||
const server = useAuthStore.getState().currentServer;
|
const server = useAuthStore.getState().currentServer;
|
||||||
|
|
||||||
const genres = await props.queryClient.fetchQuery({
|
const genres = await props.queryClient.fetchQuery({
|
||||||
cacheTime: 1000 * 60 * 60 * 4,
|
gcTime: 1000 * 60 * 5,
|
||||||
queryFn: ({ signal }) =>
|
queryFn: ({ signal }) =>
|
||||||
api.controller.getGenreList({
|
api.controller.getGenreList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
|
|
@ -267,11 +267,11 @@ export const openShuffleAllModal = async (
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
queryKey: queryKeys.genres.list(server?.id),
|
queryKey: queryKeys.genres.list(server?.id),
|
||||||
staleTime: 1000 * 60 * 5,
|
staleTime: 1000 * 60 * 60 * 4,
|
||||||
});
|
});
|
||||||
|
|
||||||
const musicFolders = await props.queryClient.fetchQuery({
|
const musicFolders = await props.queryClient.fetchQuery({
|
||||||
cacheTime: 1000 * 60 * 60 * 4,
|
gcTime: 1000 * 60 * 5,
|
||||||
queryFn: ({ signal }) =>
|
queryFn: ({ signal }) =>
|
||||||
api.controller.getMusicFolderList({
|
api.controller.getMusicFolderList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
|
|
@ -280,7 +280,7 @@ export const openShuffleAllModal = async (
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
queryKey: queryKeys.musicFolders.list(server?.id),
|
queryKey: queryKeys.musicFolders.list(server?.id),
|
||||||
staleTime: 1000 * 60 * 5,
|
staleTime: 1000 * 60 * 60 * 4,
|
||||||
});
|
});
|
||||||
|
|
||||||
openModal({
|
openModal({
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@ export const getPlaylistSongsById = async (args: {
|
||||||
|
|
||||||
const queryKey = queryKeys.playlists.songList(server?.id, id);
|
const queryKey = queryKeys.playlists.songList(server?.id, id);
|
||||||
|
|
||||||
const res = await queryClient.fetchQuery(
|
const res = await queryClient.fetchQuery({
|
||||||
queryKey,
|
gcTime: 1000 * 60,
|
||||||
async ({ signal }) =>
|
queryFn: async ({ signal }) =>
|
||||||
api.controller.getPlaylistSongList({
|
api.controller.getPlaylistSongList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
server,
|
server,
|
||||||
|
|
@ -38,11 +38,9 @@ export const getPlaylistSongsById = async (args: {
|
||||||
},
|
},
|
||||||
query: queryFilter,
|
query: queryFilter,
|
||||||
}),
|
}),
|
||||||
{
|
queryKey,
|
||||||
cacheTime: 1000 * 60,
|
staleTime: 1000 * 60,
|
||||||
staleTime: 1000 * 60,
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
res.items = sortSongList(
|
res.items = sortSongList(
|
||||||
|
|
@ -74,9 +72,9 @@ export const getAlbumSongsById = async (args: {
|
||||||
|
|
||||||
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
||||||
|
|
||||||
const res = await queryClient.fetchQuery(
|
const res = await queryClient.fetchQuery({
|
||||||
queryKey,
|
gcTime: 1000 * 60,
|
||||||
async ({ signal }) =>
|
queryFn: async ({ signal }) =>
|
||||||
api.controller.getSongList({
|
api.controller.getSongList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
server,
|
server,
|
||||||
|
|
@ -84,11 +82,9 @@ export const getAlbumSongsById = async (args: {
|
||||||
},
|
},
|
||||||
query: queryFilter,
|
query: queryFilter,
|
||||||
}),
|
}),
|
||||||
{
|
queryKey,
|
||||||
cacheTime: 1000 * 60,
|
staleTime: 1000 * 60,
|
||||||
staleTime: 1000 * 60,
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
@ -118,9 +114,9 @@ export const getGenreSongsById = async (args: {
|
||||||
|
|
||||||
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
||||||
|
|
||||||
const res = await queryClient.fetchQuery(
|
const res = await queryClient.fetchQuery({
|
||||||
queryKey,
|
gcTime: 1000 * 60,
|
||||||
async ({ signal }) =>
|
queryFn: async ({ signal }) =>
|
||||||
api.controller.getSongList({
|
api.controller.getSongList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
server,
|
server,
|
||||||
|
|
@ -128,11 +124,9 @@ export const getGenreSongsById = async (args: {
|
||||||
},
|
},
|
||||||
query: queryFilter,
|
query: queryFilter,
|
||||||
}),
|
}),
|
||||||
{
|
queryKey,
|
||||||
cacheTime: 1000 * 60,
|
staleTime: 1000 * 60,
|
||||||
staleTime: 1000 * 60,
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
data.items.push(...res!.items);
|
data.items.push(...res!.items);
|
||||||
if (data.totalRecordCount) {
|
if (data.totalRecordCount) {
|
||||||
|
|
@ -162,9 +156,9 @@ export const getAlbumArtistSongsById = async (args: {
|
||||||
|
|
||||||
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
||||||
|
|
||||||
const res = await queryClient.fetchQuery(
|
const res = await queryClient.fetchQuery({
|
||||||
queryKey,
|
gcTime: 1000 * 60,
|
||||||
async ({ signal }) =>
|
queryFn: async ({ signal }) =>
|
||||||
api.controller.getSongList({
|
api.controller.getSongList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
server,
|
server,
|
||||||
|
|
@ -172,11 +166,9 @@ export const getAlbumArtistSongsById = async (args: {
|
||||||
},
|
},
|
||||||
query: queryFilter,
|
query: queryFilter,
|
||||||
}),
|
}),
|
||||||
{
|
queryKey,
|
||||||
cacheTime: 1000 * 60,
|
staleTime: 1000 * 60,
|
||||||
staleTime: 1000 * 60,
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
@ -199,9 +191,9 @@ export const getArtistSongsById = async (args: {
|
||||||
|
|
||||||
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
||||||
|
|
||||||
const res = await queryClient.fetchQuery(
|
const res = await queryClient.fetchQuery({
|
||||||
queryKey,
|
gcTime: 1000 * 60,
|
||||||
async ({ signal }) =>
|
queryFn: async ({ signal }) =>
|
||||||
api.controller.getSongList({
|
api.controller.getSongList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
server,
|
server,
|
||||||
|
|
@ -209,11 +201,9 @@ export const getArtistSongsById = async (args: {
|
||||||
},
|
},
|
||||||
query: queryFilter,
|
query: queryFilter,
|
||||||
}),
|
}),
|
||||||
{
|
queryKey,
|
||||||
cacheTime: 1000 * 60,
|
staleTime: 1000 * 60,
|
||||||
staleTime: 1000 * 60,
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
@ -234,9 +224,9 @@ export const getSongsByQuery = async (args: {
|
||||||
|
|
||||||
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
|
||||||
|
|
||||||
const res = await queryClient.fetchQuery(
|
const res = await queryClient.fetchQuery({
|
||||||
queryKey,
|
gcTime: 1000 * 60,
|
||||||
async ({ signal }) => {
|
queryFn: async ({ signal }) => {
|
||||||
return api.controller.getSongList({
|
return api.controller.getSongList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
server,
|
server,
|
||||||
|
|
@ -245,11 +235,9 @@ export const getSongsByQuery = async (args: {
|
||||||
query: queryFilter,
|
query: queryFilter,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
queryKey,
|
||||||
cacheTime: 1000 * 60,
|
staleTime: 1000 * 60,
|
||||||
staleTime: 1000 * 60,
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
@ -265,9 +253,9 @@ export const getSongById = async (args: {
|
||||||
|
|
||||||
const queryKey = queryKeys.songs.detail(server?.id, queryFilter);
|
const queryKey = queryKeys.songs.detail(server?.id, queryFilter);
|
||||||
|
|
||||||
const res = await queryClient.fetchQuery(
|
const res = await queryClient.fetchQuery({
|
||||||
queryKey,
|
gcTime: 1000 * 60,
|
||||||
async ({ signal }) =>
|
queryFn: async ({ signal }) =>
|
||||||
api.controller.getSongDetail({
|
api.controller.getSongDetail({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
server,
|
server,
|
||||||
|
|
@ -275,11 +263,9 @@ export const getSongById = async (args: {
|
||||||
},
|
},
|
||||||
query: queryFilter,
|
query: queryFilter,
|
||||||
}),
|
}),
|
||||||
{
|
queryKey,
|
||||||
cacheTime: 1000 * 60,
|
staleTime: 1000 * 60,
|
||||||
staleTime: 1000 * 60,
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!res) throw new Error('Song not found');
|
if (!res) throw new Error('Song not found');
|
||||||
|
|
||||||
|
|
|
||||||
57
src/renderer/features/playlists/api/playlists-api.ts
Normal file
57
src/renderer/features/playlists/api/playlists-api.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '/@/renderer/api';
|
||||||
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
|
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { getServerById } from '/@/renderer/store';
|
||||||
|
import {
|
||||||
|
PlaylistDetailQuery,
|
||||||
|
PlaylistListQuery,
|
||||||
|
PlaylistSongListQuery,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
export const playlistsQueries = {
|
||||||
|
detail: (args: QueryHookArgs<PlaylistDetailQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
const server = getServerById(args.serverId);
|
||||||
|
if (!server) throw new Error('Server not found');
|
||||||
|
return api.controller.getPlaylistDetail({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.playlists.detail(args.serverId || '', args.query.id, args.query),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
list: (args: QueryHookArgs<PlaylistListQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
gcTime: 1000 * 60 * 60,
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
const server = getServerById(args.serverId);
|
||||||
|
if (!server) throw new Error('Server not found');
|
||||||
|
return api.controller.getPlaylistList({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.playlists.list(args.serverId || '', args.query),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
songList: (args: QueryHookArgs<PlaylistSongListQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
const server = getServerById(args.serverId);
|
||||||
|
if (!server) throw new Error('Server not found');
|
||||||
|
return api.controller.getPlaylistSongList({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.playlists.songList(args.serverId || '', args.query.id),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { closeModal, ContextModalProps } from '@mantine/modals';
|
import { closeModal, ContextModalProps } from '@mantine/modals';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { memo, useCallback, useMemo, useRef, useState } from 'react';
|
import { memo, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
|
@ -8,8 +9,8 @@ import styles from './add-to-playlist-context-modal.module.css';
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { getGenreSongsById } from '/@/renderer/features/player';
|
import { getGenreSongsById } from '/@/renderer/features/player';
|
||||||
|
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||||
import { useAddToPlaylist } from '/@/renderer/features/playlists/mutations/add-to-playlist-mutation';
|
import { useAddToPlaylist } from '/@/renderer/features/playlists/mutations/add-to-playlist-mutation';
|
||||||
import { usePlaylistList } from '/@/renderer/features/playlists/queries/playlist-list-query';
|
|
||||||
import { queryClient } from '/@/renderer/lib/react-query';
|
import { queryClient } from '/@/renderer/lib/react-query';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
import { formatDurationString } from '/@/renderer/utils';
|
import { formatDurationString } from '/@/renderer/utils';
|
||||||
|
|
@ -66,19 +67,21 @@ export const AddToPlaylistContextModal = ({
|
||||||
|
|
||||||
const addToPlaylistMutation = useAddToPlaylist({});
|
const addToPlaylistMutation = useAddToPlaylist({});
|
||||||
|
|
||||||
const playlistList = usePlaylistList({
|
const playlistList = useQuery(
|
||||||
query: {
|
playlistsQueries.list({
|
||||||
_custom: {
|
query: {
|
||||||
navidrome: {
|
_custom: {
|
||||||
smart: false,
|
navidrome: {
|
||||||
|
smart: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
sortBy: PlaylistListSort.NAME,
|
||||||
|
sortOrder: SortOrder.ASC,
|
||||||
|
startIndex: 0,
|
||||||
},
|
},
|
||||||
sortBy: PlaylistListSort.NAME,
|
serverId: server?.id,
|
||||||
sortOrder: SortOrder.ASC,
|
}),
|
||||||
startIndex: 0,
|
);
|
||||||
},
|
|
||||||
serverId: server?.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [playlistSelect, playlistMap] = useMemo(() => {
|
const [playlistSelect, playlistMap] = useMemo(() => {
|
||||||
const existingPlaylists = new Array<Playlist & { label: string; value: string }>();
|
const existingPlaylists = new Array<Playlist & { label: string; value: string }>();
|
||||||
|
|
@ -113,9 +116,15 @@ export const AddToPlaylistContextModal = ({
|
||||||
|
|
||||||
const queryKey = queryKeys.songs.list(server?.id || '', query);
|
const queryKey = queryKeys.songs.list(server?.id || '', query);
|
||||||
|
|
||||||
const songsRes = await queryClient.fetchQuery(queryKey, ({ signal }) => {
|
const songsRes = await queryClient.fetchQuery({
|
||||||
if (!server) throw new Error('No server');
|
queryFn: ({ signal }) => {
|
||||||
return api.controller.getSongList({ apiClientProps: { server, signal }, query });
|
if (!server) throw new Error('No server');
|
||||||
|
return api.controller.getSongList({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
return songsRes;
|
return songsRes;
|
||||||
|
|
@ -134,9 +143,15 @@ export const AddToPlaylistContextModal = ({
|
||||||
|
|
||||||
const queryKey = queryKeys.songs.list(server?.id || '', query);
|
const queryKey = queryKeys.songs.list(server?.id || '', query);
|
||||||
|
|
||||||
const songsRes = await queryClient.fetchQuery(queryKey, ({ signal }) => {
|
const songsRes = await queryClient.fetchQuery({
|
||||||
if (!server) throw new Error('No server');
|
queryFn: ({ signal }) => {
|
||||||
return api.controller.getSongList({ apiClientProps: { server, signal }, query });
|
if (!server) throw new Error('No server');
|
||||||
|
return api.controller.getSongList({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
return songsRes;
|
return songsRes;
|
||||||
|
|
@ -213,9 +228,8 @@ export const AddToPlaylistContextModal = ({
|
||||||
if (values.skipDuplicates) {
|
if (values.skipDuplicates) {
|
||||||
const queryKey = queryKeys.playlists.songList(server?.id || '', playlistId);
|
const queryKey = queryKeys.playlists.songList(server?.id || '', playlistId);
|
||||||
|
|
||||||
const playlistSongsRes = await queryClient.fetchQuery(
|
const playlistSongsRes = await queryClient.fetchQuery({
|
||||||
queryKey,
|
queryFn: ({ signal }) => {
|
||||||
({ signal }) => {
|
|
||||||
if (!server)
|
if (!server)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
t('error.serverNotSelectedError', {
|
t('error.serverNotSelectedError', {
|
||||||
|
|
@ -232,7 +246,8 @@ export const AddToPlaylistContextModal = ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
queryKey,
|
||||||
|
});
|
||||||
|
|
||||||
const playlistSongIds = playlistSongsRes?.items?.map((song) => song.id);
|
const playlistSongIds = playlistSongsRes?.items?.map((song) => song.id);
|
||||||
|
|
||||||
|
|
@ -508,7 +523,7 @@ export const AddToPlaylistContextModal = ({
|
||||||
/>
|
/>
|
||||||
<Group justify="flex-end">
|
<Group justify="flex-end">
|
||||||
<ModalButton
|
<ModalButton
|
||||||
disabled={isLoading || addToPlaylistMutation.isLoading}
|
disabled={isLoading || addToPlaylistMutation.isPending}
|
||||||
onClick={() => closeModal(id)}
|
onClick={() => closeModal(id)}
|
||||||
uppercase
|
uppercase
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
|
|
@ -518,7 +533,7 @@ export const AddToPlaylistContextModal = ({
|
||||||
<ModalButton
|
<ModalButton
|
||||||
disabled={
|
disabled={
|
||||||
isLoading ||
|
isLoading ||
|
||||||
addToPlaylistMutation.isLoading ||
|
addToPlaylistMutation.isPending ||
|
||||||
(form.values.selectedPlaylistIds.length === 0 &&
|
(form.values.selectedPlaylistIds.length === 0 &&
|
||||||
form.values.newPlaylists.length === 0)
|
form.values.newPlaylists.length === 0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const isPublicDisplayed = hasFeature(server, ServerFeature.PUBLIC_PLAYLIST);
|
const isPublicDisplayed = hasFeature(server, ServerFeature.PUBLIC_PLAYLIST);
|
||||||
const isSubmitDisabled = !form.values.name || mutation.isLoading;
|
const isSubmitDisabled = !form.values.name || mutation.isPending;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
|
|
@ -160,7 +160,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
|
||||||
</ModalButton>
|
</ModalButton>
|
||||||
<ModalButton
|
<ModalButton
|
||||||
disabled={isSubmitDisabled}
|
disabled={isSubmitDisabled}
|
||||||
loading={mutation.isLoading}
|
loading={mutation.isPending}
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import type {
|
||||||
} from '@ag-grid-community/core';
|
} from '@ag-grid-community/core';
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { AnimatePresence } from 'motion/react';
|
import { AnimatePresence } from 'motion/react';
|
||||||
import { MutableRefObject, useCallback, useMemo } from 'react';
|
import { MutableRefObject, useCallback, useMemo } from 'react';
|
||||||
|
|
@ -25,7 +25,7 @@ import {
|
||||||
SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS,
|
SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS,
|
||||||
} from '/@/renderer/features/context-menu/context-menu-items';
|
} from '/@/renderer/features/context-menu/context-menu-items';
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||||
import { useAppFocus } from '/@/renderer/hooks';
|
import { useAppFocus } from '/@/renderer/hooks';
|
||||||
import {
|
import {
|
||||||
useCurrentServer,
|
useCurrentServer,
|
||||||
|
|
@ -70,7 +70,9 @@ export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetai
|
||||||
};
|
};
|
||||||
}, [page?.table.id, playlistId]);
|
}, [page?.table.id, playlistId]);
|
||||||
|
|
||||||
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
|
const detailQuery = useQuery(
|
||||||
|
playlistsQueries.detail({ query: { id: playlistId }, serverId: server?.id }),
|
||||||
|
);
|
||||||
|
|
||||||
const p = usePlaylistDetailTablePagination(playlistId);
|
const p = usePlaylistDetailTablePagination(playlistId);
|
||||||
const pagination = {
|
const pagination = {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
import { closeAllModals, openModal } from '@mantine/modals';
|
import { closeAllModals, openModal } from '@mantine/modals';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
|
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
@ -10,9 +10,9 @@ import { useNavigate, useParams } from 'react-router';
|
||||||
import i18n from '/@/i18n/i18n';
|
import i18n from '/@/i18n/i18n';
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
|
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||||
import { openUpdatePlaylistModal } from '/@/renderer/features/playlists/components/update-playlist-form';
|
import { openUpdatePlaylistModal } from '/@/renderer/features/playlists/components/update-playlist-form';
|
||||||
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
||||||
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
|
||||||
import { OrderToggleButton } from '/@/renderer/features/shared';
|
import { OrderToggleButton } from '/@/renderer/features/shared';
|
||||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import { MoreButton } from '/@/renderer/features/shared/components/more-button';
|
import { MoreButton } from '/@/renderer/features/shared/components/more-button';
|
||||||
|
|
@ -262,7 +262,9 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
||||||
const sortBy = page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID;
|
const sortBy = page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID;
|
||||||
const sortOrder = page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC;
|
const sortOrder = page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC;
|
||||||
|
|
||||||
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
|
const detailQuery = useQuery(
|
||||||
|
playlistsQueries.detail({ query: { id: playlistId }, serverId: server?.id }),
|
||||||
|
);
|
||||||
const isSmartPlaylist = detailQuery.data?.rules;
|
const isSmartPlaylist = detailQuery.data?.rules;
|
||||||
|
|
||||||
const cq = useContainerQuery();
|
const cq = useContainerQuery();
|
||||||
|
|
@ -291,7 +293,9 @@ export const PlaylistDetailSongListHeaderFilters = ({
|
||||||
}, [tableRef, page.display, setPagination]);
|
}, [tableRef, page.display, setPagination]);
|
||||||
|
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
queryClient.invalidateQueries(queryKeys.playlists.songList(server?.id || '', playlistId));
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: queryKeys.playlists.songList(server?.id || '', playlistId),
|
||||||
|
});
|
||||||
handleFilterChange();
|
handleFilterChange();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,27 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { MutableRefObject } from 'react';
|
import { MutableRefObject } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
import { PageHeader } from '/@/renderer/components/page-header/page-header';
|
import { PageHeader } from '/@/renderer/components/page-header/page-header';
|
||||||
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
|
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||||
import { PlaylistDetailSongListHeaderFilters } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header-filters';
|
import { PlaylistDetailSongListHeaderFilters } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header-filters';
|
||||||
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
|
||||||
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
|
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer, usePlaylistDetailStore } from '/@/renderer/store';
|
||||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||||
import { formatDurationString } from '/@/renderer/utils';
|
import { formatDurationString } from '/@/renderer/utils';
|
||||||
import { Badge } from '/@/shared/components/badge/badge';
|
import { Badge } from '/@/shared/components/badge/badge';
|
||||||
import { SpinnerIcon } from '/@/shared/components/spinner/spinner';
|
import { SpinnerIcon } from '/@/shared/components/spinner/spinner';
|
||||||
import { Stack } from '/@/shared/components/stack/stack';
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
|
import {
|
||||||
|
LibraryItem,
|
||||||
|
PlaylistSongListQueryClientSide,
|
||||||
|
SongListSort,
|
||||||
|
SortOrder,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
import { Play } from '/@/shared/types/types';
|
import { Play } from '/@/shared/types/types';
|
||||||
|
|
||||||
interface PlaylistDetailHeaderProps {
|
interface PlaylistDetailHeaderProps {
|
||||||
|
|
@ -33,7 +41,23 @@ export const PlaylistDetailSongListHeader = ({
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { playlistId } = useParams() as { playlistId: string };
|
const { playlistId } = useParams() as { playlistId: string };
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
|
const detailQuery = useQuery(
|
||||||
|
playlistsQueries.detail({ query: { id: playlistId }, serverId: server?.id }),
|
||||||
|
);
|
||||||
|
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||||
|
const page = usePlaylistDetailStore();
|
||||||
|
const filters: Partial<PlaylistSongListQueryClientSide> = {
|
||||||
|
sortBy: page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID,
|
||||||
|
sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC,
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePlay = async (playType: Play) => {
|
||||||
|
handlePlayQueueAdd?.({
|
||||||
|
byItemType: { id: [playlistId], type: LibraryItem.PLAYLIST },
|
||||||
|
playType,
|
||||||
|
query: filters,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const playButtonBehavior = usePlayButtonBehavior();
|
const playButtonBehavior = usePlayButtonBehavior();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -121,15 +121,17 @@ export const PlaylistListGridView = ({ gridRef, itemCount }: PlaylistListGridVie
|
||||||
|
|
||||||
const queryKey = queryKeys.playlists.list(server?.id || '', query);
|
const queryKey = queryKeys.playlists.list(server?.id || '', query);
|
||||||
|
|
||||||
const playlists = await queryClient.fetchQuery(queryKey, async ({ signal }) =>
|
const playlists = await queryClient.fetchQuery({
|
||||||
controller.getPlaylistList({
|
queryFn: async ({ signal }) =>
|
||||||
apiClientProps: {
|
controller.getPlaylistList({
|
||||||
server,
|
apiClientProps: {
|
||||||
signal,
|
server,
|
||||||
},
|
signal,
|
||||||
query,
|
},
|
||||||
}),
|
query,
|
||||||
);
|
}),
|
||||||
|
queryKey,
|
||||||
|
});
|
||||||
|
|
||||||
return playlists;
|
return playlists;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -170,15 +170,17 @@ export const PlaylistListHeaderFilters = ({
|
||||||
|
|
||||||
const queryKey = queryKeys.playlists.list(server?.id || '', query);
|
const queryKey = queryKeys.playlists.list(server?.id || '', query);
|
||||||
|
|
||||||
const playlists = await queryClient.fetchQuery(queryKey, async ({ signal }) =>
|
const playlists = await queryClient.fetchQuery({
|
||||||
api.controller.getPlaylistList({
|
queryFn: async ({ signal }) =>
|
||||||
apiClientProps: {
|
api.controller.getPlaylistList({
|
||||||
server,
|
apiClientProps: {
|
||||||
signal,
|
server,
|
||||||
},
|
signal,
|
||||||
query,
|
},
|
||||||
}),
|
query,
|
||||||
);
|
}),
|
||||||
|
queryKey,
|
||||||
|
});
|
||||||
|
|
||||||
return playlists;
|
return playlists;
|
||||||
},
|
},
|
||||||
|
|
@ -207,9 +209,8 @@ export const PlaylistListHeaderFilters = ({
|
||||||
...pageFilters,
|
...pageFilters,
|
||||||
});
|
});
|
||||||
|
|
||||||
const playlistsRes = await queryClient.fetchQuery(
|
const playlistsRes = await queryClient.fetchQuery({
|
||||||
queryKey,
|
queryFn: async ({ signal }) =>
|
||||||
async ({ signal }) =>
|
|
||||||
api.controller.getPlaylistList({
|
api.controller.getPlaylistList({
|
||||||
apiClientProps: {
|
apiClientProps: {
|
||||||
server,
|
server,
|
||||||
|
|
@ -221,7 +222,8 @@ export const PlaylistListHeaderFilters = ({
|
||||||
...pageFilters,
|
...pageFilters,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
queryKey,
|
||||||
|
});
|
||||||
|
|
||||||
params.successCallback(
|
params.successCallback(
|
||||||
playlistsRes?.items || [],
|
playlistsRes?.items || [],
|
||||||
|
|
@ -338,7 +340,9 @@ export const PlaylistListHeaderFilters = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
queryClient.invalidateQueries(queryKeys.playlists.list(server?.id || '', filter));
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: queryKeys.playlists.list(server?.id || '', filter),
|
||||||
|
});
|
||||||
handleFilterChange(filter);
|
handleFilterChange(filter);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { openModal } from '@mantine/modals';
|
import { openModal } from '@mantine/modals';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import clone from 'lodash/clone';
|
import clone from 'lodash/clone';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import setWith from 'lodash/setWith';
|
import setWith from 'lodash/setWith';
|
||||||
|
|
@ -8,7 +9,7 @@ import { forwardRef, Ref, useImperativeHandle, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { QueryBuilder } from '/@/renderer/components/query-builder';
|
import { QueryBuilder } from '/@/renderer/components/query-builder';
|
||||||
import { usePlaylistList } from '/@/renderer/features/playlists/queries/playlist-list-query';
|
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||||
import {
|
import {
|
||||||
convertNDQueryToQueryGroup,
|
convertNDQueryToQueryGroup,
|
||||||
convertQueryGroupToNDQuery,
|
convertQueryGroupToNDQuery,
|
||||||
|
|
@ -108,10 +109,12 @@ export const PlaylistQueryBuilder = forwardRef(
|
||||||
query ? convertNDQueryToQueryGroup(query) : DEFAULT_QUERY,
|
query ? convertNDQueryToQueryGroup(query) : DEFAULT_QUERY,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: playlists } = usePlaylistList({
|
const { data: playlists } = useQuery(
|
||||||
query: { sortBy: PlaylistListSort.NAME, sortOrder: SortOrder.ASC, startIndex: 0 },
|
playlistsQueries.list({
|
||||||
serverId: server?.id,
|
query: { sortBy: PlaylistListSort.NAME, sortOrder: SortOrder.ASC, startIndex: 0 },
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const playlistData = useMemo(() => {
|
const playlistData = useMemo(() => {
|
||||||
if (!playlists) return [];
|
if (!playlists) return [];
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ export const SaveAsPlaylistForm = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const isPublicDisplayed = hasFeature(server, ServerFeature.PUBLIC_PLAYLIST);
|
const isPublicDisplayed = hasFeature(server, ServerFeature.PUBLIC_PLAYLIST);
|
||||||
const isSubmitDisabled = !form.values.name || mutation.isLoading;
|
const isSubmitDisabled = !form.values.name || mutation.isPending;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
|
|
@ -106,7 +106,7 @@ export const SaveAsPlaylistForm = ({
|
||||||
<ModalButton onClick={onCancel}>{t('common.cancel')}</ModalButton>
|
<ModalButton onClick={onCancel}>{t('common.cancel')}</ModalButton>
|
||||||
<ModalButton
|
<ModalButton
|
||||||
disabled={isSubmitDisabled}
|
disabled={isSubmitDisabled}
|
||||||
loading={mutation.isLoading}
|
loading={mutation.isPending}
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ export const UpdatePlaylistForm = ({ body, onCancel, query, users }: UpdatePlayl
|
||||||
|
|
||||||
const isPublicDisplayed = hasFeature(server, ServerFeature.PUBLIC_PLAYLIST);
|
const isPublicDisplayed = hasFeature(server, ServerFeature.PUBLIC_PLAYLIST);
|
||||||
const isOwnerDisplayed = server?.type === ServerType.NAVIDROME && userList;
|
const isOwnerDisplayed = server?.type === ServerType.NAVIDROME && userList;
|
||||||
const isSubmitDisabled = !form.values.name || mutation.isLoading;
|
const isSubmitDisabled = !form.values.name || mutation.isPending;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
|
|
@ -143,7 +143,7 @@ export const UpdatePlaylistForm = ({ body, onCancel, query, users }: UpdatePlayl
|
||||||
<ModalButton onClick={onCancel}>{t('common.cancel')}</ModalButton>
|
<ModalButton onClick={onCancel}>{t('common.cancel')}</ModalButton>
|
||||||
<ModalButton
|
<ModalButton
|
||||||
disabled={isSubmitDisabled}
|
disabled={isSubmitDisabled}
|
||||||
loading={mutation.isLoading}
|
loading={mutation.isPending}
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,16 @@ export const useAddToPlaylist = (args: MutationHookArgs) => {
|
||||||
|
|
||||||
if (!serverId) return;
|
if (!serverId) return;
|
||||||
|
|
||||||
queryClient.invalidateQueries(queryKeys.playlists.list(serverId), { exact: false });
|
queryClient.invalidateQueries({
|
||||||
queryClient.invalidateQueries(queryKeys.playlists.detail(serverId, variables.query.id));
|
exact: false,
|
||||||
queryClient.invalidateQueries(
|
queryKey: queryKeys.playlists.list(serverId),
|
||||||
queryKeys.playlists.songList(serverId, variables.query.id),
|
});
|
||||||
);
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: queryKeys.playlists.detail(serverId, variables.query.id),
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: queryKeys.playlists.songList(serverId, variables.query.id),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,10 @@ export const useCreatePlaylist = (args: MutationHookArgs) => {
|
||||||
onSuccess: (_args, variables) => {
|
onSuccess: (_args, variables) => {
|
||||||
const server = getServerById(variables.serverId);
|
const server = getServerById(variables.serverId);
|
||||||
if (server) {
|
if (server) {
|
||||||
queryClient.invalidateQueries(queryKeys.playlists.list(server.id));
|
queryClient.invalidateQueries({
|
||||||
|
exact: false,
|
||||||
|
queryKey: queryKeys.playlists.list(server.id),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,14 @@ export const useDeletePlaylist = (args: MutationHookArgs) => {
|
||||||
return api.controller.deletePlaylist({ ...args, apiClientProps: { server } });
|
return api.controller.deletePlaylist({ ...args, apiClientProps: { server } });
|
||||||
},
|
},
|
||||||
onMutate: () => {
|
onMutate: () => {
|
||||||
queryClient.cancelQueries(queryKeys.playlists.list(server?.id || ''));
|
queryClient.cancelQueries({ queryKey: queryKeys.playlists.list(server?.id || '') });
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(queryKeys.playlists.list(server?.id || ''));
|
queryClient.invalidateQueries({
|
||||||
|
exact: false,
|
||||||
|
queryKey: queryKeys.playlists.list(server?.id || ''),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,15 @@ export const useRemoveFromPlaylist = (options?: MutationOptions) => {
|
||||||
|
|
||||||
if (!serverId) return;
|
if (!serverId) return;
|
||||||
|
|
||||||
queryClient.invalidateQueries(queryKeys.playlists.list(serverId), { exact: false });
|
queryClient.invalidateQueries({
|
||||||
queryClient.invalidateQueries(queryKeys.playlists.detail(serverId, variables.query.id));
|
queryKey: queryKeys.playlists.list(serverId),
|
||||||
queryClient.invalidateQueries(
|
});
|
||||||
queryKeys.playlists.songList(serverId, variables.query.id),
|
queryClient.invalidateQueries({
|
||||||
);
|
queryKey: queryKeys.playlists.detail(serverId, variables.query.id),
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: queryKeys.playlists.songList(serverId, variables.query.id),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,14 @@ export const useUpdatePlaylist = (args: MutationHookArgs) => {
|
||||||
|
|
||||||
if (!serverId) return;
|
if (!serverId) return;
|
||||||
|
|
||||||
queryClient.invalidateQueries(queryKeys.playlists.list(serverId));
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: queryKeys.playlists.list(serverId),
|
||||||
|
});
|
||||||
|
|
||||||
if (query?.id) {
|
if (query?.id) {
|
||||||
queryClient.invalidateQueries(queryKeys.playlists.detail(serverId, query.id));
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: queryKeys.playlists.detail(serverId, query.id),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import type { PlaylistDetailQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const usePlaylistDetail = (args: QueryHookArgs<PlaylistDetailQuery>) => {
|
|
||||||
const { options, query, serverId } = args || {};
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!server?.id,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getPlaylistDetail({ apiClientProps: { server, signal }, query });
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.playlists.detail(server?.id || '', query.id, query),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import type { QueryOptions } from '/@/renderer/lib/react-query';
|
|
||||||
import type { PlaylistListQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const usePlaylistList = (args: {
|
|
||||||
options?: QueryOptions;
|
|
||||||
query: PlaylistListQuery;
|
|
||||||
serverId?: string;
|
|
||||||
}) => {
|
|
||||||
const { options, query, serverId } = args;
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
cacheTime: 1000 * 60 * 60,
|
|
||||||
enabled: !!server?.id,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getPlaylistList({ apiClientProps: { server, signal }, query });
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.playlists.list(server?.id || '', query),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import type { PlaylistSongListQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const usePlaylistSongList = (args: QueryHookArgs<PlaylistSongListQuery>) => {
|
|
||||||
const { options, query, serverId } = args || {};
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!server,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getPlaylistSongList({
|
|
||||||
apiClientProps: { server, signal },
|
|
||||||
query,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.playlists.songList(server?.id || '', query.id),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
import { closeAllModals, openModal } from '@mantine/modals';
|
import { closeAllModals, openModal } from '@mantine/modals';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import Fuse from 'fuse.js';
|
||||||
import { motion } from 'motion/react';
|
import { motion } from 'motion/react';
|
||||||
import { useMemo, useRef, useState } from 'react';
|
import { useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath, useNavigate, useParams } from 'react-router';
|
import { generatePath, useNavigate, useParams } from 'react-router';
|
||||||
|
|
||||||
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
|
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
|
||||||
|
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||||
import { PlaylistDetailSongListContent } from '/@/renderer/features/playlists/components/playlist-detail-song-list-content';
|
import { PlaylistDetailSongListContent } from '/@/renderer/features/playlists/components/playlist-detail-song-list-content';
|
||||||
import { PlaylistDetailSongListHeader } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header';
|
import { PlaylistDetailSongListHeader } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header';
|
||||||
import { PlaylistQueryBuilder } from '/@/renderer/features/playlists/components/playlist-query-builder';
|
import { PlaylistQueryBuilder } from '/@/renderer/features/playlists/components/playlist-query-builder';
|
||||||
import { SaveAsPlaylistForm } from '/@/renderer/features/playlists/components/save-as-playlist-form';
|
import { SaveAsPlaylistForm } from '/@/renderer/features/playlists/components/save-as-playlist-form';
|
||||||
import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation';
|
import { useCreatePlaylist } from '/@/renderer/features/playlists/mutations/create-playlist-mutation';
|
||||||
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
import { useDeletePlaylist } from '/@/renderer/features/playlists/mutations/delete-playlist-mutation';
|
||||||
import { usePlaylistDetail } from '/@/renderer/features/playlists/queries/playlist-detail-query';
|
|
||||||
import { usePlaylistSongList } from '/@/renderer/features/playlists/queries/playlist-song-list-query';
|
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useCurrentServer, usePlaylistDetailStore } from '/@/renderer/store';
|
import { useCurrentServer, usePlaylistDetailStore } from '/@/renderer/store';
|
||||||
|
|
@ -35,7 +36,9 @@ const PlaylistDetailSongListRoute = () => {
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const handlePlayQueueAdd = useHandlePlayQueueAdd();
|
const handlePlayQueueAdd = useHandlePlayQueueAdd();
|
||||||
|
|
||||||
const detailQuery = usePlaylistDetail({ query: { id: playlistId }, serverId: server?.id });
|
const detailQuery = useQuery(
|
||||||
|
playlistsQueries.detail({ query: { id: playlistId }, serverId: server?.id }),
|
||||||
|
);
|
||||||
const createPlaylistMutation = useCreatePlaylist({});
|
const createPlaylistMutation = useCreatePlaylist({});
|
||||||
const deletePlaylistMutation = useDeletePlaylist({});
|
const deletePlaylistMutation = useDeletePlaylist({});
|
||||||
|
|
||||||
|
|
@ -148,12 +151,14 @@ const PlaylistDetailSongListRoute = () => {
|
||||||
|
|
||||||
const page = usePlaylistDetailStore();
|
const page = usePlaylistDetailStore();
|
||||||
|
|
||||||
const playlistSongs = usePlaylistSongList({
|
const playlistSongs = useQuery(
|
||||||
query: {
|
playlistsQueries.songList({
|
||||||
id: playlistId,
|
query: {
|
||||||
},
|
id: playlistId,
|
||||||
serverId: server?.id,
|
},
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const filterSortedSongs = useMemo(() => {
|
const filterSortedSongs = useMemo(() => {
|
||||||
let items = playlistSongs.data?.items;
|
let items = playlistSongs.data?.items;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { ListContext } from '/@/renderer/context/list-context';
|
import { ListContext } from '/@/renderer/context/list-context';
|
||||||
|
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||||
import { PlaylistListContent } from '/@/renderer/features/playlists/components/playlist-list-content';
|
import { PlaylistListContent } from '/@/renderer/features/playlists/components/playlist-list-content';
|
||||||
import { PlaylistListHeader } from '/@/renderer/features/playlists/components/playlist-list-header';
|
import { PlaylistListHeader } from '/@/renderer/features/playlists/components/playlist-list-header';
|
||||||
import { usePlaylistList } from '/@/renderer/features/playlists/queries/playlist-list-query';
|
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
import { useCurrentServer, useListStoreByKey } from '/@/renderer/store';
|
import { useCurrentServer, useListStoreByKey } from '/@/renderer/store';
|
||||||
import { PlaylistListSort, PlaylistSongListQuery, SortOrder } from '/@/shared/types/domain-types';
|
import { PlaylistListSort, PlaylistSongListQuery, SortOrder } from '/@/shared/types/domain-types';
|
||||||
|
|
@ -20,20 +21,21 @@ const PlaylistListRoute = () => {
|
||||||
const pageKey = 'playlist';
|
const pageKey = 'playlist';
|
||||||
const { filter } = useListStoreByKey<PlaylistSongListQuery>({ key: pageKey });
|
const { filter } = useListStoreByKey<PlaylistSongListQuery>({ key: pageKey });
|
||||||
|
|
||||||
const itemCountCheck = usePlaylistList({
|
const itemCountCheck = useQuery(
|
||||||
options: {
|
playlistsQueries.list({
|
||||||
cacheTime: 1000 * 60 * 60 * 2,
|
options: {
|
||||||
staleTime: 1000 * 60 * 60 * 2,
|
gcTime: 1000 * 60 * 60 * 2,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
...filter,
|
...filter,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
sortBy: PlaylistListSort.NAME,
|
sortBy: PlaylistListSort.NAME,
|
||||||
sortOrder: SortOrder.ASC,
|
sortOrder: SortOrder.ASC,
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
},
|
},
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const itemCount =
|
const itemCount =
|
||||||
itemCountCheck.data?.totalRecordCount === null
|
itemCountCheck.data?.totalRecordCount === null
|
||||||
|
|
|
||||||
22
src/renderer/features/search/api/search-api.ts
Normal file
22
src/renderer/features/search/api/search-api.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '/@/renderer/api';
|
||||||
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
|
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { getServerById } from '/@/renderer/store';
|
||||||
|
import { SearchQuery } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
export const searchQueries = {
|
||||||
|
search: (args: QueryHookArgs<SearchQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
return api.controller.search({
|
||||||
|
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.search.list(args.serverId || '', args.query),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Fragment, useCallback, useRef, useState } from 'react';
|
import { Fragment, useCallback, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath, useNavigate } from 'react-router';
|
import { generatePath, useNavigate } from 'react-router';
|
||||||
|
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
|
import { searchQueries } from '/@/renderer/features/search/api/search-api';
|
||||||
import { Command, CommandPalettePages } from '/@/renderer/features/search/components/command';
|
import { Command, CommandPalettePages } from '/@/renderer/features/search/components/command';
|
||||||
import { CommandItemSelectable } from '/@/renderer/features/search/components/command-item-selectable';
|
import { CommandItemSelectable } from '/@/renderer/features/search/components/command-item-selectable';
|
||||||
import { GoToCommands } from '/@/renderer/features/search/components/go-to-commands';
|
import { GoToCommands } from '/@/renderer/features/search/components/go-to-commands';
|
||||||
import { HomeCommands } from '/@/renderer/features/search/components/home-commands';
|
import { HomeCommands } from '/@/renderer/features/search/components/home-commands';
|
||||||
import { LibraryCommandItem } from '/@/renderer/features/search/components/library-command-item';
|
import { LibraryCommandItem } from '/@/renderer/features/search/components/library-command-item';
|
||||||
import { ServerCommands } from '/@/renderer/features/search/components/server-commands';
|
import { ServerCommands } from '/@/renderer/features/search/components/server-commands';
|
||||||
import { useSearch } from '/@/renderer/features/search/queries/search-query';
|
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||||
|
|
@ -48,19 +49,21 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { data, isLoading } = useSearch({
|
const { data, isLoading } = useQuery(
|
||||||
options: { enabled: isHome && debouncedQuery !== '' && query !== '' },
|
searchQueries.search({
|
||||||
query: {
|
options: { enabled: isHome && debouncedQuery !== '' && query !== '' },
|
||||||
albumArtistLimit: 4,
|
query: {
|
||||||
albumArtistStartIndex: 0,
|
albumArtistLimit: 4,
|
||||||
albumLimit: 4,
|
albumArtistStartIndex: 0,
|
||||||
albumStartIndex: 0,
|
albumLimit: 4,
|
||||||
query: debouncedQuery,
|
albumStartIndex: 0,
|
||||||
songLimit: 4,
|
query: debouncedQuery,
|
||||||
songStartIndex: 0,
|
songLimit: 4,
|
||||||
},
|
songStartIndex: 0,
|
||||||
serverId: server?.id,
|
},
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const showAlbumGroup = isHome && Boolean(query && data && data?.albums?.length > 0);
|
const showAlbumGroup = isHome && Boolean(query && data && data?.albums?.length > 0);
|
||||||
const showArtistGroup = isHome && Boolean(query && data && data?.albumArtists?.length > 0);
|
const showArtistGroup = isHome && Boolean(query && data && data?.albumArtists?.length > 0);
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
import { SearchQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
export const useSearch = (args: QueryHookArgs<SearchQuery>) => {
|
|
||||||
const { options, query, serverId } = args;
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!serverId,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.search({
|
|
||||||
apiClientProps: {
|
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
},
|
|
||||||
query,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.search.list(serverId || '', query),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
63
src/renderer/features/shared/api/shared-api.ts
Normal file
63
src/renderer/features/shared/api/shared-api.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '/@/renderer/api';
|
||||||
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
|
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { getServerById } from '/@/renderer/store';
|
||||||
|
import { MusicFolderListQuery, TagQuery, UserListQuery } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
export const sharedQueries = {
|
||||||
|
musicFolders: (args: QueryHookArgs<MusicFolderListQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
const server = getServerById(args.serverId);
|
||||||
|
if (!server) throw new Error('Server not found');
|
||||||
|
return api.controller.getMusicFolderList({ apiClientProps: { server, signal } });
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.musicFolders.list(args.serverId || ''),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
roles: (args: QueryHookArgs<object>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
const server = getServerById(args.serverId);
|
||||||
|
if (!server) throw new Error('Server not found');
|
||||||
|
return api.controller.getRoles({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.roles.list(args.serverId || ''),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
tags: (args: QueryHookArgs<TagQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
gcTime: 1000 * 60,
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
const server = getServerById(args.serverId);
|
||||||
|
if (!server) throw new Error('Server not found');
|
||||||
|
return api.controller.getTags({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.tags.list(args.serverId || '', args.query.type),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
users: (args: QueryHookArgs<UserListQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
const server = getServerById(args.serverId);
|
||||||
|
if (!server) throw new Error('Server not found');
|
||||||
|
return api.controller.getUserList({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.users.list(args.serverId || '', args.query),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
import { MusicFolderListQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
export const useMusicFolders = (args: QueryHookArgs<MusicFolderListQuery>) => {
|
|
||||||
const { options, serverId } = args || {};
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
const query = useQuery({
|
|
||||||
enabled: !!server,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getMusicFolderList({ apiClientProps: { server, signal } });
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.musicFolders.list(server?.id || ''),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
|
|
||||||
return query;
|
|
||||||
};
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { closeAllModals, openModal } from '@mantine/modals';
|
import { closeAllModals, openModal } from '@mantine/modals';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { MouseEvent, useCallback, useMemo, useState } from 'react';
|
import { MouseEvent, useCallback, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
@ -8,7 +9,8 @@ import { Link } from 'react-router-dom';
|
||||||
import styles from './sidebar-playlist-list.module.css';
|
import styles from './sidebar-playlist-list.module.css';
|
||||||
|
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { CreatePlaylistForm, usePlaylistList } from '/@/renderer/features/playlists';
|
import { CreatePlaylistForm } from '/@/renderer/features/playlists';
|
||||||
|
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||||
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
|
import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
|
|
@ -142,14 +144,16 @@ export const SidebarPlaylistList = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
|
|
||||||
const playlistsQuery = usePlaylistList({
|
const playlistsQuery = useQuery(
|
||||||
query: {
|
playlistsQueries.list({
|
||||||
sortBy: PlaylistListSort.NAME,
|
query: {
|
||||||
sortOrder: SortOrder.ASC,
|
sortBy: PlaylistListSort.NAME,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.ASC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId: server?.id,
|
},
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const handlePlayPlaylist = useCallback(
|
const handlePlayPlaylist = useCallback(
|
||||||
(id: string, playType: Play) => {
|
(id: string, playType: Play) => {
|
||||||
|
|
@ -258,14 +262,16 @@ export const SidebarSharedPlaylistList = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
|
|
||||||
const playlistsQuery = usePlaylistList({
|
const playlistsQuery = useQuery(
|
||||||
query: {
|
playlistsQueries.list({
|
||||||
sortBy: PlaylistListSort.NAME,
|
query: {
|
||||||
sortOrder: SortOrder.ASC,
|
sortBy: PlaylistListSort.NAME,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.ASC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId: server?.id,
|
},
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const handlePlayPlaylist = useCallback(
|
const handlePlayPlaylist = useCallback(
|
||||||
(id: string, playType: Play) => {
|
(id: string, playType: Play) => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { RowDoubleClickedEvent } from '@ag-grid-community/core';
|
import { RowDoubleClickedEvent } from '@ag-grid-community/core';
|
||||||
import { AgGridReact } from '@ag-grid-community/react';
|
import { AgGridReact } from '@ag-grid-community/react';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
|
||||||
|
|
@ -9,7 +10,7 @@ import { ErrorFallback } from '/@/renderer/features/action-required';
|
||||||
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
|
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
|
||||||
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
||||||
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
|
import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add';
|
||||||
import { useSimilarSongs } from '/@/renderer/features/similar-songs/queries/similar-song-queries';
|
import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
|
||||||
import { usePlayButtonBehavior, useTableSettings } from '/@/renderer/store';
|
import { usePlayButtonBehavior, useTableSettings } from '/@/renderer/store';
|
||||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||||
import { LibraryItem, Song } from '/@/shared/types/domain-types';
|
import { LibraryItem, Song } from '/@/shared/types/domain-types';
|
||||||
|
|
@ -26,14 +27,19 @@ export const SimilarSongsList = ({ count, fullScreen, song }: SimilarSongsListPr
|
||||||
const handlePlayQueueAdd = useHandlePlayQueueAdd();
|
const handlePlayQueueAdd = useHandlePlayQueueAdd();
|
||||||
const playButtonBehavior = usePlayButtonBehavior();
|
const playButtonBehavior = usePlayButtonBehavior();
|
||||||
|
|
||||||
const songQuery = useSimilarSongs({
|
const songQuery = useQuery(
|
||||||
options: {
|
songsQueries.similar({
|
||||||
cacheTime: 1000 * 60 * 2,
|
options: {
|
||||||
staleTime: 1000 * 60 * 1,
|
gcTime: 1000 * 60 * 2,
|
||||||
},
|
},
|
||||||
query: { albumArtistIds: song.albumArtists.map((art) => art.id), count, songId: song.id },
|
query: {
|
||||||
serverId: song?.serverId,
|
albumArtistIds: song.albumArtists.map((art) => art.id),
|
||||||
});
|
count,
|
||||||
|
songId: song.id,
|
||||||
|
},
|
||||||
|
serverId: song?.serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const columnDefs = useMemo(
|
const columnDefs = useMemo(
|
||||||
() => getColumnDefs(tableConfig.columns, false, 'generic'),
|
() => getColumnDefs(tableConfig.columns, false, 'generic'),
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
import { SimilarSongsQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
export const useSimilarSongs = (args: QueryHookArgs<SimilarSongsQuery>) => {
|
|
||||||
const { options, query, serverId } = args || {};
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!server,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
|
|
||||||
return api.controller.getSimilarSongs({
|
|
||||||
apiClientProps: { server, signal },
|
|
||||||
query: {
|
|
||||||
albumArtistIds: query.albumArtistIds,
|
|
||||||
count: query.count ?? 50,
|
|
||||||
songId: query.songId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.songs.similar(server?.id || '', query),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
60
src/renderer/features/songs/api/songs-api.ts
Normal file
60
src/renderer/features/songs/api/songs-api.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { api } from '/@/renderer/api';
|
||||||
|
import { controller } from '/@/renderer/api/controller';
|
||||||
|
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
|
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||||
|
import { getServerById } from '/@/renderer/store';
|
||||||
|
import { SimilarSongsQuery, SongListQuery } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
export const songsQueries = {
|
||||||
|
list: (args: QueryHookArgs<SongListQuery>, imageSize?: number) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
const server = getServerById(args.serverId);
|
||||||
|
if (!server) throw new Error('Server not found');
|
||||||
|
return controller.getSongList({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query: { ...args.query, imageSize },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.songs.list(args.serverId || '', { ...args.query, imageSize }),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
listCount: (args: QueryHookArgs<SongListQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
const server = getServerById(args.serverId);
|
||||||
|
if (!server) throw new Error('Server not found');
|
||||||
|
return api.controller.getSongListCount({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query: args.query,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.songs.count(
|
||||||
|
args.serverId || '',
|
||||||
|
Object.keys(args.query).length === 0 ? undefined : args.query,
|
||||||
|
),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
similar: (args: QueryHookArgs<SimilarSongsQuery>) => {
|
||||||
|
return queryOptions({
|
||||||
|
queryFn: ({ signal }) => {
|
||||||
|
const server = getServerById(args.serverId);
|
||||||
|
if (!server) throw new Error('Server not found');
|
||||||
|
return api.controller.getSimilarSongs({
|
||||||
|
apiClientProps: { server, signal },
|
||||||
|
query: {
|
||||||
|
albumArtistIds: args.query.albumArtistIds,
|
||||||
|
count: args.query.count ?? 50,
|
||||||
|
songId: args.query.songId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
queryKey: queryKeys.songs.similar(args.serverId || '', args.query),
|
||||||
|
...args.options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||||
import { useGenreList } from '/@/renderer/features/genres';
|
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||||
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
|
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||||
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
|
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
|
||||||
import { Divider } from '/@/shared/components/divider/divider';
|
import { Divider } from '/@/shared/components/divider/divider';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
|
|
@ -18,7 +19,7 @@ interface JellyfinSongFiltersProps {
|
||||||
customFilters?: Partial<SongListFilter>;
|
customFilters?: Partial<SongListFilter>;
|
||||||
onFilterChange: (filters: SongListFilter) => void;
|
onFilterChange: (filters: SongListFilter) => void;
|
||||||
pageKey: string;
|
pageKey: string;
|
||||||
serverId?: string;
|
serverId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const JellyfinSongFilters = ({
|
export const JellyfinSongFilters = ({
|
||||||
|
|
@ -35,15 +36,17 @@ export const JellyfinSongFilters = ({
|
||||||
|
|
||||||
// Despite the fact that getTags returns genres, it only returns genre names.
|
// Despite the fact that getTags returns genres, it only returns genre names.
|
||||||
// We prefer using IDs, hence the double query
|
// We prefer using IDs, hence the double query
|
||||||
const genreListQuery = useGenreList({
|
const genreListQuery = useQuery(
|
||||||
query: {
|
genresQueries.list({
|
||||||
musicFolderId: filter?.musicFolderId,
|
query: {
|
||||||
sortBy: GenreListSort.NAME,
|
musicFolderId: filter?.musicFolderId,
|
||||||
sortOrder: SortOrder.ASC,
|
sortBy: GenreListSort.NAME,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.ASC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId,
|
},
|
||||||
});
|
serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const genreList = useMemo(() => {
|
const genreList = useMemo(() => {
|
||||||
if (!genreListQuery?.data) return [];
|
if (!genreListQuery?.data) return [];
|
||||||
|
|
@ -53,13 +56,15 @@ export const JellyfinSongFilters = ({
|
||||||
}));
|
}));
|
||||||
}, [genreListQuery.data]);
|
}, [genreListQuery.data]);
|
||||||
|
|
||||||
const tagsQuery = useTagList({
|
const tagsQuery = useQuery(
|
||||||
query: {
|
sharedQueries.tags({
|
||||||
folder: filter?.musicFolderId,
|
query: {
|
||||||
type: LibraryItem.SONG,
|
folder: filter?.musicFolderId,
|
||||||
},
|
type: LibraryItem.SONG,
|
||||||
serverId,
|
},
|
||||||
});
|
serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const selectedGenres = useMemo(() => {
|
const selectedGenres = useMemo(() => {
|
||||||
return filter?._custom?.jellyfin?.GenreIds?.split(',');
|
return filter?._custom?.jellyfin?.GenreIds?.split(',');
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
@ -6,8 +7,8 @@ import {
|
||||||
MultiSelectWithInvalidData,
|
MultiSelectWithInvalidData,
|
||||||
SelectWithInvalidData,
|
SelectWithInvalidData,
|
||||||
} from '/@/renderer/components/select-with-invalid-data';
|
} from '/@/renderer/components/select-with-invalid-data';
|
||||||
import { useGenreList } from '/@/renderer/features/genres';
|
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||||
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
|
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||||
import {
|
import {
|
||||||
getServerById,
|
getServerById,
|
||||||
SongListFilter,
|
SongListFilter,
|
||||||
|
|
@ -29,7 +30,7 @@ interface NavidromeSongFiltersProps {
|
||||||
customFilters?: Partial<SongListFilter>;
|
customFilters?: Partial<SongListFilter>;
|
||||||
onFilterChange: (filters: SongListFilter) => void;
|
onFilterChange: (filters: SongListFilter) => void;
|
||||||
pageKey: string;
|
pageKey: string;
|
||||||
serverId?: string;
|
serverId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NavidromeSongFilters = ({
|
export const NavidromeSongFilters = ({
|
||||||
|
|
@ -45,21 +46,25 @@ export const NavidromeSongFilters = ({
|
||||||
|
|
||||||
const isGenrePage = customFilters?.genreIds !== undefined;
|
const isGenrePage = customFilters?.genreIds !== undefined;
|
||||||
|
|
||||||
const genreListQuery = useGenreList({
|
const genreListQuery = useQuery(
|
||||||
query: {
|
genresQueries.list({
|
||||||
sortBy: GenreListSort.NAME,
|
query: {
|
||||||
sortOrder: SortOrder.ASC,
|
sortBy: GenreListSort.NAME,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.ASC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId,
|
},
|
||||||
});
|
serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const tagsQuery = useTagList({
|
const tagsQuery = useQuery(
|
||||||
query: {
|
sharedQueries.tags({
|
||||||
type: LibraryItem.SONG,
|
query: {
|
||||||
},
|
type: LibraryItem.SONG,
|
||||||
serverId,
|
},
|
||||||
});
|
serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const genreList = useMemo(() => {
|
const genreList = useMemo(() => {
|
||||||
if (!genreListQuery?.data) return [];
|
if (!genreListQuery?.data) return [];
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import {
|
||||||
VirtualInfiniteGridRef,
|
VirtualInfiniteGridRef,
|
||||||
} from '/@/renderer/components/virtual-grid';
|
} from '/@/renderer/components/virtual-grid';
|
||||||
import { useListContext } from '/@/renderer/context/list-context';
|
import { useListContext } from '/@/renderer/context/list-context';
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add';
|
||||||
import { useHandleFavorite } from '/@/renderer/features/shared/hooks/use-handle-favorite';
|
import { useHandleFavorite } from '/@/renderer/features/shared/hooks/use-handle-favorite';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useCurrentServer, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
|
import { useCurrentServer, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
|
||||||
|
|
@ -26,7 +26,6 @@ import {
|
||||||
SongListSort,
|
SongListSort,
|
||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
import { CardRow, ListDisplayType } from '/@/shared/types/types';
|
import { CardRow, ListDisplayType } from '/@/shared/types/types';
|
||||||
|
|
||||||
interface SongListGridViewProps {
|
interface SongListGridViewProps {
|
||||||
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
|
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
|
||||||
itemCount?: number;
|
itemCount?: number;
|
||||||
|
|
@ -185,15 +184,17 @@ export const SongListGridView = ({ gridRef, itemCount }: SongListGridViewProps)
|
||||||
|
|
||||||
const queryKey = queryKeys.songs.list(server?.id || '', query, id);
|
const queryKey = queryKeys.songs.list(server?.id || '', query, id);
|
||||||
|
|
||||||
const songs = await queryClient.fetchQuery(queryKey, async ({ signal }) =>
|
const songs = await queryClient.fetchQuery({
|
||||||
controller.getSongList({
|
queryFn: async ({ signal }) =>
|
||||||
apiClientProps: {
|
controller.getSongList({
|
||||||
server,
|
apiClientProps: {
|
||||||
signal,
|
server,
|
||||||
},
|
signal,
|
||||||
query,
|
},
|
||||||
}),
|
query,
|
||||||
);
|
}),
|
||||||
|
queryKey,
|
||||||
|
});
|
||||||
|
|
||||||
return songs;
|
return songs;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
import { openModal } from '@mantine/modals';
|
import { openModal } from '@mantine/modals';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
import { MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
@ -10,7 +11,8 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
import { useListContext } from '/@/renderer/context/list-context';
|
import { useListContext } from '/@/renderer/context/list-context';
|
||||||
import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared';
|
import { OrderToggleButton } from '/@/renderer/features/shared';
|
||||||
|
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||||
import { FilterButton } from '/@/renderer/features/shared/components/filter-button';
|
import { FilterButton } from '/@/renderer/features/shared/components/filter-button';
|
||||||
import { FolderButton } from '/@/renderer/features/shared/components/folder-button';
|
import { FolderButton } from '/@/renderer/features/shared/components/folder-button';
|
||||||
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
|
|
@ -223,7 +225,9 @@ export const SongListHeaderFilters = ({
|
||||||
|
|
||||||
const cq = useContainerQuery();
|
const cq = useContainerQuery();
|
||||||
|
|
||||||
const musicFoldersQuery = useMusicFolders({ query: null, serverId: server?.id });
|
const musicFoldersQuery = useQuery(
|
||||||
|
sharedQueries.musicFolders({ query: null, serverId: server?.id }),
|
||||||
|
);
|
||||||
|
|
||||||
const sortByLabel =
|
const sortByLabel =
|
||||||
(server?.type &&
|
(server?.type &&
|
||||||
|
|
@ -407,7 +411,7 @@ export const SongListHeaderFilters = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
queryClient.invalidateQueries(queryKeys.songs.list(server?.id || ''));
|
queryClient.invalidateQueries({ queryKey: queryKeys.songs.list(server?.id || '') });
|
||||||
if (isGrid) {
|
if (isGrid) {
|
||||||
handleRefreshGrid(gridRef, filter);
|
handleRefreshGrid(gridRef, filter);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { ChangeEvent, useMemo } from 'react';
|
import { ChangeEvent, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { useGenreList } from '/@/renderer/features/genres';
|
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||||
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
|
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
|
||||||
import { Divider } from '/@/shared/components/divider/divider';
|
import { Divider } from '/@/shared/components/divider/divider';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
|
|
@ -16,7 +17,7 @@ interface SubsonicSongFiltersProps {
|
||||||
customFilters?: Partial<SongListFilter>;
|
customFilters?: Partial<SongListFilter>;
|
||||||
onFilterChange: (filters: SongListFilter) => void;
|
onFilterChange: (filters: SongListFilter) => void;
|
||||||
pageKey: string;
|
pageKey: string;
|
||||||
serverId?: string;
|
serverId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SubsonicSongFilters = ({
|
export const SubsonicSongFilters = ({
|
||||||
|
|
@ -31,14 +32,16 @@ export const SubsonicSongFilters = ({
|
||||||
|
|
||||||
const isGenrePage = customFilters?.genreIds !== undefined;
|
const isGenrePage = customFilters?.genreIds !== undefined;
|
||||||
|
|
||||||
const genreListQuery = useGenreList({
|
const genreListQuery = useQuery(
|
||||||
query: {
|
genresQueries.list({
|
||||||
sortBy: GenreListSort.NAME,
|
query: {
|
||||||
sortOrder: SortOrder.ASC,
|
sortBy: GenreListSort.NAME,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.ASC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId,
|
},
|
||||||
});
|
serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const genreList = useMemo(() => {
|
const genreList = useMemo(() => {
|
||||||
if (!genreListQuery?.data) return [];
|
if (!genreListQuery?.data) return [];
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import type { SongListQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const useSongListCount = (args: QueryHookArgs<SongListQuery>) => {
|
|
||||||
const { options, query, serverId } = args;
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!serverId,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getSongListCount({
|
|
||||||
apiClientProps: {
|
|
||||||
server,
|
|
||||||
signal,
|
|
||||||
},
|
|
||||||
query,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.songs.count(
|
|
||||||
serverId || '',
|
|
||||||
Object.keys(query).length === 0 ? undefined : query,
|
|
||||||
),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import type { SongListQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { controller } from '/@/renderer/api/controller';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const useSongList = (args: QueryHookArgs<SongListQuery>, imageSize?: number) => {
|
|
||||||
const { options, query, serverId } = args || {};
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!server?.id,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return controller.getSongList({
|
|
||||||
apiClientProps: { server, signal },
|
|
||||||
query: { ...query, imageSize },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.songs.list(server?.id || '', { ...query, imageSize }),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { useCallback, useMemo, useRef } from 'react';
|
import { useCallback, useMemo, useRef } from 'react';
|
||||||
import { useParams, useSearchParams } from 'react-router-dom';
|
import { useParams, useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||||
import { ListContext } from '/@/renderer/context/list-context';
|
import { ListContext } from '/@/renderer/context/list-context';
|
||||||
import { useGenreList } from '/@/renderer/features/genres';
|
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||||
import { AnimatedPage } from '/@/renderer/features/shared';
|
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||||
|
import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
|
||||||
import { SongListContent } from '/@/renderer/features/songs/components/song-list-content';
|
import { SongListContent } from '/@/renderer/features/songs/components/song-list-content';
|
||||||
import { SongListHeader } from '/@/renderer/features/songs/components/song-list-header';
|
import { SongListHeader } from '/@/renderer/features/songs/components/song-list-header';
|
||||||
import { useSongListCount } from '/@/renderer/features/songs/queries/song-list-count-query';
|
|
||||||
import { useCurrentServer, useListFilterByKey } from '/@/renderer/store';
|
import { useCurrentServer, useListFilterByKey } from '/@/renderer/store';
|
||||||
import { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/shared/types/domain-types';
|
import { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/shared/types/domain-types';
|
||||||
import { Play } from '/@/shared/types/types';
|
import { Play } from '/@/shared/types/types';
|
||||||
|
|
@ -46,18 +47,20 @@ const TrackListRoute = () => {
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
const genreList = useGenreList({
|
const genreList = useQuery(
|
||||||
options: {
|
genresQueries.list({
|
||||||
cacheTime: 1000 * 60 * 60,
|
options: {
|
||||||
enabled: !!genreId,
|
enabled: !!genreId,
|
||||||
},
|
gcTime: 1000 * 60 * 60,
|
||||||
query: {
|
},
|
||||||
sortBy: GenreListSort.NAME,
|
query: {
|
||||||
sortOrder: SortOrder.ASC,
|
sortBy: GenreListSort.NAME,
|
||||||
startIndex: 0,
|
sortOrder: SortOrder.ASC,
|
||||||
},
|
startIndex: 0,
|
||||||
serverId: server?.id,
|
},
|
||||||
});
|
serverId: server?.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const genreTitle = useMemo(() => {
|
const genreTitle = useMemo(() => {
|
||||||
if (!genreList.data) return '';
|
if (!genreList.data) return '';
|
||||||
|
|
@ -68,14 +71,15 @@ const TrackListRoute = () => {
|
||||||
return genre?.name;
|
return genre?.name;
|
||||||
}, [genreId, genreList.data]);
|
}, [genreId, genreList.data]);
|
||||||
|
|
||||||
const itemCountCheck = useSongListCount({
|
const itemCountCheck = useQuery(
|
||||||
options: {
|
songsQueries.listCount({
|
||||||
cacheTime: 1000 * 60,
|
options: {
|
||||||
staleTime: 1000 * 60,
|
gcTime: 1000 * 60,
|
||||||
},
|
},
|
||||||
query: songListFilter,
|
query: songListFilter,
|
||||||
serverId: server?.id,
|
serverId: server?.id,
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const itemCount = itemCountCheck.data === null ? undefined : itemCountCheck.data;
|
const itemCount = itemCountCheck.data === null ? undefined : itemCountCheck.data;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
import { hasFeature } from '/@/shared/api/utils';
|
|
||||||
import { TagQuery } from '/@/shared/types/domain-types';
|
|
||||||
import { ServerFeature } from '/@/shared/types/features-types';
|
|
||||||
|
|
||||||
export const useTagList = (args: QueryHookArgs<TagQuery>) => {
|
|
||||||
const { options, query, serverId } = args || {};
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!server && hasFeature(server, ServerFeature.TAGS),
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
return api.controller.getTags({ apiClientProps: { server, signal }, query });
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.tags.list(server?.id || '', query.type),
|
|
||||||
staleTime: 1000 * 60,
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
|
||||||
import type { UserListQuery } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
|
||||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
|
||||||
import { getServerById } from '/@/renderer/store';
|
|
||||||
|
|
||||||
export const useUserList = (args: QueryHookArgs<UserListQuery>) => {
|
|
||||||
const { options, query, serverId } = args || {};
|
|
||||||
const server = getServerById(serverId);
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
enabled: !!server,
|
|
||||||
queryFn: ({ signal }) => {
|
|
||||||
if (!server) throw new Error('Server not found');
|
|
||||||
api.controller.getUserList({ apiClientProps: { server, signal }, query });
|
|
||||||
},
|
|
||||||
queryKey: queryKeys.users.list(server?.id || '', query),
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import type {
|
import type {
|
||||||
DefaultOptions,
|
DefaultOptions,
|
||||||
|
QueryOptions,
|
||||||
UseInfiniteQueryOptions,
|
UseInfiniteQueryOptions,
|
||||||
UseMutationOptions,
|
UseMutationOptions,
|
||||||
UseQueryOptions,
|
UseQueryOptions,
|
||||||
|
|
@ -22,14 +23,11 @@ const queryConfig: DefaultOptions = {
|
||||||
retry: process.env.NODE_ENV === 'production',
|
retry: process.env.NODE_ENV === 'production',
|
||||||
},
|
},
|
||||||
queries: {
|
queries: {
|
||||||
cacheTime: 1000 * 60 * 3,
|
gcTime: 1000 * 60 * 3,
|
||||||
onError: (err) => {
|
|
||||||
console.error('react query error:', err);
|
|
||||||
},
|
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
retry: process.env.NODE_ENV === 'production',
|
retry: process.env.NODE_ENV === 'production',
|
||||||
staleTime: 1000 * 5,
|
staleTime: 1000 * 5,
|
||||||
useErrorBoundary: (error: any) => {
|
throwOnError: (error: any) => {
|
||||||
return error?.response?.status >= 500;
|
return error?.response?.status >= 500;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -40,23 +38,10 @@ export const queryClient = new QueryClient({
|
||||||
queryCache,
|
queryCache,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type InfiniteQueryOptions = {
|
export type InfiniteQueryHookArgs<T> = {
|
||||||
cacheTime?: UseInfiniteQueryOptions['cacheTime'];
|
options?: UseInfiniteQueryOptions;
|
||||||
enabled?: UseInfiniteQueryOptions['enabled'];
|
query: T;
|
||||||
keepPreviousData?: UseInfiniteQueryOptions['keepPreviousData'];
|
serverId: string | undefined;
|
||||||
meta?: UseInfiniteQueryOptions['meta'];
|
|
||||||
onError?: (err: any) => void;
|
|
||||||
onSettled?: any;
|
|
||||||
onSuccess?: any;
|
|
||||||
queryKey?: UseInfiniteQueryOptions['queryKey'];
|
|
||||||
refetchInterval?: number;
|
|
||||||
refetchIntervalInBackground?: UseInfiniteQueryOptions['refetchIntervalInBackground'];
|
|
||||||
refetchOnWindowFocus?: boolean;
|
|
||||||
retry?: UseInfiniteQueryOptions['retry'];
|
|
||||||
retryDelay?: UseInfiniteQueryOptions['retryDelay'];
|
|
||||||
staleTime?: UseInfiniteQueryOptions['staleTime'];
|
|
||||||
suspense?: UseInfiniteQueryOptions['suspense'];
|
|
||||||
useErrorBoundary?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MutationHookArgs = {
|
export type MutationHookArgs = {
|
||||||
|
|
@ -74,26 +59,34 @@ export type MutationOptions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QueryHookArgs<T> = {
|
export type QueryHookArgs<T> = {
|
||||||
options?: QueryOptions;
|
options?: UseQueryHookOptions;
|
||||||
query: T;
|
query: T;
|
||||||
serverId: string | undefined;
|
serverId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type QueryOptions = {
|
type UseQueryHookOptions = {
|
||||||
cacheTime?: UseQueryOptions['cacheTime'];
|
enabled?: boolean;
|
||||||
enabled?: UseQueryOptions['enabled'];
|
gcTime?: QueryOptions['gcTime'];
|
||||||
keepPreviousData?: UseQueryOptions['keepPreviousData'];
|
// initialData?: UseQueryOptions['initialData'];
|
||||||
|
// initialDataUpdatedAt?: UseQueryOptions['initialDataUpdatedAt'];
|
||||||
meta?: UseQueryOptions['meta'];
|
meta?: UseQueryOptions['meta'];
|
||||||
onError?: (err: any) => void;
|
networkMode?: UseQueryOptions['networkMode'];
|
||||||
onSettled?: any;
|
notifyOnChangeProps?: UseQueryOptions['notifyOnChangeProps'];
|
||||||
onSuccess?: any;
|
placeholderData?: (prev: any) => any;
|
||||||
|
// queryFn?: UseQueryOptions['queryFn'];
|
||||||
queryKey?: UseQueryOptions['queryKey'];
|
queryKey?: UseQueryOptions['queryKey'];
|
||||||
|
queryKeyHashFn?: UseQueryOptions['queryKeyHashFn'];
|
||||||
refetchInterval?: number;
|
refetchInterval?: number;
|
||||||
refetchIntervalInBackground?: UseQueryOptions['refetchIntervalInBackground'];
|
refetchIntervalInBackground?: UseQueryOptions['refetchIntervalInBackground'];
|
||||||
|
refetchOnMount?: boolean;
|
||||||
|
refetchOnReconnect?: boolean;
|
||||||
refetchOnWindowFocus?: boolean;
|
refetchOnWindowFocus?: boolean;
|
||||||
retry?: UseQueryOptions['retry'];
|
retry?: UseQueryOptions['retry'];
|
||||||
retryDelay?: UseQueryOptions['retryDelay'];
|
retryDelay?: UseQueryOptions['retryDelay'];
|
||||||
staleTime?: UseQueryOptions['staleTime'];
|
retryOnMount?: UseQueryOptions['retryOnMount'];
|
||||||
suspense?: UseQueryOptions['suspense'];
|
// select?: UseQueryOptions['select'];
|
||||||
useErrorBoundary?: boolean;
|
staleTime?: number;
|
||||||
|
structuralSharing?: UseQueryOptions['structuralSharing'];
|
||||||
|
subscribed?: UseQueryOptions['subscribed'];
|
||||||
|
throwOnError?: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ createRoot(document.getElementById('root')!).render(
|
||||||
persistOptions={{
|
persistOptions={{
|
||||||
buster: 'feishin',
|
buster: 'feishin',
|
||||||
dehydrateOptions: {
|
dehydrateOptions: {
|
||||||
dehydrateQueries: true,
|
|
||||||
shouldDehydrateQuery: (query) => {
|
shouldDehydrateQuery: (query) => {
|
||||||
const isSuccess = query.state.status === 'success';
|
const isSuccess = query.state.status === 'success';
|
||||||
const isLyricsQueryKey =
|
const isLyricsQueryKey =
|
||||||
|
|
@ -45,7 +44,7 @@ createRoot(document.getElementById('root')!).render(
|
||||||
hydrateOptions: {
|
hydrateOptions: {
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
cacheTime: Infinity,
|
gcTime: Infinity,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,8 @@ export const useAuthStore = createWithEqualityFn<AuthSlice>()(
|
||||||
|
|
||||||
export const useCurrentServerId = () => useAuthStore((state) => state.currentServer)?.id || '';
|
export const useCurrentServerId = () => useAuthStore((state) => state.currentServer)?.id || '';
|
||||||
|
|
||||||
export const useCurrentServer = () => useAuthStore((state) => state.currentServer);
|
export const useCurrentServer = () =>
|
||||||
|
useAuthStore((state) => state.currentServer) as ServerListItem;
|
||||||
|
|
||||||
export const useServerList = () => useAuthStore((state) => state.serverList);
|
export const useServerList = () => useAuthStore((state) => state.serverList);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue