Compare commits

...

19 Commits

Author SHA1 Message Date
jeffvli 0c7c0e488d temp 2 2025-09-07 12:24:21 -07:00
jeffvli a7430dae31 temp 2025-09-07 12:24:21 -07:00
jeffvli 98e8bda45d progress on subsonic api 2025-09-07 12:24:20 -07:00
jeffvli 96221c8fa7 fix various imports 2025-09-07 12:23:01 -07:00
jeffvli 8c7cac369a add experimental request logger 2025-09-07 12:21:07 -07:00
jeffvli f1c011f677 temp progress 2025-09-07 12:21:06 -07:00
jeffvli 351464c52d add missing i18n path resolution to main/preload 2025-09-07 12:21:06 -07:00
jeffvli da8ba31a88 scaffold new OS controller 2025-09-07 12:21:06 -07:00
jeffvli d8a8880e48 add console logger utility 2025-09-07 12:21:06 -07:00
jeffvli fe36535aee improve domain types to better match OS, update normalizer functions 2025-09-07 12:21:06 -07:00
jeffvli 67eec51e5f add new date format utility function 2025-09-07 12:21:06 -07:00
jeffvli a7f21db563 add new api controller, rework and rename types 2025-09-07 12:21:06 -07:00
jeffvli 6c360c3c19 add autogen opensubsonic schema 2025-09-07 12:21:06 -07:00
jeffvli 4d7779eae1 rename preload types file 2025-09-07 12:18:33 -07:00
jeffvli 71b307e4a6 add new api controller types 2025-09-07 12:18:33 -07:00
jeffvli a3a67d20a9 fix imports 2025-09-07 12:18:33 -07:00
jeffvli 1c22461ee4 move all domain types to separate files 2025-09-07 12:18:33 -07:00
jeffvli 7785874605 add i18n path to node tsconfig 2025-09-07 12:18:33 -07:00
jeffvli 9147b041f3 begin reorganizing domain types 2025-09-07 12:18:33 -07:00
231 changed files with 15051 additions and 3419 deletions
+2
View File
@@ -30,6 +30,7 @@ const config: UserConfig = {
], ],
resolve: { resolve: {
alias: { alias: {
'/@/i18n': resolve('src/i18n'),
'/@/main': resolve('src/main'), '/@/main': resolve('src/main'),
'/@/shared': resolve('src/shared'), '/@/shared': resolve('src/shared'),
}, },
@@ -39,6 +40,7 @@ const config: UserConfig = {
plugins: [externalizeDepsPlugin()], plugins: [externalizeDepsPlugin()],
resolve: { resolve: {
alias: { alias: {
'/@/i18n': resolve('src/i18n'),
'/@/preload': resolve('src/preload'), '/@/preload': resolve('src/preload'),
'/@/shared': resolve('src/shared'), '/@/shared': resolve('src/shared'),
}, },
+1 -1
View File
@@ -6,7 +6,7 @@ import eslintPluginReactHooks from 'eslint-plugin-react-hooks';
import eslintPluginReactRefresh from 'eslint-plugin-react-refresh'; import eslintPluginReactRefresh from 'eslint-plugin-react-refresh';
export default tseslint.config( export default tseslint.config(
{ ignores: ['**/node_modules', '**/dist', '**/out'] }, { ignores: ['**/node_modules', '**/dist', '**/out', '**/*-schema.d.ts'] },
tseslint.configs.recommended, tseslint.configs.recommended,
perfectionist.configs['recommended-natural'], perfectionist.configs['recommended-natural'],
eslintPluginReact.configs.flat.recommended, eslintPluginReact.configs.flat.recommended,
+8 -3
View File
@@ -27,6 +27,8 @@
"dev": "electron-vite dev", "dev": "electron-vite dev",
"dev:remote": "vite dev --config remote.vite.config.ts", "dev:remote": "vite dev --config remote.vite.config.ts",
"dev:watch": "electron-vite dev --watch", "dev:watch": "electron-vite dev --watch",
"generate-api": "pnpm run generate-api:subsonic",
"generate-api:subsonic": "openapi-typescript https://opensubsonic.netlify.app/docs/openapi/openapi.json -o ./src/shared/api/subsonic/subsonic-schema.d.ts",
"i18next": "i18next -c src/i18n/i18next-parser.config.js", "i18next": "i18next -c src/i18n/i18next-parser.config.js",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"lint": "pnpm run lint-code && pnpm run lint-styles", "lint": "pnpm run lint-code && pnpm run lint-styles",
@@ -71,9 +73,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.83.0",
"@tanstack/react-query-devtools": "^4.32.1", "@tanstack/react-query-devtools": "^5.83.0",
"@tanstack/react-query-persist-client": "^4.32.1", "@tanstack/react-query-persist-client": "^5.83.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",
@@ -103,6 +105,7 @@
"mpris-service": "^2.1.2", "mpris-service": "^2.1.2",
"nanoid": "^3.3.3", "nanoid": "^3.3.3",
"node-mpv": "github:jeffvli/Node-MPV#32b4d64395289ad710c41d481d2707a7acfc228f", "node-mpv": "github:jeffvli/Node-MPV#32b4d64395289ad710c41d481d2707a7acfc228f",
"openapi-fetch": "^0.14.0",
"overlayscrollbars": "^2.11.1", "overlayscrollbars": "^2.11.1",
"overlayscrollbars-react": "^0.5.6", "overlayscrollbars-react": "^0.5.6",
"qs": "^6.14.0", "qs": "^6.14.0",
@@ -135,6 +138,7 @@
"@types/lodash": "^4.17.18", "@types/lodash": "^4.17.18",
"@types/md5": "^2.3.5", "@types/md5": "^2.3.5",
"@types/node": "^22.15.32", "@types/node": "^22.15.32",
"@types/qs": "^6.14.0",
"@types/react": "^18.3.23", "@types/react": "^18.3.23",
"@types/react-dom": "^18.3.7", "@types/react-dom": "^18.3.7",
"@types/react-window": "^1.8.5", "@types/react-window": "^1.8.5",
@@ -155,6 +159,7 @@
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19", "eslint-plugin-react-refresh": "^0.4.19",
"i18next-parser": "^9.0.2", "i18next-parser": "^9.0.2",
"openapi-typescript": "^7.8.0",
"postcss-preset-mantine": "^1.17.0", "postcss-preset-mantine": "^1.17.0",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"prettier-plugin-packagejson": "^2.5.14", "prettier-plugin-packagejson": "^2.5.14",
+226 -122
View File
@@ -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.83.0
version: 4.36.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) version: 5.83.0(react@19.1.0)
'@tanstack/react-query-devtools': '@tanstack/react-query-devtools':
specifier: ^4.32.1 specifier: ^5.83.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.83.0(@tanstack/react-query@5.83.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.83.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.83.0(@tanstack/react-query@5.83.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)
@@ -155,6 +155,9 @@ importers:
node-mpv: node-mpv:
specifier: github:jeffvli/Node-MPV#32b4d64395289ad710c41d481d2707a7acfc228f specifier: github:jeffvli/Node-MPV#32b4d64395289ad710c41d481d2707a7acfc228f
version: https://codeload.github.com/jeffvli/Node-MPV/tar.gz/32b4d64395289ad710c41d481d2707a7acfc228f version: https://codeload.github.com/jeffvli/Node-MPV/tar.gz/32b4d64395289ad710c41d481d2707a7acfc228f
openapi-fetch:
specifier: ^0.14.0
version: 0.14.0
overlayscrollbars: overlayscrollbars:
specifier: ^2.11.1 specifier: ^2.11.1
version: 2.11.3 version: 2.11.3
@@ -246,6 +249,9 @@ importers:
'@types/node': '@types/node':
specifier: ^22.15.32 specifier: ^22.15.32
version: 22.15.32 version: 22.15.32
'@types/qs':
specifier: ^6.14.0
version: 6.14.0
'@types/react': '@types/react':
specifier: ^18.3.23 specifier: ^18.3.23
version: 18.3.23 version: 18.3.23
@@ -266,7 +272,7 @@ importers:
version: 8.18.1 version: 8.18.1
'@vitejs/plugin-react': '@vitejs/plugin-react':
specifier: ^4.3.4 specifier: ^4.3.4
version: 4.5.0(vite@6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)) version: 4.5.0(vite@6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)(yaml@2.8.0))
concurrently: concurrently:
specifier: ^7.1.0 specifier: ^7.1.0
version: 7.6.0 version: 7.6.0
@@ -284,7 +290,7 @@ importers:
version: 3.2.1 version: 3.2.1
electron-vite: electron-vite:
specifier: ^3.1.0 specifier: ^3.1.0
version: 3.1.0(vite@6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)) version: 3.1.0(vite@6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)(yaml@2.8.0))
eslint: eslint:
specifier: ^9.24.0 specifier: ^9.24.0
version: 9.27.0 version: 9.27.0
@@ -306,6 +312,9 @@ importers:
i18next-parser: i18next-parser:
specifier: ^9.0.2 specifier: ^9.0.2
version: 9.3.0 version: 9.3.0
openapi-typescript:
specifier: ^7.8.0
version: 7.8.0(typescript@5.8.3)
postcss-preset-mantine: postcss-preset-mantine:
specifier: ^1.17.0 specifier: ^1.17.0
version: 1.17.0(postcss@8.5.3) version: 1.17.0(postcss@8.5.3)
@@ -335,7 +344,7 @@ importers:
version: 5.8.3 version: 5.8.3
vite: vite:
specifier: ^6.3.5 specifier: ^6.3.5
version: 6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2) version: 6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)(yaml@2.8.0)
vite-plugin-conditional-import: vite-plugin-conditional-import:
specifier: ^0.1.7 specifier: ^0.1.7
version: 0.1.7 version: 0.1.7
@@ -344,7 +353,7 @@ importers:
version: 1.6.0 version: 1.6.0
vite-plugin-ejs: vite-plugin-ejs:
specifier: ^1.7.0 specifier: ^1.7.0
version: 1.7.0(vite@6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)) version: 1.7.0(vite@6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)(yaml@2.8.0))
packages: packages:
@@ -1040,6 +1049,16 @@ packages:
peerDependencies: peerDependencies:
react: ^16.8 || ^17.0 || ^18.0 react: ^16.8 || ^17.0 || ^18.0
'@redocly/ajv@8.11.2':
resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==}
'@redocly/config@0.22.2':
resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==}
'@redocly/openapi-core@1.34.3':
resolution: {integrity: sha512-3arRdUp1fNx55itnjKiUhO6t4Mf91TsrTIYINDNLAZPS0TPd5YpiXRctwjel0qqWoOOhjA34cZ3m4dksLDFUYg==}
engines: {node: '>=18.17.0', npm: '>=9.5.0'}
'@remix-run/router@1.23.0': '@remix-run/router@1.23.0':
resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==} resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
@@ -1163,39 +1182,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.83.0':
resolution: {integrity: sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==} resolution: {integrity: sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA==}
engines: {node: '>=12'}
'@tanstack/query-core@4.36.1': '@tanstack/query-devtools@5.81.2':
resolution: {integrity: sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==} resolution: {integrity: sha512-jCeJcDCwKfoyyBXjXe9+Lo8aTkavygHHsUHAlxQKKaDeyT0qyQNLKl7+UyqYH2dDF6UN/14873IPBHchcsU+Zg==}
'@tanstack/query-persist-client-core@4.36.1': '@tanstack/query-persist-client-core@5.83.0':
resolution: {integrity: sha512-eocgCeI7D7TRv1IUUBMfVwOI0wdSmMkBIbkKhqEdTrnUHUQEeOaYac8oeZk2cumAWJdycu6P/wB+WqGynTnzXg==} resolution: {integrity: sha512-hdKgHkr1MYnwZX+QHj/9JjXZx9gL2RUCD5xSX0EHZiqUQhMk4Gcryq9xosn8LmYRMlhkjk7n9uV+X4UXRvgoIg==}
'@tanstack/react-query-devtools@4.36.1': '@tanstack/react-query-devtools@5.83.0':
resolution: {integrity: sha512-WYku83CKP3OevnYSG8Y/QO9g0rT75v1om5IvcWUwiUZJ4LanYGLVCZ8TdFG5jfsq4Ej/lu2wwDAULEUnRIMBSw==} resolution: {integrity: sha512-yfp8Uqd3I1jgx8gl0lxbSSESu5y4MO2ThOPBnGNTYs0P+ZFu+E9g5IdOngyUGuo6Uz6Qa7p9TLdZEX3ntik2fQ==}
peerDependencies: peerDependencies:
'@tanstack/react-query': ^4.36.1 '@tanstack/react-query': ^5.83.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.83.0':
resolution: {integrity: sha512-32I5b9aAu4NCiXZ7Te/KEQLfHbYeTNriVPrKYcvEThnZ9tlW01vLcSoxpUIsMYRsembvJUUAkzYBAiZHLOd6pQ==} resolution: {integrity: sha512-uEqJnSbqlvzlhYJ+RU+2c2DmbbT7cw6eFjiewEXZFXaSGWNjvUG02LePrwL8cdLlRQFcZKas30IdckboOoVg9Q==}
peerDependencies: peerDependencies:
'@tanstack/react-query': ^4.36.1 '@tanstack/react-query': ^5.83.0
react: ^18 || ^19
'@tanstack/react-query@4.36.1': '@tanstack/react-query@5.83.0':
resolution: {integrity: sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==} resolution: {integrity: sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ==}
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==}
@@ -1269,6 +1280,9 @@ packages:
'@types/prop-types@15.7.14': '@types/prop-types@15.7.14':
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
'@types/qs@6.14.0':
resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==}
'@types/react-dom@18.3.7': '@types/react-dom@18.3.7':
resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
peerDependencies: peerDependencies:
@@ -1428,6 +1442,10 @@ packages:
ajv@8.17.1: ajv@8.17.1:
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
ansi-regex@5.0.1: ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1674,6 +1692,9 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'} engines: {node: '>=10'}
change-case@5.4.4:
resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==}
charenc@0.0.2: charenc@0.0.2:
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
@@ -1752,6 +1773,9 @@ packages:
colord@2.9.3: colord@2.9.3:
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
colorette@1.4.0:
resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}
colorjs.io@0.5.2: colorjs.io@0.5.2:
resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==} resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==}
@@ -1800,10 +1824,6 @@ 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:
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
engines: {node: '>=12.13'}
core-util-is@1.0.2: core-util-is@1.0.2:
resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
@@ -2701,6 +2721,10 @@ packages:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
engines: {node: '>=8'} engines: {node: '>=8'}
index-to-position@1.1.0:
resolution: {integrity: sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==}
engines: {node: '>=18'}
infer-owner@1.0.4: infer-owner@1.0.4:
resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==}
@@ -2870,10 +2894,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==}
@@ -2903,6 +2923,10 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
js-levenshtein@1.1.6:
resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
engines: {node: '>=0.10.0'}
js-tokens@4.0.0: js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -3339,6 +3363,18 @@ packages:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
engines: {node: '>=6'} engines: {node: '>=6'}
openapi-fetch@0.14.0:
resolution: {integrity: sha512-PshIdm1NgdLvb05zp8LqRQMNSKzIlPkyMxYFxwyHR+UlKD4t2nUjkDhNxeRbhRSEd3x5EUNh2w5sJYwkhOH4fg==}
openapi-typescript-helpers@0.0.15:
resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==}
openapi-typescript@7.8.0:
resolution: {integrity: sha512-1EeVWmDzi16A+siQlo/SwSGIT7HwaFAVjvMA7/jG5HMLSnrUOzPL7uSTRZZa4v/LCRxHTApHKtNY6glApEoiUQ==}
hasBin: true
peerDependencies:
typescript: ^5.x
optionator@0.9.4: optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -3402,6 +3438,10 @@ packages:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'} engines: {node: '>=8'}
parse-json@8.3.0:
resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==}
engines: {node: '>=18'}
parse5-htmlparser2-tree-adapter@7.1.0: parse5-htmlparser2-tree-adapter@7.1.0:
resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}
@@ -3470,6 +3510,10 @@ packages:
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
engines: {node: '>=10.4.0'} engines: {node: '>=10.4.0'}
pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
possible-typed-array-names@1.1.0: possible-typed-array-names@1.1.0:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -3797,9 +3841,6 @@ packages:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
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==}
@@ -4288,9 +4329,9 @@ 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: supports-color@10.0.0:
resolution: {integrity: sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==} resolution: {integrity: sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==}
engines: {node: '>=10'} engines: {node: '>=18'}
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==}
@@ -4489,6 +4530,9 @@ packages:
peerDependencies: peerDependencies:
browserslist: '>= 4.21.0' browserslist: '>= 4.21.0'
uri-js-replace@1.0.1:
resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==}
uri-js@4.4.1: uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@@ -4744,6 +4788,14 @@ packages:
yallist@4.0.0: yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
yaml-ast-parser@0.0.43:
resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==}
yaml@2.8.0:
resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==}
engines: {node: '>= 14.6'}
hasBin: true
yargs-parser@21.1.1: yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -4851,7 +4903,7 @@ snapshots:
'@babel/traverse': 7.27.1 '@babel/traverse': 7.27.1
'@babel/types': 7.27.1 '@babel/types': 7.27.1
convert-source-map: 2.0.0 convert-source-map: 2.0.0
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
gensync: 1.0.0-beta.2 gensync: 1.0.0-beta.2
json5: 2.2.3 json5: 2.2.3
semver: 6.3.1 semver: 6.3.1
@@ -4937,7 +4989,7 @@ snapshots:
'@babel/parser': 7.27.2 '@babel/parser': 7.27.2
'@babel/template': 7.27.2 '@babel/template': 7.27.2
'@babel/types': 7.27.1 '@babel/types': 7.27.1
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
globals: 11.12.0 globals: 11.12.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -5039,7 +5091,7 @@ snapshots:
'@electron/get@2.0.3': '@electron/get@2.0.3':
dependencies: dependencies:
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
env-paths: 2.2.1 env-paths: 2.2.1
fs-extra: 8.1.0 fs-extra: 8.1.0
got: 11.8.6 got: 11.8.6
@@ -5069,7 +5121,7 @@ snapshots:
'@electron/notarize@2.5.0': '@electron/notarize@2.5.0':
dependencies: dependencies:
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
fs-extra: 9.1.0 fs-extra: 9.1.0
promise-retry: 2.0.1 promise-retry: 2.0.1
transitivePeerDependencies: transitivePeerDependencies:
@@ -5078,7 +5130,7 @@ snapshots:
'@electron/osx-sign@1.3.1': '@electron/osx-sign@1.3.1':
dependencies: dependencies:
compare-version: 0.1.2 compare-version: 0.1.2
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
fs-extra: 10.1.0 fs-extra: 10.1.0
isbinaryfile: 4.0.10 isbinaryfile: 4.0.10
minimist: 1.2.8 minimist: 1.2.8
@@ -5091,7 +5143,7 @@ snapshots:
'@electron/node-gyp': https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2 '@electron/node-gyp': https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2
'@malept/cross-spawn-promise': 2.0.0 '@malept/cross-spawn-promise': 2.0.0
chalk: 4.1.2 chalk: 4.1.2
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
detect-libc: 2.0.4 detect-libc: 2.0.4
fs-extra: 10.1.0 fs-extra: 10.1.0
got: 11.8.6 got: 11.8.6
@@ -5110,7 +5162,7 @@ snapshots:
dependencies: dependencies:
'@electron/asar': 3.2.18 '@electron/asar': 3.2.18
'@malept/cross-spawn-promise': 2.0.0 '@malept/cross-spawn-promise': 2.0.0
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
dir-compare: 4.2.0 dir-compare: 4.2.0
fs-extra: 11.3.1 fs-extra: 11.3.1
minimatch: 9.0.5 minimatch: 9.0.5
@@ -5121,7 +5173,7 @@ snapshots:
'@electron/windows-sign@1.2.2': '@electron/windows-sign@1.2.2':
dependencies: dependencies:
cross-dirname: 0.1.0 cross-dirname: 0.1.0
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
fs-extra: 11.3.1 fs-extra: 11.3.1
minimist: 1.2.8 minimist: 1.2.8
postject: 1.0.0-alpha.6 postject: 1.0.0-alpha.6
@@ -5214,7 +5266,7 @@ snapshots:
'@eslint/config-array@0.20.0': '@eslint/config-array@0.20.0':
dependencies: dependencies:
'@eslint/object-schema': 2.1.6 '@eslint/object-schema': 2.1.6
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
minimatch: 3.1.2 minimatch: 3.1.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -5232,7 +5284,7 @@ snapshots:
'@eslint/eslintrc@3.3.1': '@eslint/eslintrc@3.3.1':
dependencies: dependencies:
ajv: 6.12.6 ajv: 6.12.6
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
espree: 10.3.0 espree: 10.3.0
globals: 14.0.0 globals: 14.0.0
ignore: 5.3.2 ignore: 5.3.2
@@ -5337,7 +5389,7 @@ snapshots:
'@malept/flatpak-bundler@0.4.0': '@malept/flatpak-bundler@0.4.0':
dependencies: dependencies:
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
fs-extra: 9.1.0 fs-extra: 9.1.0
lodash: 4.17.21 lodash: 4.17.21
tmp-promise: 3.0.3 tmp-promise: 3.0.3
@@ -5547,6 +5599,29 @@ snapshots:
'@babel/runtime': 7.27.1 '@babel/runtime': 7.27.1
react: 19.1.0 react: 19.1.0
'@redocly/ajv@8.11.2':
dependencies:
fast-deep-equal: 3.1.3
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
uri-js-replace: 1.0.1
'@redocly/config@0.22.2': {}
'@redocly/openapi-core@1.34.3(supports-color@10.0.0)':
dependencies:
'@redocly/ajv': 8.11.2
'@redocly/config': 0.22.2
colorette: 1.4.0
https-proxy-agent: 7.0.6(supports-color@10.0.0)
js-levenshtein: 1.1.6
js-yaml: 4.1.0
minimatch: 5.1.6
pluralize: 8.0.0
yaml-ast-parser: 0.0.43
transitivePeerDependencies:
- supports-color
'@remix-run/router@1.23.0': {} '@remix-run/router@1.23.0': {}
'@rolldown/pluginutils@1.0.0-beta.9': {} '@rolldown/pluginutils@1.0.0-beta.9': {}
@@ -5621,37 +5696,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.83.0': {}
dependencies:
remove-accents: 0.5.0
'@tanstack/query-core@4.36.1': {} '@tanstack/query-devtools@5.81.2': {}
'@tanstack/query-persist-client-core@4.36.1': '@tanstack/query-persist-client-core@5.83.0':
dependencies: dependencies:
'@tanstack/query-core': 4.36.1 '@tanstack/query-core': 5.83.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.83.0(@tanstack/react-query@5.83.0(react@19.1.0))(react@19.1.0)':
dependencies: dependencies:
'@tanstack/match-sorter-utils': 8.19.4 '@tanstack/query-devtools': 5.81.2
'@tanstack/react-query': 4.36.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tanstack/react-query': 5.83.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.83.0(@tanstack/react-query@5.83.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.83.0
'@tanstack/react-query': 4.36.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@tanstack/react-query': 5.83.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.83.0(react@19.1.0)':
'@tanstack/query-core': 4.36.1 dependencies:
'@tanstack/query-core': 5.83.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': {}
@@ -5732,6 +5800,8 @@ snapshots:
'@types/prop-types@15.7.14': {} '@types/prop-types@15.7.14': {}
'@types/qs@6.14.0': {}
'@types/react-dom@18.3.7(@types/react@18.3.23)': '@types/react-dom@18.3.7(@types/react@18.3.23)':
dependencies: dependencies:
'@types/react': 18.3.23 '@types/react': 18.3.23
@@ -5798,7 +5868,7 @@ snapshots:
'@typescript-eslint/types': 8.32.1 '@typescript-eslint/types': 8.32.1
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3) '@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
'@typescript-eslint/visitor-keys': 8.32.1 '@typescript-eslint/visitor-keys': 8.32.1
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
eslint: 9.27.0 eslint: 9.27.0
typescript: 5.8.3 typescript: 5.8.3
transitivePeerDependencies: transitivePeerDependencies:
@@ -5813,7 +5883,7 @@ snapshots:
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3) '@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3)
'@typescript-eslint/utils': 8.32.1(eslint@9.27.0)(typescript@5.8.3) '@typescript-eslint/utils': 8.32.1(eslint@9.27.0)(typescript@5.8.3)
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
eslint: 9.27.0 eslint: 9.27.0
ts-api-utils: 2.1.0(typescript@5.8.3) ts-api-utils: 2.1.0(typescript@5.8.3)
typescript: 5.8.3 typescript: 5.8.3
@@ -5826,7 +5896,7 @@ snapshots:
dependencies: dependencies:
'@typescript-eslint/types': 8.32.1 '@typescript-eslint/types': 8.32.1
'@typescript-eslint/visitor-keys': 8.32.1 '@typescript-eslint/visitor-keys': 8.32.1
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
fast-glob: 3.3.3 fast-glob: 3.3.3
is-glob: 4.0.3 is-glob: 4.0.3
minimatch: 9.0.5 minimatch: 9.0.5
@@ -5852,7 +5922,7 @@ snapshots:
'@typescript-eslint/types': 8.32.1 '@typescript-eslint/types': 8.32.1
eslint-visitor-keys: 4.2.0 eslint-visitor-keys: 4.2.0
'@vitejs/plugin-react@4.5.0(vite@6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2))': '@vitejs/plugin-react@4.5.0(vite@6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)(yaml@2.8.0))':
dependencies: dependencies:
'@babel/core': 7.27.1 '@babel/core': 7.27.1
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.1) '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.1)
@@ -5860,7 +5930,7 @@ snapshots:
'@rolldown/pluginutils': 1.0.0-beta.9 '@rolldown/pluginutils': 1.0.0-beta.9
'@types/babel__core': 7.20.5 '@types/babel__core': 7.20.5
react-refresh: 0.17.0 react-refresh: 0.17.0
vite: 6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2) vite: 6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)(yaml@2.8.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -5896,7 +5966,7 @@ snapshots:
agent-base@6.0.2: agent-base@6.0.2:
dependencies: dependencies:
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -5933,6 +6003,8 @@ snapshots:
json-schema-traverse: 1.0.0 json-schema-traverse: 1.0.0
require-from-string: 2.0.2 require-from-string: 2.0.2
ansi-colors@4.1.3: {}
ansi-regex@5.0.1: {} ansi-regex@5.0.1: {}
ansi-regex@6.1.0: {} ansi-regex@6.1.0: {}
@@ -5966,7 +6038,7 @@ snapshots:
builder-util-runtime: 9.3.1 builder-util-runtime: 9.3.1
chromium-pickle-js: 0.2.0 chromium-pickle-js: 0.2.0
config-file-ts: 0.2.8-rc1 config-file-ts: 0.2.8-rc1
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
dmg-builder: 26.0.12(electron-builder-squirrel-windows@26.0.12) dmg-builder: 26.0.12(electron-builder-squirrel-windows@26.0.12)
dotenv: 16.5.0 dotenv: 16.5.0
dotenv-expand: 11.0.7 dotenv-expand: 11.0.7
@@ -6186,7 +6258,7 @@ snapshots:
builder-util-runtime@9.3.1: builder-util-runtime@9.3.1:
dependencies: dependencies:
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
sax: 1.4.1 sax: 1.4.1
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -6199,10 +6271,10 @@ snapshots:
builder-util-runtime: 9.3.1 builder-util-runtime: 9.3.1
chalk: 4.1.2 chalk: 4.1.2
cross-spawn: 7.0.6 cross-spawn: 7.0.6
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
fs-extra: 10.1.0 fs-extra: 10.1.0
http-proxy-agent: 7.0.2 http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6 https-proxy-agent: 7.0.6(supports-color@10.0.0)
is-ci: 3.0.1 is-ci: 3.0.1
js-yaml: 4.1.0 js-yaml: 4.1.0
sanitize-filename: 1.6.3 sanitize-filename: 1.6.3
@@ -6283,6 +6355,8 @@ snapshots:
ansi-styles: 4.3.0 ansi-styles: 4.3.0
supports-color: 7.2.0 supports-color: 7.2.0
change-case@5.4.4: {}
charenc@0.0.2: {} charenc@0.0.2: {}
cheerio-select@2.1.0: cheerio-select@2.1.0:
@@ -6364,6 +6438,8 @@ snapshots:
colord@2.9.3: {} colord@2.9.3: {}
colorette@1.4.0: {}
colorjs.io@0.5.2: {} colorjs.io@0.5.2: {}
colors@1.4.0: {} colors@1.4.0: {}
@@ -6418,10 +6494,6 @@ snapshots:
convert-source-map@2.0.0: {} convert-source-map@2.0.0: {}
copy-anything@3.0.5:
dependencies:
is-what: 4.1.16
core-util-is@1.0.2: core-util-is@1.0.2:
optional: true optional: true
@@ -6521,9 +6593,11 @@ snapshots:
dependencies: dependencies:
ms: 2.0.0 ms: 2.0.0
debug@4.4.1: debug@4.4.1(supports-color@10.0.0):
dependencies: dependencies:
ms: 2.1.3 ms: 2.1.3
optionalDependencies:
supports-color: 10.0.0
decompress-response@6.0.0: decompress-response@6.0.0:
dependencies: dependencies:
@@ -6713,7 +6787,7 @@ snapshots:
electron-localshortcut@3.2.1: electron-localshortcut@3.2.1:
dependencies: dependencies:
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
electron-is-accelerator: 0.1.2 electron-is-accelerator: 0.1.2
keyboardevent-from-electron-accelerator: 2.0.0 keyboardevent-from-electron-accelerator: 2.0.0
keyboardevents-areequal: 0.2.2 keyboardevents-areequal: 0.2.2
@@ -6755,7 +6829,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
electron-vite@3.1.0(vite@6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)): electron-vite@3.1.0(vite@6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)(yaml@2.8.0)):
dependencies: dependencies:
'@babel/core': 7.27.1 '@babel/core': 7.27.1
'@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.27.1) '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.27.1)
@@ -6763,14 +6837,14 @@ snapshots:
esbuild: 0.25.4 esbuild: 0.25.4
magic-string: 0.30.17 magic-string: 0.30.17
picocolors: 1.1.1 picocolors: 1.1.1
vite: 6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2) vite: 6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)(yaml@2.8.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
electron-winstaller@5.4.0: electron-winstaller@5.4.0:
dependencies: dependencies:
'@electron/asar': 3.4.1 '@electron/asar': 3.4.1
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
fs-extra: 7.0.1 fs-extra: 7.0.1
lodash: 4.17.21 lodash: 4.17.21
temp: 0.9.4 temp: 0.9.4
@@ -7038,7 +7112,7 @@ snapshots:
ajv: 6.12.6 ajv: 6.12.6
chalk: 4.1.2 chalk: 4.1.2
cross-spawn: 7.0.6 cross-spawn: 7.0.6
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint-scope: 8.3.0 eslint-scope: 8.3.0
eslint-visitor-keys: 4.2.0 eslint-visitor-keys: 4.2.0
@@ -7092,7 +7166,7 @@ snapshots:
extract-zip@2.0.1: extract-zip@2.0.1:
dependencies: dependencies:
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
get-stream: 5.2.0 get-stream: 5.2.0
yauzl: 2.10.0 yauzl: 2.10.0
optionalDependencies: optionalDependencies:
@@ -7499,14 +7573,14 @@ snapshots:
dependencies: dependencies:
'@tootallnate/once': 2.0.0 '@tootallnate/once': 2.0.0
agent-base: 6.0.2 agent-base: 6.0.2
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
http-proxy-agent@7.0.2: http-proxy-agent@7.0.2:
dependencies: dependencies:
agent-base: 7.1.3 agent-base: 7.1.3
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -7518,14 +7592,14 @@ snapshots:
https-proxy-agent@5.0.1: https-proxy-agent@5.0.1:
dependencies: dependencies:
agent-base: 6.0.2 agent-base: 6.0.2
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
https-proxy-agent@7.0.6: https-proxy-agent@7.0.6(supports-color@10.0.0):
dependencies: dependencies:
agent-base: 7.1.3 agent-base: 7.1.3
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -7598,6 +7672,8 @@ snapshots:
indent-string@4.0.0: {} indent-string@4.0.0: {}
index-to-position@1.1.0: {}
infer-owner@1.0.4: {} infer-owner@1.0.4: {}
inflight@1.0.6: inflight@1.0.6:
@@ -7758,8 +7834,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: {}
@@ -7792,6 +7866,8 @@ snapshots:
filelist: 1.0.4 filelist: 1.0.4
minimatch: 3.1.2 minimatch: 3.1.2
js-levenshtein@1.1.6: {}
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
js-yaml@4.1.0: js-yaml@4.1.0:
@@ -8192,6 +8268,22 @@ snapshots:
dependencies: dependencies:
mimic-fn: 2.1.0 mimic-fn: 2.1.0
openapi-fetch@0.14.0:
dependencies:
openapi-typescript-helpers: 0.0.15
openapi-typescript-helpers@0.0.15: {}
openapi-typescript@7.8.0(typescript@5.8.3):
dependencies:
'@redocly/openapi-core': 1.34.3(supports-color@10.0.0)
ansi-colors: 4.1.3
change-case: 5.4.4
parse-json: 8.3.0
supports-color: 10.0.0
typescript: 5.8.3
yargs-parser: 21.1.1
optionator@0.9.4: optionator@0.9.4:
dependencies: dependencies:
deep-is: 0.1.4 deep-is: 0.1.4
@@ -8265,6 +8357,12 @@ snapshots:
json-parse-even-better-errors: 2.3.1 json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4 lines-and-columns: 1.2.4
parse-json@8.3.0:
dependencies:
'@babel/code-frame': 7.27.1
index-to-position: 1.1.0
type-fest: 4.41.0
parse5-htmlparser2-tree-adapter@7.1.0: parse5-htmlparser2-tree-adapter@7.1.0:
dependencies: dependencies:
domhandler: 5.0.3 domhandler: 5.0.3
@@ -8321,6 +8419,8 @@ snapshots:
base64-js: 1.5.1 base64-js: 1.5.1
xmlbuilder: 15.1.1 xmlbuilder: 15.1.1
pluralize@8.0.0: {}
possible-typed-array-names@1.1.0: {} possible-typed-array-names@1.1.0: {}
postcss-js@4.0.1(postcss@8.5.3): postcss-js@4.0.1(postcss@8.5.3):
@@ -8596,7 +8696,7 @@ snapshots:
read-binary-file-arch@1.0.6: read-binary-file-arch@1.0.6:
dependencies: dependencies:
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -8636,8 +8736,6 @@ snapshots:
gopd: 1.2.0 gopd: 1.2.0
set-function-name: 2.0.2 set-function-name: 2.0.2
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: {}
@@ -8966,7 +9064,7 @@ snapshots:
socks-proxy-agent@7.0.0: socks-proxy-agent@7.0.0:
dependencies: dependencies:
agent-base: 6.0.2 agent-base: 6.0.2
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
socks: 2.8.6 socks: 2.8.6
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -9157,7 +9255,7 @@ snapshots:
cosmiconfig: 9.0.0(typescript@5.8.3) cosmiconfig: 9.0.0(typescript@5.8.3)
css-functions-list: 3.2.3 css-functions-list: 3.2.3
css-tree: 3.1.0 css-tree: 3.1.0
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
fast-glob: 3.3.3 fast-glob: 3.3.3
fastest-levenshtein: 1.0.16 fastest-levenshtein: 1.0.16
file-entry-cache: 10.1.1 file-entry-cache: 10.1.1
@@ -9195,13 +9293,11 @@ snapshots:
sumchecker@3.0.1: sumchecker@3.0.1:
dependencies: dependencies:
debug: 4.4.1 debug: 4.4.1(supports-color@10.0.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
superjson@1.13.3: supports-color@10.0.0: {}
dependencies:
copy-anything: 3.0.5
supports-color@7.2.0: supports-color@7.2.0:
dependencies: dependencies:
@@ -9421,6 +9517,8 @@ snapshots:
escalade: 3.2.0 escalade: 3.2.0
picocolors: 1.1.1 picocolors: 1.1.1
uri-js-replace@1.0.1: {}
uri-js@4.4.1: uri-js@4.4.1:
dependencies: dependencies:
punycode: 2.3.1 punycode: 2.3.1
@@ -9529,12 +9627,12 @@ snapshots:
fast-glob: 3.3.3 fast-glob: 3.3.3
magic-string: 0.30.17 magic-string: 0.30.17
vite-plugin-ejs@1.7.0(vite@6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)): vite-plugin-ejs@1.7.0(vite@6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)(yaml@2.8.0)):
dependencies: dependencies:
ejs: 3.1.10 ejs: 3.1.10
vite: 6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2) vite: 6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)(yaml@2.8.0)
vite@6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2): vite@6.3.5(@types/node@22.15.32)(sass-embedded@1.89.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.2)(yaml@2.8.0):
dependencies: dependencies:
esbuild: 0.25.4 esbuild: 0.25.4
fdir: 6.4.4(picomatch@4.0.2) fdir: 6.4.4(picomatch@4.0.2)
@@ -9548,6 +9646,7 @@ snapshots:
sass-embedded: 1.89.0 sass-embedded: 1.89.0
sugarss: 4.0.1(postcss@8.5.3) sugarss: 4.0.1(postcss@8.5.3)
terser: 5.39.2 terser: 5.39.2
yaml: 2.8.0
void-elements@3.1.0: {} void-elements@3.1.0: {}
@@ -9661,6 +9760,11 @@ snapshots:
yallist@4.0.0: {} yallist@4.0.0: {}
yaml-ast-parser@0.0.43: {}
yaml@2.8.0:
optional: true
yargs-parser@21.1.1: {} yargs-parser@21.1.1: {}
yargs@17.7.2: yargs@17.7.2:
+3 -1
View File
@@ -1,5 +1,7 @@
module.exports = { module.exports = {
plugins: { plugins: {
'postcss-preset-mantine': {}, 'postcss-preset-mantine': {
mixins: {},
},
}, },
}; };
+5 -1
View File
@@ -227,7 +227,11 @@
"songCount": "song count", "songCount": "song count",
"title": "title", "title": "title",
"toYear": "to year", "toYear": "to year",
"trackNumber": "track" "trackNumber": "track",
"createdAt": "created at",
"updatedAt": "updated at",
"type": "type",
"email": "email"
}, },
"form": { "form": {
"addServer": { "addServer": {
+3 -2
View File
@@ -1,13 +1,14 @@
import axios, { AxiosResponse } from 'axios'; import axios, { AxiosResponse } from 'axios';
import { load } from 'cheerio'; import { load } from 'cheerio';
import { orderSearchResults } from './shared';
import { import {
InternetProviderLyricResponse, InternetProviderLyricResponse,
InternetProviderLyricSearchResponse, InternetProviderLyricSearchResponse,
LyricSearchQuery, LyricSearchQuery,
LyricSource, LyricSource,
} from '.'; } from '/@/shared/types/domain/lyric-domain-types';
import { orderSearchResults } from './shared';
const SEARCH_URL = 'https://genius.com/api/search/song'; const SEARCH_URL = 'https://genius.com/api/search/song';
+6 -29
View File
@@ -17,35 +17,12 @@ import {
getSearchResults as searchNetease, getSearchResults as searchNetease,
} from './netease'; } from './netease';
import { Song } from '/@/shared/types/domain-types'; import {
InternetProviderLyricResponse,
export enum LyricSource { InternetProviderLyricSearchResponse,
GENIUS = 'Genius', LyricSource,
LRCLIB = 'lrclib.net', } from '/@/shared/types/domain/lyric-domain-types';
NETEASE = 'NetEase', import { Song } from '/@/shared/types/domain/song-domain-types';
}
export type FullLyricsMetadata = Omit<InternetProviderLyricResponse, 'id' | 'lyrics' | 'source'> & {
lyrics: LyricsResponse;
remote: boolean;
source: string;
};
export type InternetProviderLyricResponse = {
artist: string;
id: string;
lyrics: string;
name: string;
source: LyricSource;
};
export type InternetProviderLyricSearchResponse = {
artist: string;
id: string;
name: string;
score?: number;
source: LyricSource;
};
export type LyricGetQuery = { export type LyricGetQuery = {
remoteSongId: string; remoteSongId: string;
+3 -2
View File
@@ -1,13 +1,14 @@
// Credits to https://github.com/tranxuanthang/lrcget for API implementation // Credits to https://github.com/tranxuanthang/lrcget for API implementation
import axios, { AxiosResponse } from 'axios'; import axios, { AxiosResponse } from 'axios';
import { orderSearchResults } from './shared';
import { import {
InternetProviderLyricResponse, InternetProviderLyricResponse,
InternetProviderLyricSearchResponse, InternetProviderLyricSearchResponse,
LyricSearchQuery, LyricSearchQuery,
LyricSource, LyricSource,
} from '.'; } from '/@/shared/types/domain/lyric-domain-types';
import { orderSearchResults } from './shared';
const FETCH_URL = 'https://lrclib.net/api/get'; const FETCH_URL = 'https://lrclib.net/api/get';
const SEEARCH_URL = 'https://lrclib.net/api/search'; const SEEARCH_URL = 'https://lrclib.net/api/search';
+28 -27
View File
@@ -1,43 +1,20 @@
import axios, { AxiosResponse } from 'axios'; import axios, { AxiosResponse } from 'axios';
import { store } from '../settings';
import { orderSearchResults } from './shared';
import { import {
InternetProviderLyricResponse, InternetProviderLyricResponse,
InternetProviderLyricSearchResponse, InternetProviderLyricSearchResponse,
LyricSearchQuery, LyricSearchQuery,
LyricSource, LyricSource,
} from '.'; } from '/@/shared/types/domain/lyric-domain-types';
import { store } from '../settings';
import { orderSearchResults } from './shared';
const SEARCH_URL = 'https://music.163.com/api/search/get'; const SEARCH_URL = 'https://music.163.com/api/search/get';
const LYRICS_URL = 'https://music.163.com/api/song/lyric'; const LYRICS_URL = 'https://music.163.com/api/song/lyric';
// Adapted from https://github.com/NyaomiDEV/Sunamu/blob/master/src/main/lyricproviders/netease.ts // Adapted from https://github.com/NyaomiDEV/Sunamu/blob/master/src/main/lyricproviders/netease.ts
export interface Result {
hasMore: boolean;
songCount: number;
songs: Song[];
}
export interface Song {
album: Album;
alias: string[];
artists: Artist[];
copyrightId: number;
duration: number;
fee: number;
ftype: number;
id: number;
mark: number;
mvid: number;
name: string;
rtype: number;
rUrl: null;
status: number;
transNames?: string[];
}
interface Album { interface Album {
artist: Artist; artist: Artist;
copyrightId: number; copyrightId: number;
@@ -69,6 +46,30 @@ interface NetEaseResponse {
result: Result; result: Result;
} }
interface Result {
hasMore: boolean;
songCount: number;
songs: Song[];
}
interface Song {
album: Album;
alias: string[];
artists: Artist[];
copyrightId: number;
duration: number;
fee: number;
ftype: number;
id: number;
mark: number;
mvid: number;
name: string;
rtype: number;
rUrl: null;
status: number;
transNames?: string[];
}
export async function getLyricsBySongId(songId: string): Promise<null | string> { export async function getLyricsBySongId(songId: string): Promise<null | string> {
let result: AxiosResponse<any, any>; let result: AxiosResponse<any, any>;
try { try {
+2 -4
View File
@@ -1,9 +1,7 @@
import Fuse from 'fuse.js'; import Fuse from 'fuse.js';
import { import { InternetProviderLyricSearchResponse } from '/@/shared/types/domain/lyric-domain-types';
InternetProviderLyricSearchResponse, import { LyricSearchQuery } from '/@/shared/types/domain/lyric-domain-types';
LyricSearchQuery,
} from '/@/shared/types/domain-types';
export const orderSearchResults = (args: { export const orderSearchResults = (args: {
params: LyricSearchQuery; params: LyricSearchQuery;
+2 -2
View File
@@ -11,8 +11,8 @@ import manifest from './manifest.json';
import { getMainWindow } from '/@/main/index'; import { getMainWindow } from '/@/main/index';
import { isLinux } from '/@/main/utils'; import { isLinux } from '/@/main/utils';
import { QueueSong } from '/@/shared/types/domain-types'; import { QueueSong } from '/@/shared/types/domain/player-domain-types';
import { ClientEvent, ServerEvent } from '/@/shared/types/remote-types'; import { ClientEvent, ServerEvent } from '/@/shared/types/domain/remote-types';
import { PlayerRepeat, PlayerStatus, SongState } from '/@/shared/types/types'; import { PlayerRepeat, PlayerStatus, SongState } from '/@/shared/types/types';
let mprisPlayer: any | undefined; let mprisPlayer: any | undefined;
+2 -2
View File
@@ -2,7 +2,7 @@ import { ipcMain } from 'electron';
import Player from 'mpris-service'; import Player from 'mpris-service';
import { getMainWindow } from '/@/main/index'; import { getMainWindow } from '/@/main/index';
import { QueueSong } from '/@/shared/types/domain-types'; import { QueueSong } from '/@/shared/types/domain/player-domain-types';
import { PlayerRepeat, PlayerStatus } from '/@/shared/types/types'; import { PlayerRepeat, PlayerStatus } from '/@/shared/types/types';
const mprisPlayer = Player({ const mprisPlayer = Player({
@@ -168,7 +168,7 @@ ipcMain.on('update-song', (_event, song: QueueSong | undefined) => {
'xesam:contentCreated': song.releaseDate, 'xesam:contentCreated': song.releaseDate,
'xesam:discNumber': song.discNumber ? song.discNumber : null, 'xesam:discNumber': song.discNumber ? song.discNumber : null,
'xesam:genre': song.genres?.length ? song.genres.map((genre: any) => genre.name) : null, 'xesam:genre': song.genres?.length ? song.genres.map((genre: any) => genre.name) : null,
'xesam:lastUsed': song.lastPlayedAt, 'xesam:lastUsed': song.userLastPlayedDate,
'xesam:title': song.name || null, 'xesam:title': song.name || null,
'xesam:trackNumber': song.trackNumber ? song.trackNumber : null, 'xesam:trackNumber': song.trackNumber ? song.trackNumber : null,
'xesam:useCount': 'xesam:useCount':
+1 -1
View File
@@ -7,7 +7,7 @@ import {
LyricSource, LyricSource,
} from '../main/features/core/lyrics'; } from '../main/features/core/lyrics';
import { QueueSong } from '/@/shared/types/domain-types'; import { QueueSong } from '/@/shared/types/domain/player-domain-types';
const getRemoteLyricsBySong = (song: QueueSong) => { const getRemoteLyricsBySong = (song: QueueSong) => {
const result = ipcRenderer.invoke('lyric-by-song', song); const result = ipcRenderer.invoke('lyric-by-song', song);
+1 -1
View File
@@ -1,6 +1,6 @@
import { ipcRenderer, IpcRendererEvent } from 'electron'; import { ipcRenderer, IpcRendererEvent } from 'electron';
import { PlayerData } from '/@/shared/types/domain-types'; import { PlayerData } from '/@/shared/types/domain/player-domain-types';
const initialize = (data: { extraParameters?: string[]; properties?: Record<string, any> }) => { const initialize = (data: { extraParameters?: string[]; properties?: Record<string, any> }) => {
return ipcRenderer.invoke('player-initialize', data); return ipcRenderer.invoke('player-initialize', data);
+1 -1
View File
@@ -1,6 +1,6 @@
import { ipcRenderer, IpcRendererEvent } from 'electron'; import { ipcRenderer, IpcRendererEvent } from 'electron';
import { QueueSong } from '/@/shared/types/domain-types'; import { QueueSong, QueueSong } from '/@/shared/types/domain/player-domain-types';
import { PlayerStatus } from '/@/shared/types/types'; import { PlayerStatus } from '/@/shared/types/types';
const requestFavorite = ( const requestFavorite = (
+172
View File
@@ -0,0 +1,172 @@
import {
ApiController,
ApiControllerError,
ApiControllerFn,
} from '/@/shared/types/adapter/api-controller-types';
import { ServerListItem } from '/@/shared/types/domain/server-domain-types';
import { logger } from '/@/shared/utils/logger';
export interface LoggingOptions {
logErrors?: boolean;
logPerformance?: boolean;
logRequests?: boolean;
logResponses?: boolean;
maxRequestSize?: number;
maxResponseSize?: number;
}
type LoggedApiControllerFn<TRequest, TResponse> = (
request: TRequest,
server: ServerListItem,
options?: any,
) => Promise<[ApiControllerError, null] | [null, TResponse]>;
export function createLoggedApiController(
controller: ApiController,
options: LoggingOptions = {},
): ApiController {
const loggedController: any = {};
// Log utility functions
loggedController._utility = createLoggedUtility(controller._utility);
// Log all controller methods
for (const [sectionKey, section] of Object.entries(controller)) {
if (sectionKey === '_utility') continue;
loggedController[sectionKey] = {};
for (const [methodKey, method] of Object.entries(section as Record<string, any>)) {
if (typeof method === 'function') {
const functionName = `${sectionKey}.${methodKey}`;
if (methodKey === 'authenticate' || methodKey === 'getType') {
// Special handling for non-standard API functions
loggedController[sectionKey][methodKey] = (...args: any[]) => {
logger.info(`[API] ${functionName} called`, {
args: JSON.stringify(args, null, 2),
});
return method(...args);
};
} else {
loggedController[sectionKey][methodKey] = createLoggedFunction(
method as ApiControllerFn<any, any>,
functionName,
options,
);
}
} else {
loggedController[sectionKey][methodKey] = method;
}
}
}
return loggedController as ApiController;
}
function createLoggedFunction<TRequest, TResponse>(
originalFn: ApiControllerFn<TRequest, TResponse> | undefined,
functionName: string,
options: LoggingOptions = {},
): LoggedApiControllerFn<TRequest, TResponse> | undefined {
if (!originalFn) {
return undefined;
}
return async (request: TRequest, requestOptions?: any) => {
const startTime = Date.now();
const requestId = Math.random().toString(36).substring(2, 15);
const {
logErrors = true,
logPerformance = true,
logRequests = true,
logResponses = true,
maxRequestSize = 1000,
maxResponseSize = 1000,
} = options;
if (logRequests) {
const requestStr = JSON.stringify(request, null, 2);
const truncatedRequest =
requestStr.length > maxRequestSize
? requestStr.substring(0, maxRequestSize) + '...'
: requestStr;
logger.info(`[API] ${functionName} called`, {
request: truncatedRequest,
requestId,
});
}
try {
const result = await originalFn(request, requestOptions);
const duration = Date.now() - startTime;
if (result[0]) {
// Error response
if (logErrors) {
const error = result[0] as ApiControllerError;
logger.error(`[API] ${functionName} failed`, {
duration: logPerformance ? `${duration}ms` : undefined,
error: {
code: error.code,
message: error.message,
},
requestId,
});
}
} else {
// Success response
if (logResponses) {
const response = result[1];
const responseStr = JSON.stringify(response);
const truncatedResponse =
responseStr.length > maxResponseSize
? responseStr.substring(0, maxResponseSize) + '...'
: responseStr;
logger.info(`[API] ${functionName} succeeded`, {
duration: logPerformance ? `${duration}ms` : undefined,
requestId,
response: truncatedResponse,
responseSize: responseStr.length,
responseType: typeof response,
});
}
}
return result;
} catch (error) {
const duration = Date.now() - startTime;
if (logErrors) {
logger.error(`[API] ${functionName} threw exception`, {
duration: logPerformance ? `${duration}ms` : undefined,
error: error instanceof Error ? error.message : String(error),
requestId,
stack: error instanceof Error ? error.stack : undefined,
});
}
throw error;
}
};
}
function createLoggedUtility<T extends Record<string, any>>(utility: T): T {
const loggedUtility: any = {};
for (const [key, value] of Object.entries(utility)) {
if (typeof value === 'function') {
loggedUtility[key] = (...args: any[]) => {
logger.debug(`[API] _utility.${key} called`, {
args: JSON.stringify(args, null, 2),
});
return value(...args);
};
} else {
loggedUtility[key] = value;
}
}
return loggedUtility as T;
}
+75
View File
@@ -0,0 +1,75 @@
import { createLoggedApiController } from '/@/renderer/api/api-controller-logger';
import { useAuthStore } from '/@/renderer/store';
import {
createApiClient as subsonicApiClient,
authenticate as subsonicAuthenticate,
controller as subsonicController,
middleware as subsonicMiddleware,
} from '/@/shared/api/subsonic/subsonic-controller';
import { ApiController } from '/@/shared/types/adapter/api-controller-types';
import { ServerType } from '/@/shared/types/domain/server-domain-types';
export const serverApiMap = {
[ServerType.JELLYFIN]: {
apiClient: null,
authenticate: null,
controller: {},
middleware: null,
},
[ServerType.NAVIDROME]: {
apiClient: null,
authenticate: null,
controller: {},
middleware: null,
},
[ServerType.SUBSONIC]: {
apiClient: subsonicApiClient,
authenticate: subsonicAuthenticate,
controller: subsonicController,
middleware: subsonicMiddleware,
},
};
const getApiByServer = (serverId: string): ApiController => {
const servers = useAuthStore.getState().serverList;
const server = servers[serverId];
if (!server) {
throw new Error('No server or api client selected');
}
const { apiClient, controller, middleware } = serverApiMap[server.type];
if (!apiClient) {
throw new Error('No api client found');
}
const client = apiClient(server, middleware);
return createLoggedApiController(controller(client, server));
};
const getAppApi = () => {
const servers = useAuthStore.getState().serverList;
return Object.entries(servers).reduce(
(acc, [id]) => {
acc[id] = getApiByServer(id);
return acc;
},
{} as Record<string, ApiController>,
);
};
export const api = {
authenticate: (serverType: ServerType) => {
const { authenticate } = serverApiMap[serverType];
if (!serverType || !authenticate) {
throw new Error();
}
return authenticate;
},
controller: getAppApi(),
};
+3 -5
View File
@@ -4,11 +4,9 @@ import { NavidromeController } from '/@/renderer/api/navidrome/navidrome-control
import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller'; import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller';
import { useAuthStore } from '/@/renderer/store'; import { useAuthStore } from '/@/renderer/store';
import { toast } from '/@/shared/components/toast/toast'; import { toast } from '/@/shared/components/toast/toast';
import { import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types';
AuthenticationResponse, import { AuthenticationResponse } from '/@/shared/types/domain/auth-domain-types';
ControllerEndpoint, import { ServerType } from '/@/shared/types/domain/server-domain-types';
ServerType,
} from '/@/shared/types/domain-types';
type ApiController = { type ApiController = {
jellyfin: ControllerEndpoint; jellyfin: ControllerEndpoint;
+1 -1
View File
@@ -11,7 +11,7 @@ import { authenticationFailure } from '/@/renderer/api/utils';
import { useAuthStore } from '/@/renderer/store'; import { useAuthStore } from '/@/renderer/store';
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types'; import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
import { getClientType } from '/@/shared/api/utils'; import { getClientType } from '/@/shared/api/utils';
import { ServerListItem } from '/@/shared/types/domain-types'; import { ServerListItem } from '/@/shared/types/domain/server-domain-types';
const c = initContract(); const c = initContract();
@@ -6,19 +6,13 @@ import { JFSongListSort, JFSortOrder } from '/@/shared/api/jellyfin.types';
import { jfNormalize } from '/@/shared/api/jellyfin/jellyfin-normalize'; import { jfNormalize } from '/@/shared/api/jellyfin/jellyfin-normalize';
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types'; import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
import { getFeatures, hasFeature, VersionInfo } from '/@/shared/api/utils'; import { getFeatures, hasFeature, VersionInfo } from '/@/shared/api/utils';
import { import { albumListSortMap } from '/@/shared/types/domain/album-domain-types';
albumArtistListSortMap, import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types';
albumListSortMap, import { albumArtistListSortMap } from '/@/shared/types/domain/artist-domain-types';
ControllerEndpoint, import { Played } from '/@/shared/types/domain/player-domain-types';
genreListSortMap, import { ServerFeature } from '/@/shared/types/domain/server-domain-types';
LibraryItem, import { LibraryItem, sortOrderMap } from '/@/shared/types/domain/shared-domain-types';
Played, import { Song, songListSortMap } from '/@/shared/types/domain/song-domain-types';
playlistListSortMap,
Song,
songListSortMap,
sortOrderMap,
} from '/@/shared/types/domain-types';
import { ServerFeature } from '/@/shared/types/features-types';
const formatCommaDelimitedString = (value: string[]) => { const formatCommaDelimitedString = (value: string[]) => {
return value.join(','); return value.join(',');
@@ -326,7 +320,7 @@ export const JellyfinController: ControllerEndpoint = {
SearchTerm: query.searchTerm, SearchTerm: query.searchTerm,
SortBy: albumListSortMap.jellyfin[query.sortBy] || 'SortName', SortBy: albumListSortMap.jellyfin[query.sortBy] || 'SortName',
SortOrder: sortOrderMap.jellyfin[query.sortOrder], SortOrder: sortOrderMap.jellyfin[query.sortOrder],
StartIndex: query.startIndex, StartIndex: query.offset,
...query._custom?.jellyfin, ...query._custom?.jellyfin,
Years: yearsFilter, Years: yearsFilter,
}, },
@@ -338,14 +332,14 @@ export const JellyfinController: ControllerEndpoint = {
return { return {
items: res.body.Items.map((item) => jfNormalize.album(item, apiClientProps.server)), items: res.body.Items.map((item) => jfNormalize.album(item, apiClientProps.server)),
startIndex: query.startIndex, offset: query.offset,
totalRecordCount: res.body.TotalRecordCount, totalRecordCount: res.body.TotalRecordCount,
}; };
}, },
getAlbumListCount: async ({ apiClientProps, query }) => getAlbumListCount: async ({ apiClientProps, query }) =>
JellyfinController.getAlbumList({ JellyfinController.getAlbumList({
apiClientProps, apiClientProps,
query: { ...query, limit: 1, startIndex: 0 }, query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!), }).then((result) => result!.totalRecordCount!),
getArtistList: async (args) => { getArtistList: async (args) => {
const { apiClientProps, query } = args; const { apiClientProps, query } = args;
@@ -360,7 +354,7 @@ export const JellyfinController: ControllerEndpoint = {
SearchTerm: query.searchTerm, SearchTerm: query.searchTerm,
SortBy: albumArtistListSortMap.jellyfin[query.sortBy] || 'SortName,Name', SortBy: albumArtistListSortMap.jellyfin[query.sortBy] || 'SortName,Name',
SortOrder: sortOrderMap.jellyfin[query.sortOrder], SortOrder: sortOrderMap.jellyfin[query.sortOrder],
StartIndex: query.startIndex, StartIndex: query.offset,
UserId: apiClientProps.server?.userId || undefined, UserId: apiClientProps.server?.userId || undefined,
}, },
}); });
@@ -373,14 +367,14 @@ export const JellyfinController: ControllerEndpoint = {
items: res.body.Items.map((item) => items: res.body.Items.map((item) =>
jfNormalize.albumArtist(item, apiClientProps.server), jfNormalize.albumArtist(item, apiClientProps.server),
), ),
startIndex: query.startIndex, offset: query.offset,
totalRecordCount: res.body.TotalRecordCount, totalRecordCount: res.body.TotalRecordCount,
}; };
}, },
getArtistListCount: async ({ apiClientProps, query }) => getArtistListCount: async ({ apiClientProps, query }) =>
JellyfinController.getArtistList({ JellyfinController.getArtistList({
apiClientProps, apiClientProps,
query: { ...query, limit: 1, startIndex: 0 }, query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!), }).then((result) => result!.totalRecordCount!),
getDownloadUrl: (args) => { getDownloadUrl: (args) => {
const { apiClientProps, query } = args; const { apiClientProps, query } = args;
@@ -402,7 +396,7 @@ export const JellyfinController: ControllerEndpoint = {
SearchTerm: query?.searchTerm, SearchTerm: query?.searchTerm,
SortBy: genreListSortMap.jellyfin[query.sortBy] || 'SortName', SortBy: genreListSortMap.jellyfin[query.sortBy] || 'SortName',
SortOrder: sortOrderMap.jellyfin[query.sortOrder], SortOrder: sortOrderMap.jellyfin[query.sortOrder],
StartIndex: query.startIndex, StartIndex: query.offset,
UserId: apiClientProps.server?.userId, UserId: apiClientProps.server?.userId,
}, },
}); });
@@ -413,7 +407,7 @@ export const JellyfinController: ControllerEndpoint = {
return { return {
items: res.body.Items.map((item) => jfNormalize.genre(item, apiClientProps.server)), items: res.body.Items.map((item) => jfNormalize.genre(item, apiClientProps.server)),
startIndex: query.startIndex || 0, offset: query.offset || 0,
totalRecordCount: res.body?.TotalRecordCount || 0, totalRecordCount: res.body?.TotalRecordCount || 0,
}; };
}, },
@@ -462,7 +456,7 @@ export const JellyfinController: ControllerEndpoint = {
return { return {
items: musicFolders.map(jfNormalize.musicFolder), items: musicFolders.map(jfNormalize.musicFolder),
startIndex: 0, offset: 0,
totalRecordCount: musicFolders?.length || 0, totalRecordCount: musicFolders?.length || 0,
}; };
}, },
@@ -509,7 +503,7 @@ export const JellyfinController: ControllerEndpoint = {
SearchTerm: query.searchTerm, SearchTerm: query.searchTerm,
SortBy: playlistListSortMap.jellyfin[query.sortBy], SortBy: playlistListSortMap.jellyfin[query.sortBy],
SortOrder: sortOrderMap.jellyfin[query.sortOrder], SortOrder: sortOrderMap.jellyfin[query.sortOrder],
StartIndex: query.startIndex, StartIndex: query.offset,
}, },
}); });
@@ -519,14 +513,14 @@ export const JellyfinController: ControllerEndpoint = {
return { return {
items: res.body.Items.map((item) => jfNormalize.playlist(item, apiClientProps.server)), items: res.body.Items.map((item) => jfNormalize.playlist(item, apiClientProps.server)),
startIndex: 0, offset: 0,
totalRecordCount: res.body.TotalRecordCount, totalRecordCount: res.body.TotalRecordCount,
}; };
}, },
getPlaylistListCount: async ({ apiClientProps, query }) => getPlaylistListCount: async ({ apiClientProps, query }) =>
JellyfinController.getPlaylistList({ JellyfinController.getPlaylistList({
apiClientProps, apiClientProps,
query: { ...query, limit: 1, startIndex: 0 }, query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!), }).then((result) => result!.totalRecordCount!),
getPlaylistSongList: async (args) => { getPlaylistSongList: async (args) => {
const { apiClientProps, query } = args; const { apiClientProps, query } = args;
@@ -556,7 +550,7 @@ export const JellyfinController: ControllerEndpoint = {
return { return {
items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')), items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')),
startIndex: query.startIndex, offset: query.startIndex,
totalRecordCount: res.body.TotalRecordCount, totalRecordCount: res.body.TotalRecordCount,
}; };
}, },
@@ -606,7 +600,7 @@ export const JellyfinController: ControllerEndpoint = {
return { return {
items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')), items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')),
startIndex: 0, offset: 0,
totalRecordCount: res.body.Items.length || 0, totalRecordCount: res.body.Items.length || 0,
}; };
}, },
@@ -746,7 +740,7 @@ export const JellyfinController: ControllerEndpoint = {
SearchTerm: query.searchTerm, SearchTerm: query.searchTerm,
SortBy: songListSortMap.jellyfin[query.sortBy] || 'Album,SortName', SortBy: songListSortMap.jellyfin[query.sortBy] || 'Album,SortName',
SortOrder: sortOrderMap.jellyfin[query.sortOrder], SortOrder: sortOrderMap.jellyfin[query.sortOrder],
StartIndex: query.startIndex, StartIndex: query.offset,
...query._custom?.jellyfin, ...query._custom?.jellyfin,
Years: yearsFilter, Years: yearsFilter,
}, },
@@ -781,7 +775,7 @@ export const JellyfinController: ControllerEndpoint = {
SearchTerm: query.searchTerm, SearchTerm: query.searchTerm,
SortBy: songListSortMap.jellyfin[query.sortBy] || 'Album,SortName', SortBy: songListSortMap.jellyfin[query.sortBy] || 'Album,SortName',
SortOrder: sortOrderMap.jellyfin[query.sortOrder], SortOrder: sortOrderMap.jellyfin[query.sortOrder],
StartIndex: query.startIndex, StartIndex: query.offset,
...query._custom?.jellyfin, ...query._custom?.jellyfin,
Years: yearsFilter, Years: yearsFilter,
}, },
@@ -810,14 +804,14 @@ export const JellyfinController: ControllerEndpoint = {
items: items.map((item) => items: items.map((item) =>
jfNormalize.song(item, apiClientProps.server, '', query.imageSize), jfNormalize.song(item, apiClientProps.server, '', query.imageSize),
), ),
startIndex: query.startIndex, offset: query.offset,
totalRecordCount, totalRecordCount,
}; };
}, },
getSongListCount: async ({ apiClientProps, query }) => getSongListCount: async ({ apiClientProps, query }) =>
JellyfinController.getSongList({ JellyfinController.getSongList({
apiClientProps, apiClientProps,
query: { ...query, limit: 1, startIndex: 0 }, query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!), }).then((result) => result!.totalRecordCount!),
getTags: async (args) => { getTags: async (args) => {
const { apiClientProps, query } = args; const { apiClientProps, query } = args;
@@ -873,7 +867,7 @@ export const JellyfinController: ControllerEndpoint = {
return { return {
items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')), items: res.body.Items.map((item) => jfNormalize.song(item, apiClientProps.server, '')),
startIndex: 0, offset: 0,
totalRecordCount: res.body.TotalRecordCount, totalRecordCount: res.body.TotalRecordCount,
}; };
}, },
+1 -1
View File
@@ -11,7 +11,7 @@ import { useAuthStore } from '/@/renderer/store';
import { ndType } from '/@/shared/api/navidrome/navidrome-types'; import { ndType } from '/@/shared/api/navidrome/navidrome-types';
import { resultWithHeaders } from '/@/shared/api/utils'; import { resultWithHeaders } from '/@/shared/api/utils';
import { toast } from '/@/shared/components/toast/toast'; import { toast } from '/@/shared/components/toast/toast';
import { ServerListItem } from '/@/shared/types/domain-types'; import { ServerListItem } from '/@/shared/types/domain/server-domain-types';
const localSettings = isElectron() ? window.api.localSettings : null; const localSettings = isElectron() ? window.api.localSettings : null;
@@ -4,25 +4,24 @@ import { SubsonicController } from '/@/renderer/api/subsonic/subsonic-controller
import { NDSongListSort } from '/@/shared/api/navidrome.types'; import { NDSongListSort } from '/@/shared/api/navidrome.types';
import { ndNormalize } from '/@/shared/api/navidrome/navidrome-normalize'; import { ndNormalize } from '/@/shared/api/navidrome/navidrome-normalize';
import { ndType } from '/@/shared/api/navidrome/navidrome-types'; import { ndType } from '/@/shared/api/navidrome/navidrome-types';
import { ssNormalize } from '/@/shared/api/subsonic/subsonic-normalize'; import { normalize } from '/@/shared/api/subsonic/subsonic-normalize';
import { SubsonicExtensions } from '/@/shared/api/subsonic/subsonic-types'; import { SubsonicExtensions } from '/@/shared/api/subsonic/subsonic-types';
import { getFeatures, hasFeature, VersionInfo } from '/@/shared/api/utils'; import { getFeatures, hasFeature, VersionInfo } from '/@/shared/api/utils';
import { albumListSortMap } from '/@/shared/types/domain/album-domain-types';
import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types';
import { albumArtistListSortMap } from '/@/shared/types/domain/artist-domain-types';
import { AuthenticationResponse } from '/@/shared/types/domain/auth-domain-types';
import { import {
albumArtistListSortMap, PlaylistSongListRequest,
albumListSortMap,
AuthenticationResponse,
ControllerEndpoint,
genreListSortMap,
playlistListSortMap,
PlaylistSongListArgs,
PlaylistSongListResponse, PlaylistSongListResponse,
} from '/@/shared/types/domain/playlist-domain-types';
import {
ServerFeature,
ServerFeatures,
ServerListItem, ServerListItem,
Song, } from '/@/shared/types/domain/server-domain-types';
songListSortMap, import { sortOrderMap } from '/@/shared/types/domain/shared-domain-types';
sortOrderMap, import { Song, songListSortMap } from '/@/shared/types/domain/song-domain-types';
userListSortMap,
} from '/@/shared/types/domain-types';
import { ServerFeature, ServerFeatures } from '/@/shared/types/features-types';
const VERSION_INFO: VersionInfo = [ const VERSION_INFO: VersionInfo = [
['0.55.0', { [ServerFeature.BFR]: [1] }], ['0.55.0', { [ServerFeature.BFR]: [1] }],
@@ -271,10 +270,10 @@ export const NavidromeController: ControllerEndpoint = {
const res = await ndApiClient(apiClientProps).getAlbumList({ const res = await ndApiClient(apiClientProps).getAlbumList({
query: { query: {
_end: query.startIndex + (query.limit || 0), _end: query.offset + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder], _order: sortOrderMap.navidrome[query.sortOrder],
_sort: albumListSortMap.navidrome[query.sortBy], _sort: albumListSortMap.navidrome[query.sortBy],
_start: query.startIndex, _start: query.offset,
artist_id: query.artistIds?.[0], artist_id: query.artistIds?.[0],
compilation: query.compilation, compilation: query.compilation,
genre_id: query.genres?.[0], genre_id: query.genres?.[0],
@@ -291,24 +290,24 @@ export const NavidromeController: ControllerEndpoint = {
return { return {
items: res.body.data.map((album) => ndNormalize.album(album, apiClientProps.server)), items: res.body.data.map((album) => ndNormalize.album(album, apiClientProps.server)),
startIndex: query?.startIndex || 0, offset: query?.offset || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0), totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
}; };
}, },
getAlbumListCount: async ({ apiClientProps, query }) => getAlbumListCount: async ({ apiClientProps, query }) =>
NavidromeController.getAlbumList({ NavidromeController.getAlbumList({
apiClientProps, apiClientProps,
query: { ...query, limit: 1, startIndex: 0 }, query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!), }).then((result) => result!.totalRecordCount!),
getArtistList: async (args) => { getArtistList: async (args) => {
const { apiClientProps, query } = args; const { apiClientProps, query } = args;
const res = await ndApiClient(apiClientProps).getAlbumArtistList({ const res = await ndApiClient(apiClientProps).getAlbumArtistList({
query: { query: {
_end: query.startIndex + (query.limit || 0), _end: query.offset + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder], _order: sortOrderMap.navidrome[query.sortOrder],
_sort: albumArtistListSortMap.navidrome[query.sortBy], _sort: albumArtistListSortMap.navidrome[query.sortBy],
_start: query.startIndex, _start: query.offset,
name: query.searchTerm, name: query.searchTerm,
...query._custom?.navidrome, ...query._custom?.navidrome,
role: query.role || undefined, role: query.role || undefined,
@@ -333,14 +332,14 @@ export const NavidromeController: ControllerEndpoint = {
apiClientProps.server, apiClientProps.server,
), ),
), ),
startIndex: query.startIndex, offset: query.offset,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0), totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
}; };
}, },
getArtistListCount: async ({ apiClientProps, query }) => getArtistListCount: async ({ apiClientProps, query }) =>
NavidromeController.getArtistList({ NavidromeController.getArtistList({
apiClientProps, apiClientProps,
query: { ...query, limit: 1, startIndex: 0 }, query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!), }).then((result) => result!.totalRecordCount!),
getDownloadUrl: SubsonicController.getDownloadUrl, getDownloadUrl: SubsonicController.getDownloadUrl,
getGenreList: async (args) => { getGenreList: async (args) => {
@@ -348,10 +347,10 @@ export const NavidromeController: ControllerEndpoint = {
const res = await ndApiClient(apiClientProps).getGenreList({ const res = await ndApiClient(apiClientProps).getGenreList({
query: { query: {
_end: query.startIndex + (query.limit || 0), _end: query.offset + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder], _order: sortOrderMap.navidrome[query.sortOrder],
_sort: genreListSortMap.navidrome[query.sortBy], _sort: genreListSortMap.navidrome[query.sortBy],
_start: query.startIndex, _start: query.offset,
name: query.searchTerm, name: query.searchTerm,
}, },
}); });
@@ -362,7 +361,7 @@ export const NavidromeController: ControllerEndpoint = {
return { return {
items: res.body.data.map((genre) => ndNormalize.genre(genre)), items: res.body.data.map((genre) => ndNormalize.genre(genre)),
startIndex: query.startIndex || 0, offset: query.offset || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0), totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
}; };
}, },
@@ -398,10 +397,10 @@ export const NavidromeController: ControllerEndpoint = {
const res = await ndApiClient(apiClientProps).getPlaylistList({ const res = await ndApiClient(apiClientProps).getPlaylistList({
query: { query: {
_end: query.startIndex + (query.limit || 0), _end: query.offset + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder], _order: sortOrderMap.navidrome[query.sortOrder],
_sort: query.sortBy ? playlistListSortMap.navidrome[query.sortBy] : undefined, _sort: query.sortBy ? playlistListSortMap.navidrome[query.sortBy] : undefined,
_start: query.startIndex, _start: query.offset,
q: query.searchTerm, q: query.searchTerm,
...customQuery, ...customQuery,
}, },
@@ -413,16 +412,18 @@ export const NavidromeController: ControllerEndpoint = {
return { return {
items: res.body.data.map((item) => ndNormalize.playlist(item, apiClientProps.server)), items: res.body.data.map((item) => ndNormalize.playlist(item, apiClientProps.server)),
startIndex: query?.startIndex || 0, offset: query?.offset || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0), totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
}; };
}, },
getPlaylistListCount: async ({ apiClientProps, query }) => getPlaylistListCount: async ({ apiClientProps, query }) =>
NavidromeController.getPlaylistList({ NavidromeController.getPlaylistList({
apiClientProps, apiClientProps,
query: { ...query, limit: 1, startIndex: 0 }, query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!), }).then((result) => result!.totalRecordCount!),
getPlaylistSongList: async (args: PlaylistSongListArgs): Promise<PlaylistSongListResponse> => { getPlaylistSongList: async (
args: PlaylistSongListRequest,
): Promise<PlaylistSongListResponse> => {
const { apiClientProps, query } = args; const { apiClientProps, query } = args;
const res = await ndApiClient(apiClientProps).getPlaylistSongList({ const res = await ndApiClient(apiClientProps).getPlaylistSongList({
@@ -446,7 +447,7 @@ export const NavidromeController: ControllerEndpoint = {
return { return {
items: res.body.data.map((item) => ndNormalize.song(item, apiClientProps.server)), items: res.body.data.map((item) => ndNormalize.song(item, apiClientProps.server)),
startIndex: query?.startIndex || 0, offset: query?.startIndex || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0), totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
}; };
}, },
@@ -518,7 +519,7 @@ export const NavidromeController: ControllerEndpoint = {
if (res.status === 200 && res.body.similarSongs?.song) { if (res.status === 200 && res.body.similarSongs?.song) {
const similar = res.body.similarSongs.song.reduce<Song[]>((acc, song) => { const similar = res.body.similarSongs.song.reduce<Song[]>((acc, song) => {
if (song.id !== query.songId) { if (song.id !== query.songId) {
acc.push(ssNormalize.song(song, apiClientProps.server)); acc.push(normalize.song(song, apiClientProps.server));
} }
return acc; return acc;
@@ -572,10 +573,10 @@ export const NavidromeController: ControllerEndpoint = {
const res = await ndApiClient(apiClientProps).getSongList({ const res = await ndApiClient(apiClientProps).getSongList({
query: { query: {
_end: query.startIndex + (query.limit || -1), _end: query.offset + (query.limit || -1),
_order: sortOrderMap.navidrome[query.sortOrder], _order: sortOrderMap.navidrome[query.sortOrder],
_sort: songListSortMap.navidrome[query.sortBy], _sort: songListSortMap.navidrome[query.sortBy],
_start: query.startIndex, _start: query.offset,
album_artist_id: query.albumArtistIds, album_artist_id: query.albumArtistIds,
album_id: query.albumIds, album_id: query.albumIds,
artist_id: query.artistIds, artist_id: query.artistIds,
@@ -595,14 +596,14 @@ export const NavidromeController: ControllerEndpoint = {
items: res.body.data.map((song) => items: res.body.data.map((song) =>
ndNormalize.song(song, apiClientProps.server, query.imageSize), ndNormalize.song(song, apiClientProps.server, query.imageSize),
), ),
startIndex: query?.startIndex || 0, offset: query?.offset || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0), totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
}; };
}, },
getSongListCount: async ({ apiClientProps, query }) => getSongListCount: async ({ apiClientProps, query }) =>
NavidromeController.getSongList({ NavidromeController.getSongList({
apiClientProps, apiClientProps,
query: { ...query, limit: 1, startIndex: 0 }, query: { ...query, limit: 1, offset: 0 },
}).then((result) => result!.totalRecordCount!), }).then((result) => result!.totalRecordCount!),
getStructuredLyrics: SubsonicController.getStructuredLyrics, getStructuredLyrics: SubsonicController.getStructuredLyrics,
getTags: async (args) => { getTags: async (args) => {
@@ -651,10 +652,10 @@ export const NavidromeController: ControllerEndpoint = {
const res = await ndApiClient(apiClientProps).getUserList({ const res = await ndApiClient(apiClientProps).getUserList({
query: { query: {
_end: query.startIndex + (query.limit || 0), _end: query.offset + (query.limit || 0),
_order: sortOrderMap.navidrome[query.sortOrder], _order: sortOrderMap.navidrome[query.sortOrder],
_sort: userListSortMap.navidrome[query.sortBy], _sort: userListSortMap.navidrome[query.sortBy],
_start: query.startIndex, _start: query.offset,
...query._custom?.navidrome, ...query._custom?.navidrome,
}, },
}); });
@@ -665,7 +666,7 @@ export const NavidromeController: ControllerEndpoint = {
return { return {
items: res.body.data.map((user) => ndNormalize.user(user)), items: res.body.data.map((user) => ndNormalize.user(user)),
startIndex: query?.startIndex || 0, offset: query?.offset || 0,
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0), totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
}; };
}, },
+15 -11
View File
@@ -1,27 +1,31 @@
import type { import { QueryFunctionContext } from '@tanstack/react-query';
import { AlbumDetailQuery, AlbumListQuery } from '/@/shared/types/domain/album-domain-types';
import {
AlbumArtistDetailQuery, AlbumArtistDetailQuery,
AlbumArtistListQuery, AlbumArtistListQuery,
AlbumDetailQuery,
AlbumListQuery,
ArtistListQuery, ArtistListQuery,
GenreListQuery, } from '/@/shared/types/domain/artist-domain-types';
import { GenreListQuery } from '/@/shared/types/domain/genre-domain-types';
import {
LyricSearchQuery, LyricSearchQuery,
LyricSource,
LyricsQuery, LyricsQuery,
} from '/@/shared/types/domain/lyric-domain-types';
import {
PlaylistDetailQuery, PlaylistDetailQuery,
PlaylistListQuery, PlaylistListQuery,
PlaylistSongListQuery, PlaylistSongListQuery,
} from '/@/shared/types/domain/playlist-domain-types';
import { SearchQuery } from '/@/shared/types/domain/search-domain-types';
import {
RandomSongListQuery, RandomSongListQuery,
SearchQuery,
SimilarSongsQuery, SimilarSongsQuery,
SongDetailQuery, SongDetailQuery,
SongListQuery, SongListQuery,
TopSongListQuery, TopSongListQuery,
UserListQuery, } from '/@/shared/types/domain/song-domain-types';
} from '/@/shared/types/domain-types'; import { UserListQuery } from '/@/shared/types/domain/user-domain-types';
import { QueryFunctionContext } from '@tanstack/react-query';
import { LyricSource } from '/@/shared/types/domain-types';
export const splitPaginatedQuery = (key: any) => { export const splitPaginatedQuery = (key: any) => {
const { limit, startIndex, ...filter } = key || {}; const { limit, startIndex, ...filter } = key || {};
+1 -1
View File
@@ -7,7 +7,7 @@ import { z } from 'zod';
import i18n from '/@/i18n/i18n'; import i18n from '/@/i18n/i18n';
import { ssType } from '/@/shared/api/subsonic/subsonic-types'; import { ssType } from '/@/shared/api/subsonic/subsonic-types';
import { toast } from '/@/shared/components/toast/toast'; import { toast } from '/@/shared/components/toast/toast';
import { ServerListItem } from '/@/shared/types/domain-types'; import { ServerListItem } from '/@/shared/types/domain/server-domain-types';
const c = initContract(); const c = initContract();
@@ -8,25 +8,18 @@ import { z } from 'zod';
import { contract, ssApiClient } from '/@/renderer/api/subsonic/subsonic-api'; import { contract, ssApiClient } from '/@/renderer/api/subsonic/subsonic-api';
import { randomString } from '/@/renderer/utils'; import { randomString } from '/@/renderer/utils';
import { ssNormalize } from '/@/shared/api/subsonic/subsonic-normalize'; import { normalize } from '/@/shared/api/subsonic/subsonic-normalize';
import { import {
AlbumListSortType, AlbumListSortType,
ssType, ssType,
SubsonicExtensions, SubsonicExtensions,
} from '/@/shared/api/subsonic/subsonic-types'; } from '/@/shared/api/subsonic/subsonic-types';
import { import { AlbumListSort, sortAlbumList } from '/@/shared/types/domain/album-domain-types';
AlbumListSort, import { ControllerEndpoint } from '/@/shared/types/domain/api-domain-types';
ControllerEndpoint, import { sortAlbumArtistList } from '/@/shared/types/domain/artist-domain-types';
GenreListSort, import { ServerFeatures } from '/@/shared/types/domain/server-domain-types';
LibraryItem, import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
PlaylistListSort, import { Song, sortSongList } from '/@/shared/types/domain/song-domain-types';
Song,
sortAlbumArtistList,
sortAlbumList,
SortOrder,
sortSongList,
} from '/@/shared/types/domain-types';
import { ServerFeatures } from '/@/shared/types/features-types';
const ALBUM_LIST_SORT_MAPPING: Record<AlbumListSort, AlbumListSortType | undefined> = { const ALBUM_LIST_SORT_MAPPING: Record<AlbumListSort, AlbumListSortType | undefined> = {
[AlbumListSort.ALBUM_ARTIST]: AlbumListSortType.ALPHABETICAL_BY_ARTIST, [AlbumListSort.ALBUM_ARTIST]: AlbumListSortType.ALPHABETICAL_BY_ARTIST,
@@ -205,11 +198,11 @@ export const SubsonicController: ControllerEndpoint = {
} }
return { return {
...ssNormalize.albumArtist(artist, apiClientProps.server, 300), ...normalize.albumArtist(artist, apiClientProps.server, 300),
albums: artist.album?.map((album) => ssNormalize.album(album, apiClientProps.server)), albums: artist.album?.map((album) => normalize.album(album, apiClientProps.server)),
similarArtists: similarArtists:
artistInfo?.similarArtist?.map((artist) => artistInfo?.similarArtist?.map((artist) =>
ssNormalize.albumArtist(artist, apiClientProps.server, 300), normalize.albumArtist(artist, apiClientProps.server, 300),
) || null, ) || null,
}; };
}, },
@@ -229,7 +222,7 @@ export const SubsonicController: ControllerEndpoint = {
const artists = (res.body.artists?.index || []).flatMap((index) => index.artist); const artists = (res.body.artists?.index || []).flatMap((index) => index.artist);
let results = artists.map((artist) => let results = artists.map((artist) =>
ssNormalize.albumArtist(artist, apiClientProps.server, 300), normalize.albumArtist(artist, apiClientProps.server, 300),
); );
if (query.searchTerm) { if (query.searchTerm) {
@@ -265,7 +258,7 @@ export const SubsonicController: ControllerEndpoint = {
throw new Error('Failed to get album detail'); throw new Error('Failed to get album detail');
} }
return ssNormalize.album(res.body.album, apiClientProps.server); return normalize.album(res.body.album, apiClientProps.server);
}, },
getAlbumList: async (args) => { getAlbumList: async (args) => {
const { apiClientProps, query } = args; const { apiClientProps, query } = args;
@@ -274,7 +267,7 @@ export const SubsonicController: ControllerEndpoint = {
const res = await ssApiClient(apiClientProps).search3({ const res = await ssApiClient(apiClientProps).search3({
query: { query: {
albumCount: query.limit, albumCount: query.limit,
albumOffset: query.startIndex, albumOffset: query.offset,
artistCount: 0, artistCount: 0,
artistOffset: 0, artistOffset: 0,
query: query.searchTerm || '', query: query.searchTerm || '',
@@ -289,12 +282,12 @@ export const SubsonicController: ControllerEndpoint = {
const results = const results =
res.body.searchResult3?.album?.map((album) => res.body.searchResult3?.album?.map((album) =>
ssNormalize.album(album, apiClientProps.server), normalize.album(album, apiClientProps.server),
) || []; ) || [];
return { return {
items: results, items: results,
startIndex: query.startIndex, offset: query.offset,
totalRecordCount: null, totalRecordCount: null,
}; };
} }
@@ -324,11 +317,11 @@ export const SubsonicController: ControllerEndpoint = {
return artist.body.artist.album ?? []; return artist.body.artist.album ?? [];
}); });
const items = albums.map((album) => ssNormalize.album(album, apiClientProps.server)); const items = albums.map((album) => normalize.album(album, apiClientProps.server));
return { return {
items: sortAlbumList(items, query.sortBy, query.sortOrder), items: sortAlbumList(items, query.sortBy, query.sortOrder),
startIndex: 0, offset: 0,
totalRecordCount: albums.length, totalRecordCount: albums.length,
}; };
} }
@@ -346,12 +339,12 @@ export const SubsonicController: ControllerEndpoint = {
const results = const results =
res.body.starred?.album?.map((album) => res.body.starred?.album?.map((album) =>
ssNormalize.album(album, apiClientProps.server), normalize.album(album, apiClientProps.server),
) || []; ) || [];
return { return {
items: sortAlbumList(results, query.sortBy, query.sortOrder), items: sortAlbumList(results, query.sortBy, query.sortOrder),
startIndex: 0, offset: 0,
totalRecordCount: res.body.starred?.album?.length || 0, totalRecordCount: res.body.starred?.album?.length || 0,
}; };
} }
@@ -381,7 +374,7 @@ export const SubsonicController: ControllerEndpoint = {
} }
if (type === AlbumListSortType.BY_YEAR && !fromYear && !toYear) { if (type === AlbumListSortType.BY_YEAR && !fromYear && !toYear) {
if (query.sortOrder === SortOrder.ASC) { if (query.sortOrder === ListSortOrder.ASC) {
fromYear = 0; fromYear = 0;
toYear = dayjs().year(); toYear = dayjs().year();
} else { } else {
@@ -395,7 +388,7 @@ export const SubsonicController: ControllerEndpoint = {
fromYear, fromYear,
genre: query.genres?.length ? query.genres[0] : undefined, genre: query.genres?.length ? query.genres[0] : undefined,
musicFolderId: query.musicFolderId, musicFolderId: query.musicFolderId,
offset: query.startIndex, offset: query.offset,
size: query.limit, size: query.limit,
toYear, toYear,
type, type,
@@ -409,9 +402,9 @@ export const SubsonicController: ControllerEndpoint = {
return { return {
items: items:
res.body.albumList2.album?.map((album) => res.body.albumList2.album?.map((album) =>
ssNormalize.album(album, apiClientProps.server, 300), normalize.album(album, apiClientProps.server, 300),
) || [], ) || [],
startIndex: query.startIndex, offset: query.offset,
totalRecordCount: null, totalRecordCount: null,
}; };
}, },
@@ -579,7 +572,7 @@ export const SubsonicController: ControllerEndpoint = {
} }
let results = artists.map((artist) => let results = artists.map((artist) =>
ssNormalize.albumArtist(artist, apiClientProps.server, 300), normalize.albumArtist(artist, apiClientProps.server, 300),
); );
if (query.searchTerm) { if (query.searchTerm) {
@@ -596,7 +589,7 @@ export const SubsonicController: ControllerEndpoint = {
return { return {
items: results, items: results,
startIndex: query.startIndex, offset: query.offset,
totalRecordCount: results?.length || 0, totalRecordCount: results?.length || 0,
}; };
}, },
@@ -640,11 +633,11 @@ export const SubsonicController: ControllerEndpoint = {
break; break;
} }
const genres = results.map(ssNormalize.genre); const genres = results.map(normalize.genre);
return { return {
items: genres, items: genres,
startIndex: 0, offset: 0,
totalRecordCount: genres.length, totalRecordCount: genres.length,
}; };
}, },
@@ -662,7 +655,7 @@ export const SubsonicController: ControllerEndpoint = {
id: folder.id.toString(), id: folder.id.toString(),
name: folder.name, name: folder.name,
})), })),
startIndex: 0, offset: 0,
totalRecordCount: res.body.musicFolders.musicFolder.length, totalRecordCount: res.body.musicFolders.musicFolder.length,
}; };
}, },
@@ -679,7 +672,7 @@ export const SubsonicController: ControllerEndpoint = {
throw new Error('Failed to get playlist detail'); throw new Error('Failed to get playlist detail');
} }
return ssNormalize.playlist(res.body.playlist, apiClientProps.server); return normalize.playlist(res.body.playlist, apiClientProps.server);
}, },
getPlaylistList: async ({ apiClientProps, query }) => { getPlaylistList: async ({ apiClientProps, query }) => {
const sortOrder = query.sortOrder.toLowerCase() as 'asc' | 'desc'; const sortOrder = query.sortOrder.toLowerCase() as 'asc' | 'desc';
@@ -724,8 +717,8 @@ export const SubsonicController: ControllerEndpoint = {
} }
return { return {
items: results.map((playlist) => ssNormalize.playlist(playlist, apiClientProps.server)), items: results.map((playlist) => normalize.playlist(playlist, apiClientProps.server)),
startIndex: 0, offset: 0,
totalRecordCount: results.length, totalRecordCount: results.length,
}; };
}, },
@@ -760,7 +753,7 @@ export const SubsonicController: ControllerEndpoint = {
} }
let results = let results =
res.body.playlist.entry?.map((song) => ssNormalize.song(song, apiClientProps.server)) || res.body.playlist.entry?.map((song) => normalize.song(song, apiClientProps.server)) ||
[]; [];
if (query.sortBy && query.sortOrder) { if (query.sortBy && query.sortOrder) {
@@ -769,7 +762,7 @@ export const SubsonicController: ControllerEndpoint = {
return { return {
items: results, items: results,
startIndex: 0, offset: 0,
totalRecordCount: results?.length || 0, totalRecordCount: results?.length || 0,
}; };
}, },
@@ -793,8 +786,8 @@ export const SubsonicController: ControllerEndpoint = {
const results = res.body.randomSongs?.song || []; const results = res.body.randomSongs?.song || [];
return { return {
items: results.map((song) => ssNormalize.song(song, apiClientProps.server)), items: results.map((song) => normalize.song(song, apiClientProps.server)),
startIndex: 0, offset: 0,
totalRecordCount: res.body.randomSongs?.song?.length || 0, totalRecordCount: res.body.randomSongs?.song?.length || 0,
}; };
}, },
@@ -878,7 +871,7 @@ export const SubsonicController: ControllerEndpoint = {
return res.body.similarSongs.song.reduce<Song[]>((acc, song) => { return res.body.similarSongs.song.reduce<Song[]>((acc, song) => {
if (song.id !== query.songId) { if (song.id !== query.songId) {
acc.push(ssNormalize.song(song, apiClientProps.server)); acc.push(normalize.song(song, apiClientProps.server));
} }
return acc; return acc;
@@ -897,7 +890,7 @@ export const SubsonicController: ControllerEndpoint = {
throw new Error('Failed to get song detail'); throw new Error('Failed to get song detail');
} }
return ssNormalize.song(res.body.song, apiClientProps.server); return normalize.song(res.body.song, apiClientProps.server);
}, },
getSongList: async ({ apiClientProps, query }) => { getSongList: async ({ apiClientProps, query }) => {
const fromAlbumPromises: Promise<ServerInferResponses<typeof contract.getAlbum>>[] = []; const fromAlbumPromises: Promise<ServerInferResponses<typeof contract.getAlbum>>[] = [];
@@ -912,7 +905,7 @@ export const SubsonicController: ControllerEndpoint = {
artistOffset: 0, artistOffset: 0,
query: query.searchTerm || '', query: query.searchTerm || '',
songCount: query.limit, songCount: query.limit,
songOffset: query.startIndex, songOffset: query.offset,
}, },
}); });
@@ -923,9 +916,9 @@ export const SubsonicController: ControllerEndpoint = {
return { return {
items: items:
res.body.searchResult3?.song?.map((song) => res.body.searchResult3?.song?.map((song) =>
ssNormalize.song(song, apiClientProps.server), normalize.song(song, apiClientProps.server),
) || [], ) || [],
startIndex: query.startIndex, offset: query.offset,
totalRecordCount: null, totalRecordCount: null,
}; };
} }
@@ -936,7 +929,7 @@ export const SubsonicController: ControllerEndpoint = {
count: query.limit, count: query.limit,
genre: query.genreIds[0], genre: query.genreIds[0],
musicFolderId: query.musicFolderId, musicFolderId: query.musicFolderId,
offset: query.startIndex, offset: query.offset,
}, },
}); });
@@ -947,8 +940,8 @@ export const SubsonicController: ControllerEndpoint = {
const results = res.body.songsByGenre?.song || []; const results = res.body.songsByGenre?.song || [];
return { return {
items: results.map((song) => ssNormalize.song(song, apiClientProps.server)) || [], items: results.map((song) => normalize.song(song, apiClientProps.server)) || [],
startIndex: 0, offset: 0,
totalRecordCount: null, totalRecordCount: null,
}; };
} }
@@ -966,12 +959,12 @@ export const SubsonicController: ControllerEndpoint = {
const results = const results =
(res.body.starred?.song || []).map((song) => (res.body.starred?.song || []).map((song) =>
ssNormalize.song(song, apiClientProps.server), normalize.song(song, apiClientProps.server),
) || []; ) || [];
return { return {
items: sortSongList(results, query.sortBy, query.sortOrder), items: sortSongList(results, query.sortBy, query.sortOrder),
startIndex: 0, offset: 0,
totalRecordCount: (res.body.starred?.song || []).length || 0, totalRecordCount: (res.body.starred?.song || []).length || 0,
}; };
} }
@@ -1040,8 +1033,8 @@ export const SubsonicController: ControllerEndpoint = {
} }
return { return {
items: results.map((song) => ssNormalize.song(song, apiClientProps.server)), items: results.map((song) => normalize.song(song, apiClientProps.server)),
startIndex: 0, offset: 0,
totalRecordCount: results.length, totalRecordCount: results.length,
}; };
} }
@@ -1054,7 +1047,7 @@ export const SubsonicController: ControllerEndpoint = {
artistOffset: 0, artistOffset: 0,
query: query.searchTerm || '', query: query.searchTerm || '',
songCount: query.limit, songCount: query.limit,
songOffset: query.startIndex, songOffset: query.offset,
}, },
}); });
@@ -1065,9 +1058,9 @@ export const SubsonicController: ControllerEndpoint = {
return { return {
items: items:
res.body.searchResult3?.song?.map((song) => res.body.searchResult3?.song?.map((song) =>
ssNormalize.song(song, apiClientProps.server), normalize.song(song, apiClientProps.server),
) || [], ) || [],
startIndex: 0, offset: 0,
totalRecordCount: null, totalRecordCount: null,
}; };
}, },
@@ -1302,9 +1295,9 @@ export const SubsonicController: ControllerEndpoint = {
return { return {
items: items:
res.body.topSongs?.song?.map((song) => res.body.topSongs?.song?.map((song) =>
ssNormalize.song(song, apiClientProps.server), normalize.song(song, apiClientProps.server),
) || [], ) || [],
startIndex: 0, offset: 0,
totalRecordCount: res.body.topSongs?.song?.length || 0, totalRecordCount: res.body.topSongs?.song?.length || 0,
}; };
}, },
@@ -1372,13 +1365,13 @@ export const SubsonicController: ControllerEndpoint = {
return { return {
albumArtists: (res.body.searchResult3?.artist || [])?.map((artist) => albumArtists: (res.body.searchResult3?.artist || [])?.map((artist) =>
ssNormalize.albumArtist(artist, apiClientProps.server), normalize.albumArtist(artist, apiClientProps.server),
), ),
albums: (res.body.searchResult3?.album || []).map((album) => albums: (res.body.searchResult3?.album || []).map((album) =>
ssNormalize.album(album, apiClientProps.server), normalize.album(album, apiClientProps.server),
), ),
songs: (res.body.searchResult3?.song || []).map((song) => songs: (res.body.searchResult3?.song || []).map((song) =>
ssNormalize.song(song, apiClientProps.server), normalize.song(song, apiClientProps.server),
), ),
}; };
}, },
+14 -12
View File
@@ -1,4 +1,3 @@
import type { Song } from '/@/shared/types/domain-types';
import type { CrossfadeStyle } from '/@/shared/types/types'; import type { CrossfadeStyle } from '/@/shared/types/types';
import type { ReactPlayerProps } from 'react-player'; import type { ReactPlayerProps } from 'react-player';
@@ -20,9 +19,11 @@ import {
gaplessHandler, gaplessHandler,
} from '/@/renderer/components/audio-player/utils/list-handlers'; } from '/@/renderer/components/audio-player/utils/list-handlers';
import { useWebAudio } from '/@/renderer/features/player/hooks/use-webaudio'; import { useWebAudio } from '/@/renderer/features/player/hooks/use-webaudio';
import { getServerById, TranscodingConfig, usePlaybackSettings, useSpeed } from '/@/renderer/store'; import { TranscodingConfig, usePlaybackSettings, useSpeed } from '/@/renderer/store';
import { useSettingsStore, useSettingsStoreActions } from '/@/renderer/store/settings.store'; import { useSettingsStore, useSettingsStoreActions } from '/@/renderer/store/settings.store';
import { toast } from '/@/shared/components/toast/toast'; import { toast } from '/@/shared/components/toast/toast';
import { QueueSong } from '/@/shared/types/domain/player-domain-types';
import { Song } from '/@/shared/types/domain/song-domain-types';
import { PlaybackStyle, PlayerStatus } from '/@/shared/types/types'; import { PlaybackStyle, PlayerStatus } from '/@/shared/types/types';
export type AudioPlayerProgress = { export type AudioPlayerProgress = {
@@ -57,27 +58,28 @@ const getDuration = (ref: any) => {
const EMPTY_SOURCE = const EMPTY_SOURCE =
'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU2LjM2LjEwMAAAAAAAAAAAAAAA//OEAAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAAEAAABIADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV6urq6urq6urq6urq6urq6urq6urq6urq6v////////////////////////////////8AAAAATGF2YzU2LjQxAAAAAAAAAAAAAAAAJAAAAAAAAAAAASDs90hvAAAAAAAAAAAAAAAAAAAA//MUZAAAAAGkAAAAAAAAA0gAAAAATEFN//MUZAMAAAGkAAAAAAAAA0gAAAAARTMu//MUZAYAAAGkAAAAAAAAA0gAAAAAOTku//MUZAkAAAGkAAAAAAAAA0gAAAAANVVV'; 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU2LjM2LjEwMAAAAAAAAAAAAAAA//OEAAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAAEAAABIADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV6urq6urq6urq6urq6urq6urq6urq6urq6v////////////////////////////////8AAAAATGF2YzU2LjQxAAAAAAAAAAAAAAAAJAAAAAAAAAAAASDs90hvAAAAAAAAAAAAAAAAAAAA//MUZAAAAAGkAAAAAAAAA0gAAAAATEFN//MUZAMAAAGkAAAAAAAAA0gAAAAARTMu//MUZAYAAAGkAAAAAAAAA0gAAAAAOTku//MUZAkAAAGkAAAAAAAAA0gAAAAANVVV';
const useSongUrl = (transcode: TranscodingConfig, current: boolean, song?: Song): null | string => { const useSongUrl = (
transcode: TranscodingConfig,
current: boolean,
song?: QueueSong,
): null | string => {
const prior = useRef(['', '']); const prior = useRef(['', '']);
return useMemo(() => { return useMemo(() => {
if (song?.serverId) { if (song?._serverId) {
// If we are the current track, we do not want a transcoding // If we are the current track, we do not want a transcoding
// reconfiguration to force a restart. // reconfiguration to force a restart.
if (current && prior.current[0] === song.uniqueId) { if (current && prior.current[0] === song._uniqueId) {
return prior.current[1]; return prior.current[1] as string;
} }
if (!transcode.enabled) { if (!transcode.enabled) {
// transcoding disabled; save the result // transcoding disabled; save the result
prior.current = [song.uniqueId, song.streamUrl]; prior.current = [song._uniqueId, song.streamUrl];
return song.streamUrl; return song.streamUrl;
} }
const result = api.controller.getTranscodingUrl({ const result = api.controller.getTranscodingUrl({
apiClientProps: {
server: getServerById(song.serverId),
},
query: { query: {
base: song.streamUrl, base: song.streamUrl,
...transcode, ...transcode,
@@ -85,14 +87,14 @@ const useSongUrl = (transcode: TranscodingConfig, current: boolean, song?: Song)
})!; })!;
// transcoding enabled; save the updated result // transcoding enabled; save the updated result
prior.current = [song.uniqueId, result]; prior.current = [song._uniqueId, result];
return result; return result;
} }
// no track; clear result // no track; clear result
prior.current = ['', '']; prior.current = ['', ''];
return null; return null;
}, [current, song?.uniqueId, song?.serverId, song?.streamUrl, transcode]); }, [song?._serverId, song?._uniqueId, song?.streamUrl, current, transcode]);
}; };
export interface AudioPlayerRef { export interface AudioPlayerRef {
@@ -13,7 +13,7 @@ import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Button } from '/@/shared/components/button/button'; import { Button } from '/@/shared/components/button/button';
import { Group } from '/@/shared/components/group/group'; import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon'; import { Icon } from '/@/shared/components/icon/icon';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
import { Play } from '/@/shared/types/types'; import { Play } from '/@/shared/types/types';
export const CardControls = ({ export const CardControls = ({
+5 -2
View File
@@ -3,18 +3,21 @@ import formatDuration from 'format-duration';
import React from 'react'; import React from 'react';
import { generatePath } from 'react-router'; import { generatePath } from 'react-router';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Song } from 'src/main/features/core/lyrics/netease';
import styles from './card-rows.module.css'; import styles from './card-rows.module.css';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { formatDateAbsolute, formatDateRelative, formatRating } from '/@/renderer/utils/format'; import { formatDateAbsolute, formatDateRelative, formatRating } from '/@/renderer/utils/format';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { Album, AlbumArtist, Artist, Playlist, Song } from '/@/shared/types/domain-types'; import { Album } from '/@/shared/types/domain/album-domain-types';
import { Artist } from '/@/shared/types/domain/artist-domain-types';
import { Playlist } from '/@/shared/types/domain/playlist-domain-types';
import { CardRow } from '/@/shared/types/types'; import { CardRow } from '/@/shared/types/types';
interface CardRowsProps { interface CardRowsProps {
data: any; data: any;
rows: CardRow<Album>[] | CardRow<AlbumArtist>[] | CardRow<Artist>[]; rows: CardRow<Album>[] | CardRow<Artist>[];
} }
export const CardRows = ({ data, rows }: CardRowsProps) => { export const CardRows = ({ data, rows }: CardRowsProps) => {
+4 -2
View File
@@ -8,12 +8,14 @@ import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/
import { Image } from '/@/shared/components/image/image'; import { Image } from '/@/shared/components/image/image';
import { Skeleton } from '/@/shared/components/skeleton/skeleton'; import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { Album, AlbumArtist, Artist, LibraryItem } from '/@/shared/types/domain-types'; import { Album } from '/@/shared/types/domain/album-domain-types';
import { Artist } from '/@/shared/types/domain/artist-domain-types';
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
import { CardRoute, CardRow, Play, PlayQueueAddOptions } from '/@/shared/types/types'; import { CardRoute, CardRow, Play, PlayQueueAddOptions } from '/@/shared/types/types';
interface BaseGridCardProps { interface BaseGridCardProps {
controls: { controls: {
cardRows: CardRow<Album>[] | CardRow<AlbumArtist>[] | CardRow<Artist>[]; cardRows: CardRow<Album>[] | CardRow<Artist>[];
handleFavorite: (options: { handleFavorite: (options: {
id: string[]; id: string[];
isFavorite: boolean; isFavorite: boolean;
@@ -20,7 +20,8 @@ import { Image } from '/@/shared/components/image/image';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { TextTitle } from '/@/shared/components/text-title/text-title'; import { TextTitle } from '/@/shared/components/text-title/text-title';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { Album, LibraryItem } from '/@/shared/types/domain-types'; import { Album } from '/@/shared/types/domain/album-domain-types';
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
import { Play } from '/@/shared/types/types'; import { Play } from '/@/shared/types/types';
const variants: Variants = { const variants: Variants = {
@@ -24,13 +24,9 @@ import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon'; import { Icon } from '/@/shared/components/icon/icon';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { TextTitle } from '/@/shared/components/text-title/text-title'; import { TextTitle } from '/@/shared/components/text-title/text-title';
import { import { Album } from '/@/shared/types/domain/album-domain-types';
Album, import { Artist, RelatedArtist } from '/@/shared/types/domain/artist-domain-types';
AlbumArtist, import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
Artist,
LibraryItem,
RelatedArtist,
} from '/@/shared/types/domain-types';
import { CardRoute, CardRow } from '/@/shared/types/types'; import { CardRoute, CardRow } from '/@/shared/types/types';
const getSlidesPerView = (windowWidth: number) => { const getSlidesPerView = (windowWidth: number) => {
@@ -10,20 +10,17 @@ import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/
import { Image } from '/@/shared/components/image/image'; import { Image } from '/@/shared/components/image/image';
import { Skeleton } from '/@/shared/components/skeleton/skeleton'; import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { import { Album } from '/@/shared/types/domain/album-domain-types';
Album, import { Artist } from '/@/shared/types/domain/artist-domain-types';
AlbumArtist, import { Playlist } from '/@/shared/types/domain/playlist-domain-types';
Artist, import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
LibraryItem, import { Song } from '/@/shared/types/domain/song-domain-types';
Playlist,
Song,
} from '/@/shared/types/domain-types';
import { CardRoute, CardRow, Play, PlayQueueAddOptions } from '/@/shared/types/types'; import { CardRoute, CardRow, Play, PlayQueueAddOptions } from '/@/shared/types/types';
interface BaseGridCardProps { interface BaseGridCardProps {
columnIndex: number; columnIndex: number;
controls: { controls: {
cardRows: CardRow<Album | AlbumArtist | Artist | Playlist | Song>[]; cardRows: CardRow<Album | Artist | Playlist | Song>[];
handleFavorite: (options: { handleFavorite: (options: {
id: string[]; id: string[];
isFavorite: boolean; isFavorite: boolean;
@@ -13,7 +13,7 @@ import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Button } from '/@/shared/components/button/button'; import { Button } from '/@/shared/components/button/button';
import { Icon } from '/@/shared/components/icon/icon'; import { Icon } from '/@/shared/components/icon/icon';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
import { Play, PlayQueueAddOptions } from '/@/shared/types/types'; import { Play, PlayQueueAddOptions } from '/@/shared/types/types';
export const GridCardControls = ({ export const GridCardControls = ({
@@ -10,20 +10,17 @@ import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/
import { Image } from '/@/shared/components/image/image'; import { Image } from '/@/shared/components/image/image';
import { Skeleton } from '/@/shared/components/skeleton/skeleton'; import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { import { Album } from '/@/shared/types/domain/album-domain-types';
Album, import { Artist } from '/@/shared/types/domain/artist-domain-types';
AlbumArtist, import { Playlist } from '/@/shared/types/domain/playlist-domain-types';
Artist, import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
LibraryItem, import { Song } from '/@/shared/types/domain/song-domain-types';
Playlist,
Song,
} from '/@/shared/types/domain-types';
import { CardRoute, CardRow, Play, PlayQueueAddOptions } from '/@/shared/types/types'; import { CardRoute, CardRow, Play, PlayQueueAddOptions } from '/@/shared/types/types';
interface BaseGridCardProps { interface BaseGridCardProps {
columnIndex: number; columnIndex: number;
controls: { controls: {
cardRows: CardRow<Album | AlbumArtist | Artist | Playlist | Song>[]; cardRows: CardRow<Album | Artist | Playlist | Song>[];
handleFavorite: (options: { handleFavorite: (options: {
id: string[]; id: string[];
isFavorite: boolean; isFavorite: boolean;
@@ -14,7 +14,9 @@ import { FixedSizeList } from 'react-window';
import styles from './virtual-grid-wrapper.module.css'; import styles from './virtual-grid-wrapper.module.css';
import { GridCard } from '/@/renderer/components/virtual-grid/grid-card'; import { GridCard } from '/@/renderer/components/virtual-grid/grid-card';
import { Album, AlbumArtist, Artist, LibraryItem } from '/@/shared/types/domain-types'; import { Album } from '/@/shared/types/domain/album-domain-types';
import { Artist } from '/@/shared/types/domain/artist-domain-types';
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
const createItemData = memoize( const createItemData = memoize(
( (
@@ -72,7 +74,7 @@ export const VirtualGridWrapper = ({
width, width,
...rest ...rest
}: Omit<FixedSizeListProps, 'children' | 'height' | 'itemSize' | 'ref' | 'width'> & { }: Omit<FixedSizeListProps, 'children' | 'height' | 'itemSize' | 'ref' | 'width'> & {
cardRows: CardRow<Album | AlbumArtist | Artist>[]; cardRows: CardRow<Album | Artist>[];
columnCount: number; columnCount: number;
display: ListDisplayType; display: ListDisplayType;
handleFavorite?: (options: { handleFavorite?: (options: {
@@ -14,7 +14,8 @@ import {
import InfiniteLoader from 'react-window-infinite-loader'; import InfiniteLoader from 'react-window-infinite-loader';
import { VirtualGridWrapper } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; import { VirtualGridWrapper } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper';
import { AnyLibraryItem, Genre, LibraryItem } from '/@/shared/types/domain-types'; import { Genre } from '/@/shared/types/domain/genre-domain-types';
import { AnyLibraryItem, LibraryItem } from '/@/shared/types/domain/shared-domain-types';
import { ListDisplayType } from '/@/shared/types/types'; import { ListDisplayType } from '/@/shared/types/types';
export type VirtualInfiniteGridRef = { export type VirtualInfiniteGridRef = {
@@ -1,4 +1,3 @@
import type { AlbumArtist, Artist } from '/@/shared/types/domain-types';
import type { ICellRendererParams } from '@ag-grid-community/core'; import type { ICellRendererParams } from '@ag-grid-community/core';
import React from 'react'; import React from 'react';
@@ -10,6 +9,7 @@ import { AppRoute } from '/@/renderer/router/routes';
import { Separator } from '/@/shared/components/separator/separator'; import { Separator } from '/@/shared/components/separator/separator';
import { Skeleton } from '/@/shared/components/skeleton/skeleton'; import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { Artist } from '/@/shared/types/domain/artist-domain-types';
export const AlbumArtistCell = ({ data, value }: ICellRendererParams) => { export const AlbumArtistCell = ({ data, value }: ICellRendererParams) => {
if (value === undefined) { if (value === undefined) {
@@ -23,7 +23,7 @@ export const AlbumArtistCell = ({ data, value }: ICellRendererParams) => {
return ( return (
<CellContainer position="left"> <CellContainer position="left">
<Text isMuted overflow="hidden" size="md"> <Text isMuted overflow="hidden" size="md">
{value?.map((item: AlbumArtist | Artist, index: number) => ( {value?.map((item: Artist, index: number) => (
<React.Fragment key={`row-${item.id}-${data.uniqueId}`}> <React.Fragment key={`row-${item.id}-${data.uniqueId}`}>
{index > 0 && <Separator />} {index > 0 && <Separator />}
{item.id ? ( {item.id ? (
@@ -1,4 +1,3 @@
import type { AlbumArtist, Artist } from '/@/shared/types/domain-types';
import type { ICellRendererParams } from '@ag-grid-community/core'; import type { ICellRendererParams } from '@ag-grid-community/core';
import React from 'react'; import React from 'react';
@@ -10,6 +9,7 @@ import { AppRoute } from '/@/renderer/router/routes';
import { Separator } from '/@/shared/components/separator/separator'; import { Separator } from '/@/shared/components/separator/separator';
import { Skeleton } from '/@/shared/components/skeleton/skeleton'; import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { Artist } from '/@/shared/types/domain/artist-domain-types';
export const ArtistCell = ({ data, value }: ICellRendererParams) => { export const ArtistCell = ({ data, value }: ICellRendererParams) => {
if (value === undefined) { if (value === undefined) {
@@ -23,7 +23,7 @@ export const ArtistCell = ({ data, value }: ICellRendererParams) => {
return ( return (
<CellContainer position="left"> <CellContainer position="left">
<Text isMuted overflow="hidden" size="md"> <Text isMuted overflow="hidden" size="md">
{value?.map((item: AlbumArtist | Artist, index: number) => ( {value?.map((item: Artist, index: number) => (
<React.Fragment key={`row-${item.id}-${data.uniqueId}`}> <React.Fragment key={`row-${item.id}-${data.uniqueId}`}>
{index > 0 && <Separator />} {index > 0 && <Separator />}
{item.id ? ( {item.id ? (
@@ -5,7 +5,7 @@ import styles from './combined-title-cell-controls.module.css';
import { usePlayQueueAdd } from '/@/renderer/features/player'; import { usePlayQueueAdd } from '/@/renderer/features/player';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
import { Play } from '/@/shared/types/types'; import { Play } from '/@/shared/types/types';
export const ListCoverControls = ({ export const ListCoverControls = ({
@@ -12,7 +12,7 @@ import { SEPARATOR_STRING } from '/@/shared/api/utils';
import { Image } from '/@/shared/components/image/image'; import { Image } from '/@/shared/components/image/image';
import { Skeleton } from '/@/shared/components/skeleton/skeleton'; import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { AlbumArtist, Artist } from '/@/shared/types/domain-types'; import { Artist } from '/@/shared/types/domain/artist-domain-types';
export const CombinedTitleCell = ({ export const CombinedTitleCell = ({
context, context,
@@ -74,7 +74,7 @@ export const CombinedTitleCell = ({
</Text> </Text>
<Text isMuted overflow="hidden" size="md"> <Text isMuted overflow="hidden" size="md">
{artists?.length ? ( {artists?.length ? (
artists.map((artist: AlbumArtist | Artist, index: number) => ( artists.map((artist: Artist, index: number) => (
<React.Fragment key={`queue-${rowIndex}-artist-${artist.id}`}> <React.Fragment key={`queue-${rowIndex}-artist-${artist.id}`}>
{index > 0 ? SEPARATOR_STRING : null} {index > 0 ? SEPARATOR_STRING : null}
{artist.id ? ( {artist.id ? (
@@ -1,4 +1,3 @@
import type { AlbumArtist, Artist } from '/@/shared/types/domain-types';
import type { ICellRendererParams } from '@ag-grid-community/core'; import type { ICellRendererParams } from '@ag-grid-community/core';
import React from 'react'; import React from 'react';
@@ -8,13 +7,14 @@ import { CellContainer } from '/@/renderer/components/virtual-table/cells/generi
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route'; import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';
import { Separator } from '/@/shared/components/separator/separator'; import { Separator } from '/@/shared/components/separator/separator';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { Artist } from '/@/shared/types/domain/artist-domain-types';
export const GenreCell = ({ data, value }: ICellRendererParams) => { export const GenreCell = ({ data, value }: ICellRendererParams) => {
const genrePath = useGenreRoute(); const genrePath = useGenreRoute();
return ( return (
<CellContainer position="left"> <CellContainer position="left">
<Text isMuted overflow="hidden" size="md"> <Text isMuted overflow="hidden" size="md">
{value?.map((item: AlbumArtist | Artist, index: number) => ( {value?.map((item: Artist, index: number) => (
<React.Fragment key={`row-${item.id}-${data.uniqueId}`}> <React.Fragment key={`row-${item.id}-${data.uniqueId}`}>
{index > 0 && <Separator />} {index > 0 && <Separator />}
<Text <Text
@@ -5,7 +5,7 @@ import { MutableRefObject, useEffect, useMemo, useRef } from 'react';
import { useAppFocus } from '/@/renderer/hooks'; import { useAppFocus } from '/@/renderer/hooks';
import { useCurrentSong, usePlayerStore } from '/@/renderer/store'; import { useCurrentSong, usePlayerStore } from '/@/renderer/store';
import { Song } from '/@/shared/types/domain-types'; import { Song } from '/@/shared/types/domain/song-domain-types';
import { PlayerStatus } from '/@/shared/types/types'; import { PlayerStatus } from '/@/shared/types/types';
interface UseCurrentSongRowStylesProps { interface UseCurrentSongRowStylesProps {
@@ -24,11 +24,11 @@ import { AppRoute } from '/@/renderer/router/routes';
import { PersistedTableColumn, useListStoreActions } from '/@/renderer/store'; import { PersistedTableColumn, useListStoreActions } from '/@/renderer/store';
import { ListKey, useListStoreByKey } from '/@/renderer/store/list.store'; import { ListKey, useListStoreByKey } from '/@/renderer/store/list.store';
import { import {
BasePaginatedQuery,
BasePaginatedResponse, BasePaginatedResponse,
BaseQuery, } from '/@/shared/types/adapter/api-controller-types';
LibraryItem, import { ServerListItem } from '/@/shared/types/domain/server-domain-types';
ServerListItem, import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
} from '/@/shared/types/domain-types';
import { ListDisplayType, TablePagination } from '/@/shared/types/types'; import { ListDisplayType, TablePagination } from '/@/shared/types/types';
export type AgGridFetchFn<TResponse, TFilter> = ( export type AgGridFetchFn<TResponse, TFilter> = (
@@ -52,7 +52,7 @@ interface UseAgGridProps<TFilter> {
const BLOCK_SIZE = 500; const BLOCK_SIZE = 500;
export const useVirtualTable = <TFilter extends BaseQuery<any>>({ export const useVirtualTable = <TFilter extends BasePaginatedQuery<any>>({
columnType, columnType,
contextMenu, contextMenu,
customFilters, customFilters,
@@ -18,7 +18,7 @@ import { Icon } from '/@/shared/components/icon/icon';
import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area'; import { ScrollArea } from '/@/shared/components/scroll-area/scroll-area';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { ServerListItem, ServerType } from '/@/shared/types/domain-types'; import { ServerListItem, ServerType } from '/@/shared/types/domain/server-domain-types';
const localSettings = isElectron() ? window.api.localSettings : null; const localSettings = isElectron() ? window.api.localSettings : null;
@@ -0,0 +1,45 @@
import { queryOptions, UseQueryOptions } from '@tanstack/react-query';
import { api } from '/@/renderer/api/api-controller';
import { AlbumListRequest } from '/@/shared/types/domain/album-domain-types';
export const getAlbumListQueryKey = (serverId: string, request?: AlbumListRequest) => {
if (!request) {
return [serverId, 'albums'];
}
return [serverId, 'albums', request];
};
export const getInfiniteAlbumListQueryKey = (serverId: string, request?: AlbumListRequest) => {
if (!request) {
return [serverId, 'albums', 'infinite'];
}
return [serverId, 'albums', 'infinite', request];
};
export const getAlbumList = async (serverId: string, request: AlbumListRequest) => {
const [error, response] = await api.controller[serverId]!.album.getList!({
query: request.query,
});
if (error) {
throw new Error(error.message);
}
return response;
};
export const getAlbumListQuery = (
serverId: string,
request: AlbumListRequest,
options?: UseQueryOptions,
) => {
return queryOptions({
enabled: !!serverId,
queryFn: () => getAlbumList(serverId, request),
queryKey: getAlbumListQueryKey(serverId, request),
...options,
});
};
@@ -49,13 +49,9 @@ import { Group } from '/@/shared/components/group/group';
import { Popover } from '/@/shared/components/popover/popover'; import { Popover } from '/@/shared/components/popover/popover';
import { Spoiler } from '/@/shared/components/spoiler/spoiler'; import { Spoiler } from '/@/shared/components/spoiler/spoiler';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { import { AlbumListQuery, AlbumListSort } from '/@/shared/types/domain/album-domain-types';
AlbumListQuery, import { QueueSong } from '/@/shared/types/domain/player-domain-types';
AlbumListSort, import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
LibraryItem,
QueueSong,
SortOrder,
} from '/@/shared/types/domain-types';
import { Play } from '/@/shared/types/types'; import { Play } from '/@/shared/types/types';
const isFullWidthRow = (node: RowNode) => { const isFullWidthRow = (node: RowNode) => {
@@ -154,8 +150,8 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
const artistQuery = useAlbumList({ const artistQuery = useAlbumList({
options: { options: {
cacheTime: 1000 * 60,
enabled: detailQuery?.data?.albumArtists[0]?.id !== undefined, enabled: detailQuery?.data?.albumArtists[0]?.id !== undefined,
gcTime: 1000 * 60,
keepPreviousData: true, keepPreviousData: true,
staleTime: 1000 * 60, staleTime: 1000 * 60,
}, },
@@ -169,9 +165,9 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
? [detailQuery?.data?.albumArtists[0].id] ? [detailQuery?.data?.albumArtists[0].id]
: undefined, : undefined,
limit: 15, limit: 15,
offset: 0,
sortBy: AlbumListSort.YEAR, sortBy: AlbumListSort.YEAR,
sortOrder: SortOrder.DESC, sortOrder: ListSortOrder.DESC,
startIndex: 0,
}, },
serverId: server?.id, serverId: server?.id,
}); });
@@ -179,15 +175,15 @@ export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentP
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,
limit: 15, limit: 15,
offset: 0,
sortBy: AlbumListSort.RANDOM, sortBy: AlbumListSort.RANDOM,
sortOrder: SortOrder.ASC, sortOrder: ListSortOrder.ASC,
startIndex: 0,
}; };
const relatedAlbumGenresQuery = useAlbumList({ const relatedAlbumGenresQuery = useAlbumList({
options: { options: {
cacheTime: 1000 * 60,
enabled: !!detailQuery?.data?.genres?.[0], enabled: !!detailQuery?.data?.genres?.[0],
gcTime: 1000 * 60,
queryKey: queryKeys.albums.related( queryKey: queryKeys.albums.related(
server?.id || '', server?.id || '',
albumId, albumId,
@@ -16,7 +16,9 @@ import { Group } from '/@/shared/components/group/group';
import { Rating } from '/@/shared/components/rating/rating'; import { Rating } from '/@/shared/components/rating/rating';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { AlbumDetailResponse, LibraryItem, ServerType } from '/@/shared/types/domain-types'; import { AlbumDetailResponse } from '/@/shared/types/domain/album-domain-types';
import { ServerType } from '/@/shared/types/domain/server-domain-types';
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
interface AlbumDetailHeaderProps { interface AlbumDetailHeaderProps {
background: { background: {
@@ -21,8 +21,8 @@ import {
AlbumListQuery, AlbumListQuery,
AlbumListResponse, AlbumListResponse,
AlbumListSort, AlbumListSort,
LibraryItem, } from '/@/shared/types/domain/album-domain-types';
} from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
import { CardRow, ListDisplayType } from '/@/shared/types/types'; import { CardRow, ListDisplayType } from '/@/shared/types/types';
export const AlbumListGridView = ({ gridRef, itemCount }: any) => { export const AlbumListGridView = ({ gridRef, itemCount }: any) => {
@@ -137,15 +137,11 @@ export const AlbumListGridView = ({ gridRef, itemCount }: any) => {
const itemData: Album[] = []; const itemData: Album[] = [];
for (const [, data] of queriesFromCache) { for (const [, data] of queriesFromCache) {
const { items, startIndex } = data || {}; const { items, offset } = data || {};
if (items && items.length !== 1 && startIndex !== undefined) { if (items && items.length !== 1 && offset !== undefined) {
let itemIndex = 0; let itemIndex = 0;
for ( for (let rowIndex = offset; rowIndex < offset + items.length; rowIndex += 1) {
let rowIndex = startIndex;
rowIndex < startIndex + items.length;
rowIndex += 1
) {
itemData[rowIndex] = items[itemIndex]; itemData[rowIndex] = items[itemIndex];
itemIndex += 1; itemIndex += 1;
} }
@@ -165,7 +161,7 @@ export const AlbumListGridView = ({ gridRef, itemCount }: any) => {
limit: take, limit: take,
...filter, ...filter,
...customFilters, ...customFilters,
startIndex: skip, offset: skip,
}; };
const queryKey = queryKeys.albums.list(server?.id || '', query, id); const queryKey = queryKeys.albums.list(server?.id || '', query, id);
@@ -34,158 +34,154 @@ import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
import { Flex } from '/@/shared/components/flex/flex'; import { Flex } from '/@/shared/components/flex/flex';
import { Group } from '/@/shared/components/group/group'; import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon'; import { Icon } from '/@/shared/components/icon/icon';
import { import { AlbumListQuery, AlbumListSort } from '/@/shared/types/domain/album-domain-types';
AlbumListQuery, import { ServerType } from '/@/shared/types/domain/server-domain-types';
AlbumListSort, import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
LibraryItem,
ServerType,
SortOrder,
} from '/@/shared/types/domain-types';
import { ListDisplayType, Play, TableColumn } from '/@/shared/types/types'; import { ListDisplayType, Play, TableColumn } from '/@/shared/types/types';
const FILTERS = { const FILTERS = {
jellyfin: [ jellyfin: [
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.albumArtist', { postProcess: 'titleCase' }), name: i18n.t('filter.albumArtist', { postProcess: 'titleCase' }),
value: AlbumListSort.ALBUM_ARTIST, value: AlbumListSort.ALBUM_ARTIST,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.communityRating', { postProcess: 'titleCase' }), name: i18n.t('filter.communityRating', { postProcess: 'titleCase' }),
value: AlbumListSort.COMMUNITY_RATING, value: AlbumListSort.COMMUNITY_RATING,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.criticRating', { postProcess: 'titleCase' }), name: i18n.t('filter.criticRating', { postProcess: 'titleCase' }),
value: AlbumListSort.CRITIC_RATING, value: AlbumListSort.CRITIC_RATING,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.name', { postProcess: 'titleCase' }), name: i18n.t('filter.name', { postProcess: 'titleCase' }),
value: AlbumListSort.NAME, value: AlbumListSort.NAME,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.playCount', { postProcess: 'titleCase' }), name: i18n.t('filter.playCount', { postProcess: 'titleCase' }),
value: AlbumListSort.PLAY_COUNT, value: AlbumListSort.PLAY_COUNT,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.random', { postProcess: 'titleCase' }), name: i18n.t('filter.random', { postProcess: 'titleCase' }),
value: AlbumListSort.RANDOM, value: AlbumListSort.RANDOM,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }), name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }),
value: AlbumListSort.RECENTLY_ADDED, value: AlbumListSort.RECENTLY_ADDED,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.releaseDate', { postProcess: 'titleCase' }), name: i18n.t('filter.releaseDate', { postProcess: 'titleCase' }),
value: AlbumListSort.RELEASE_DATE, value: AlbumListSort.RELEASE_DATE,
}, },
], ],
navidrome: [ navidrome: [
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.albumArtist', { postProcess: 'titleCase' }), name: i18n.t('filter.albumArtist', { postProcess: 'titleCase' }),
value: AlbumListSort.ALBUM_ARTIST, value: AlbumListSort.ALBUM_ARTIST,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.artist', { postProcess: 'titleCase' }), name: i18n.t('filter.artist', { postProcess: 'titleCase' }),
value: AlbumListSort.ARTIST, value: AlbumListSort.ARTIST,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.duration', { postProcess: 'titleCase' }), name: i18n.t('filter.duration', { postProcess: 'titleCase' }),
value: AlbumListSort.DURATION, value: AlbumListSort.DURATION,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.mostPlayed', { postProcess: 'titleCase' }), name: i18n.t('filter.mostPlayed', { postProcess: 'titleCase' }),
value: AlbumListSort.PLAY_COUNT, value: AlbumListSort.PLAY_COUNT,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.name', { postProcess: 'titleCase' }), name: i18n.t('filter.name', { postProcess: 'titleCase' }),
value: AlbumListSort.NAME, value: AlbumListSort.NAME,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.random', { postProcess: 'titleCase' }), name: i18n.t('filter.random', { postProcess: 'titleCase' }),
value: AlbumListSort.RANDOM, value: AlbumListSort.RANDOM,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.rating', { postProcess: 'titleCase' }), name: i18n.t('filter.rating', { postProcess: 'titleCase' }),
value: AlbumListSort.RATING, value: AlbumListSort.RATING,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }), name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }),
value: AlbumListSort.RECENTLY_ADDED, value: AlbumListSort.RECENTLY_ADDED,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.recentlyPlayed', { postProcess: 'titleCase' }), name: i18n.t('filter.recentlyPlayed', { postProcess: 'titleCase' }),
value: AlbumListSort.RECENTLY_PLAYED, value: AlbumListSort.RECENTLY_PLAYED,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.songCount', { postProcess: 'titleCase' }), name: i18n.t('filter.songCount', { postProcess: 'titleCase' }),
value: AlbumListSort.SONG_COUNT, value: AlbumListSort.SONG_COUNT,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.favorited', { postProcess: 'titleCase' }), name: i18n.t('filter.favorited', { postProcess: 'titleCase' }),
value: AlbumListSort.FAVORITED, value: AlbumListSort.FAVORITED,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.releaseYear', { postProcess: 'titleCase' }), name: i18n.t('filter.releaseYear', { postProcess: 'titleCase' }),
value: AlbumListSort.YEAR, value: AlbumListSort.YEAR,
}, },
], ],
subsonic: [ subsonic: [
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.albumArtist', { postProcess: 'titleCase' }), name: i18n.t('filter.albumArtist', { postProcess: 'titleCase' }),
value: AlbumListSort.ALBUM_ARTIST, value: AlbumListSort.ALBUM_ARTIST,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.mostPlayed', { postProcess: 'titleCase' }), name: i18n.t('filter.mostPlayed', { postProcess: 'titleCase' }),
value: AlbumListSort.PLAY_COUNT, value: AlbumListSort.PLAY_COUNT,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.name', { postProcess: 'titleCase' }), name: i18n.t('filter.name', { postProcess: 'titleCase' }),
value: AlbumListSort.NAME, value: AlbumListSort.NAME,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.random', { postProcess: 'titleCase' }), name: i18n.t('filter.random', { postProcess: 'titleCase' }),
value: AlbumListSort.RANDOM, value: AlbumListSort.RANDOM,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }), name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }),
value: AlbumListSort.RECENTLY_ADDED, value: AlbumListSort.RECENTLY_ADDED,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.recentlyPlayed', { postProcess: 'titleCase' }), name: i18n.t('filter.recentlyPlayed', { postProcess: 'titleCase' }),
value: AlbumListSort.RECENTLY_PLAYED, value: AlbumListSort.RECENTLY_PLAYED,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.favorited', { postProcess: 'titleCase' }), name: i18n.t('filter.favorited', { postProcess: 'titleCase' }),
value: AlbumListSort.FAVORITED, value: AlbumListSort.FAVORITED,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.releaseYear', { postProcess: 'titleCase' }), name: i18n.t('filter.releaseYear', { postProcess: 'titleCase' }),
value: AlbumListSort.YEAR, value: AlbumListSort.YEAR,
}, },
@@ -299,7 +295,7 @@ export const AlbumListHeaderFilters = ({
customFilters, customFilters,
data: { data: {
sortBy: e.currentTarget.value as AlbumListSort, sortBy: e.currentTarget.value as AlbumListSort,
sortOrder: sortOrder || SortOrder.ASC, sortOrder: sortOrder || ListSortOrder.ASC,
}, },
itemType: LibraryItem.ALBUM, itemType: LibraryItem.ALBUM,
key: pageKey, key: pageKey,
@@ -337,7 +333,8 @@ export const AlbumListHeaderFilters = ({
); );
const handleToggleSortOrder = useCallback(() => { const handleToggleSortOrder = useCallback(() => {
const newSortOrder = filter.sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC; const newSortOrder =
filter.sortOrder === ListSortOrder.ASC ? ListSortOrder.DESC : ListSortOrder.ASC;
const updatedFilters = setFilter({ const updatedFilters = setFilter({
customFilters, customFilters,
data: { sortOrder: newSortOrder }, data: { sortOrder: newSortOrder },
@@ -16,7 +16,8 @@ import { titleCase } from '/@/renderer/utils';
import { Flex } from '/@/shared/components/flex/flex'; import { Flex } from '/@/shared/components/flex/flex';
import { Group } from '/@/shared/components/group/group'; import { Group } from '/@/shared/components/group/group';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { AlbumListQuery, LibraryItem } from '/@/shared/types/domain-types'; import { AlbumListQuery } from '/@/shared/types/domain/album-domain-types';
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
interface AlbumListHeaderProps { interface AlbumListHeaderProps {
genreId?: string; genreId?: string;
@@ -4,7 +4,7 @@ import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-
import { useListContext } from '/@/renderer/context/list-context'; import { useListContext } from '/@/renderer/context/list-context';
import { ALBUM_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; import { ALBUM_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
export const AlbumListTableView = ({ itemCount, tableRef }: any) => { export const AlbumListTableView = ({ itemCount, tableRef }: any) => {
const server = useCurrentServer(); const server = useCurrentServer();
@@ -14,13 +14,10 @@ import { SpinnerIcon } from '/@/shared/components/spinner/spinner';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { YesNoSelect } from '/@/shared/components/yes-no-select/yes-no-select'; import { YesNoSelect } from '/@/shared/components/yes-no-select/yes-no-select';
import { import { AlbumListQuery } from '/@/shared/types/domain/album-domain-types';
AlbumArtistListSort, import { AlbumArtistListSort } from '/@/shared/types/domain/artist-domain-types';
AlbumListQuery, import { GenreListSort } from '/@/shared/types/domain/genre-domain-types';
GenreListSort, import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
LibraryItem,
SortOrder,
} from '/@/shared/types/domain-types';
interface JellyfinAlbumFiltersProps { interface JellyfinAlbumFiltersProps {
customFilters?: Partial<AlbumListFilter>; customFilters?: Partial<AlbumListFilter>;
@@ -44,14 +41,14 @@ export const JellyfinAlbumFilters = ({
// 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 = useGenreList({
options: { options: {
cacheTime: 1000 * 60 * 2, gcTime: 1000 * 60 * 2,
staleTime: 1000 * 60 * 1, staleTime: 1000 * 60 * 1,
}, },
query: { query: {
musicFolderId: filter?.musicFolderId, musicFolderId: filter?.musicFolderId,
sortBy: GenreListSort.NAME, sortBy: GenreListSort.NAME,
sortOrder: SortOrder.ASC, sortOrder: ListSortOrder.ASC,
startIndex: 0, offset: 0,
}, },
serverId, serverId,
}); });
@@ -66,7 +63,7 @@ export const JellyfinAlbumFilters = ({
const tagsQuery = useTagList({ const tagsQuery = useTagList({
options: { options: {
cacheTime: 1000 * 60 * 2, gcTime: 1000 * 60 * 2,
staleTime: 1000 * 60 * 1, staleTime: 1000 * 60 * 1,
}, },
query: { query: {
@@ -173,12 +170,12 @@ export const JellyfinAlbumFilters = ({
const albumArtistListQuery = useAlbumArtistList({ const albumArtistListQuery = useAlbumArtistList({
options: { options: {
cacheTime: 1000 * 60 * 2, gcTime: 1000 * 60 * 2,
staleTime: 1000 * 60 * 1, staleTime: 1000 * 60 * 1,
}, },
query: { query: {
sortBy: AlbumArtistListSort.NAME, sortBy: AlbumArtistListSort.NAME,
sortOrder: SortOrder.ASC, sortOrder: ListSortOrder.ASC,
startIndex: 0, startIndex: 0,
}, },
serverId, serverId,
@@ -16,13 +16,10 @@ import { Stack } from '/@/shared/components/stack/stack';
import { Switch } from '/@/shared/components/switch/switch'; import { Switch } from '/@/shared/components/switch/switch';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { YesNoSelect } from '/@/shared/components/yes-no-select/yes-no-select'; import { YesNoSelect } from '/@/shared/components/yes-no-select/yes-no-select';
import { import { AlbumListQuery } from '/@/shared/types/domain/album-domain-types';
AlbumArtistListSort, import { AlbumArtistListSort } from '/@/shared/types/domain/artist-domain-types';
AlbumListQuery, import { GenreListSort } from '/@/shared/types/domain/genre-domain-types';
GenreListSort, import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
LibraryItem,
SortOrder,
} from '/@/shared/types/domain-types';
interface NavidromeAlbumFiltersProps { interface NavidromeAlbumFiltersProps {
customFilters?: Partial<AlbumListFilter>; customFilters?: Partial<AlbumListFilter>;
@@ -45,13 +42,13 @@ export const NavidromeAlbumFilters = ({
const genreListQuery = useGenreList({ const genreListQuery = useGenreList({
options: { options: {
cacheTime: 1000 * 60 * 2, gcTime: 1000 * 60 * 2,
staleTime: 1000 * 60 * 1, staleTime: 1000 * 60 * 1,
}, },
query: { query: {
sortBy: GenreListSort.NAME, sortBy: GenreListSort.NAME,
sortOrder: SortOrder.ASC, sortOrder: ListSortOrder.ASC,
startIndex: 0, offset: 0,
}, },
serverId, serverId,
}); });
@@ -79,7 +76,7 @@ export const NavidromeAlbumFilters = ({
const tagsQuery = useTagList({ const tagsQuery = useTagList({
options: { options: {
cacheTime: 1000 * 60 * 2, gcTime: 1000 * 60 * 2,
staleTime: 1000 * 60 * 1, staleTime: 1000 * 60 * 1,
}, },
query: { query: {
@@ -188,13 +185,13 @@ export const NavidromeAlbumFilters = ({
const albumArtistListQuery = useAlbumArtistList({ const albumArtistListQuery = useAlbumArtistList({
options: { options: {
cacheTime: 1000 * 60 * 2, gcTime: 1000 * 60 * 2,
staleTime: 1000 * 60 * 1, staleTime: 1000 * 60 * 1,
}, },
query: { query: {
// searchTerm: debouncedSearchTerm, // searchTerm: debouncedSearchTerm,
sortBy: AlbumArtistListSort.NAME, sortBy: AlbumArtistListSort.NAME,
sortOrder: SortOrder.ASC, sortOrder: ListSortOrder.ASC,
startIndex: 0, startIndex: 0,
}, },
serverId, serverId,
@@ -14,13 +14,10 @@ import { SpinnerIcon } from '/@/shared/components/spinner/spinner';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { Switch } from '/@/shared/components/switch/switch'; import { Switch } from '/@/shared/components/switch/switch';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { import { AlbumListQuery } from '/@/shared/types/domain/album-domain-types';
AlbumArtistListSort, import { AlbumArtistListSort } from '/@/shared/types/domain/artist-domain-types';
AlbumListQuery, import { GenreListSort } from '/@/shared/types/domain/genre-domain-types';
GenreListSort, import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
LibraryItem,
SortOrder,
} from '/@/shared/types/domain-types';
interface SubsonicAlbumFiltersProps { interface SubsonicAlbumFiltersProps {
disableArtistFilter?: boolean; disableArtistFilter?: boolean;
@@ -42,12 +39,12 @@ export const SubsonicAlbumFilters = ({
const albumArtistListQuery = useAlbumArtistList({ const albumArtistListQuery = useAlbumArtistList({
options: { options: {
cacheTime: 1000 * 60 * 2, gcTime: 1000 * 60 * 2,
staleTime: 1000 * 60 * 1, staleTime: 1000 * 60 * 1,
}, },
query: { query: {
sortBy: AlbumArtistListSort.NAME, sortBy: AlbumArtistListSort.NAME,
sortOrder: SortOrder.ASC, sortOrder: ListSortOrder.ASC,
startIndex: 0, startIndex: 0,
}, },
serverId, serverId,
@@ -75,13 +72,13 @@ export const SubsonicAlbumFilters = ({
const genreListQuery = useGenreList({ const genreListQuery = useGenreList({
options: { options: {
cacheTime: 1000 * 60 * 2, gcTime: 1000 * 60 * 2,
staleTime: 1000 * 60 * 1, staleTime: 1000 * 60 * 1,
}, },
query: { query: {
sortBy: GenreListSort.NAME, sortBy: GenreListSort.NAME,
sortOrder: SortOrder.ASC, sortOrder: ListSortOrder.ASC,
startIndex: 0, offset: 0,
}, },
serverId, serverId,
}); });
@@ -1,15 +1,15 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query'; import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import type { AlbumDetailQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { controller } from '/@/renderer/api/controller'; import { controller } from '/@/renderer/api/controller';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { getServerById } from '/@/renderer/store'; import { useServerById } from '/@/renderer/store';
import { AlbumDetailQuery } from '/@/shared/types/domain/album-domain-types';
export const useAlbumDetail = (args: QueryHookArgs<AlbumDetailQuery>) => { export const useAlbumDetail = (args: RQueryHookArgs<AlbumDetailQuery>) => {
const { options, query, serverId } = args; const { options, query, serverId } = args;
const server = getServerById(serverId); const server = useServerById(serverId);
return useQuery({ return useQuery({
queryFn: ({ signal }) => { queryFn: ({ signal }) => {
@@ -1,15 +1,15 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query'; import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import type { AlbumListQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
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 { getServerById } from '/@/renderer/store'; import { useServerById } from '/@/renderer/store';
import { AlbumListQuery } from '/@/shared/types/domain/album-domain-types';
export const useAlbumListCount = (args: QueryHookArgs<AlbumListQuery>) => { export const useAlbumListCount = (args: RQueryHookArgs<AlbumListQuery>) => {
const { options, query, serverId } = args; const { options, query, serverId } = args;
const server = getServerById(serverId); const server = useServerById(serverId);
return useQuery({ return useQuery({
enabled: !!serverId, enabled: !!serverId,
@@ -1,16 +1,16 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query'; import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import type { AlbumListQuery, AlbumListResponse } from '/@/shared/types/domain-types';
import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api'; import { api } from '/@/renderer/api';
import { controller } from '/@/renderer/api/controller'; import { controller } from '/@/renderer/api/controller';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
import { getServerById } from '/@/renderer/store'; import { useServerById } from '/@/renderer/store';
import { AlbumListQuery, AlbumListResponse } from '/@/shared/types/domain/album-domain-types';
export const useAlbumList = (args: QueryHookArgs<AlbumListQuery>) => { export const useAlbumList = (args: RQueryHookArgs<AlbumListQuery>) => {
const { options, query, serverId } = args; const { options, query, serverId } = args;
const server = getServerById(serverId); const server = useServerById(serverId);
return useQuery({ return useQuery({
enabled: !!serverId, enabled: !!serverId,
@@ -33,9 +33,9 @@ export const useAlbumList = (args: QueryHookArgs<AlbumListQuery>) => {
}); });
}; };
export const useAlbumListInfinite = (args: QueryHookArgs<AlbumListQuery>) => { export const useAlbumListInfinite = (args: RQueryHookArgs<AlbumListQuery>) => {
const { options, query, serverId } = args; const { options, query, serverId } = args;
const server = getServerById(serverId); const server = useServerById(serverId);
return useInfiniteQuery({ return useInfiniteQuery({
enabled: !!serverId, enabled: !!serverId,
@@ -57,7 +57,7 @@ export const useAlbumListInfinite = (args: QueryHookArgs<AlbumListQuery>) => {
query: { query: {
...query, ...query,
limit: query.limit || 50, limit: query.limit || 50,
startIndex: pageParam * (query.limit || 50), offset: pageParam * (query.limit || 50),
}, },
}); });
}, },
@@ -12,7 +12,7 @@ import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
import { useFastAverageColor } from '/@/renderer/hooks'; import { useFastAverageColor } from '/@/renderer/hooks';
import { useCurrentServer, useGeneralSettings } from '/@/renderer/store'; import { useCurrentServer, useGeneralSettings } from '/@/renderer/store';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
const AlbumDetailRoute = () => { const AlbumDetailRoute = () => {
const tableRef = useRef<AgGridReactType | null>(null); const tableRef = useRef<AgGridReactType | null>(null);
@@ -16,12 +16,9 @@ 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';
import { useCurrentServer, useListFilterByKey } from '/@/renderer/store'; import { useCurrentServer, useListFilterByKey } from '/@/renderer/store';
import { import { AlbumListQuery } from '/@/shared/types/domain/album-domain-types';
AlbumListQuery, import { GenreListSort } from '/@/shared/types/domain/genre-domain-types';
GenreListSort, import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
LibraryItem,
SortOrder,
} from '/@/shared/types/domain-types';
import { Play } from '/@/shared/types/types'; import { Play } from '/@/shared/types/types';
const AlbumListRoute = () => { const AlbumListRoute = () => {
@@ -55,13 +52,13 @@ const AlbumListRoute = () => {
const genreList = useGenreList({ const genreList = useGenreList({
options: { options: {
cacheTime: 1000 * 60 * 60, gcTime: 1000 * 60 * 60,
enabled: !!genreId, enabled: !!genreId,
}, },
query: { query: {
sortBy: GenreListSort.NAME, sortBy: GenreListSort.NAME,
sortOrder: SortOrder.ASC, sortOrder: ListSortOrder.ASC,
startIndex: 0, offset: 0,
}, },
serverId: server?.id, serverId: server?.id,
}); });
@@ -77,7 +74,7 @@ const AlbumListRoute = () => {
const itemCountCheck = useAlbumListCount({ const itemCountCheck = useAlbumListCount({
options: { options: {
cacheTime: 1000 * 60, gcTime: 1000 * 60,
staleTime: 1000 * 60, staleTime: 1000 * 60,
}, },
query: { query: {
@@ -33,7 +33,8 @@ import { Icon } from '/@/shared/components/icon/icon';
import { Spoiler } from '/@/shared/components/spoiler/spoiler'; import { Spoiler } from '/@/shared/components/spoiler/spoiler';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { LibraryItem, SongDetailResponse } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
import { SongDetailResponse } from '/@/shared/types/domain/song-domain-types';
const DummyAlbumDetailRoute = () => { const DummyAlbumDetailRoute = () => {
const cq = useContainerQuery(); const cq = useContainerQuery();
@@ -35,15 +35,10 @@ import { Group } from '/@/shared/components/group/group';
import { Spoiler } from '/@/shared/components/spoiler/spoiler'; import { Spoiler } from '/@/shared/components/spoiler/spoiler';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { TextTitle } from '/@/shared/components/text-title/text-title'; import { TextTitle } from '/@/shared/components/text-title/text-title';
import { import { Album, AlbumListSort } from '/@/shared/types/domain/album-domain-types';
Album, import { QueueSong } from '/@/shared/types/domain/player-domain-types';
AlbumArtist, import { ServerType } from '/@/shared/types/domain/server-domain-types';
AlbumListSort, import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
LibraryItem,
QueueSong,
ServerType,
SortOrder,
} from '/@/shared/types/domain-types';
import { CardRow, Play, TableColumn } from '/@/shared/types/types'; import { CardRow, Play, TableColumn } from '/@/shared/types/types';
interface AlbumArtistDetailContentProps { interface AlbumArtistDetailContentProps {
@@ -106,8 +101,8 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
compilation: false, compilation: false,
limit: 15, limit: 15,
sortBy: AlbumListSort.RELEASE_DATE, sortBy: AlbumListSort.RELEASE_DATE,
sortOrder: SortOrder.DESC, sortOrder: ListSortOrder.DESC,
startIndex: 0, offset: 0,
}, },
serverId: server?.id, serverId: server?.id,
}); });
@@ -121,8 +116,8 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
compilation: true, compilation: true,
limit: 15, limit: 15,
sortBy: AlbumListSort.RELEASE_DATE, sortBy: AlbumListSort.RELEASE_DATE,
sortOrder: SortOrder.DESC, sortOrder: ListSortOrder.DESC,
startIndex: 0, offset: 0,
}, },
serverId: server?.id, serverId: server?.id,
}); });
@@ -11,7 +11,8 @@ import { Group } from '/@/shared/components/group/group';
import { Rating } from '/@/shared/components/rating/rating'; import { Rating } from '/@/shared/components/rating/rating';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { LibraryItem, ServerType } from '/@/shared/types/domain-types'; import { ServerType } from '/@/shared/types/domain/server-domain-types';
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
interface AlbumArtistDetailHeaderProps { interface AlbumArtistDetailHeaderProps {
background?: string; background?: string;
@@ -12,7 +12,9 @@ import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/conte
import { usePlayQueueAdd } from '/@/renderer/features/player'; import { usePlayQueueAdd } from '/@/renderer/features/player';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { LibraryItem, QueueSong, SongListQuery } from '/@/shared/types/domain-types'; import { QueueSong } from '/@/shared/types/domain/player-domain-types';
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
import { SongListQuery } from '/@/shared/types/domain/song-domain-types';
interface AlbumArtistSongListContentProps { interface AlbumArtistSongListContentProps {
data: QueueSong[]; data: QueueSong[];
@@ -6,7 +6,7 @@ import { LibraryHeaderBar } from '/@/renderer/features/shared';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
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 { QueueSong } from '/@/shared/types/domain-types'; import { QueueSong } from '/@/shared/types/domain/player-domain-types';
import { Play } from '/@/shared/types/types'; import { Play } from '/@/shared/types/types';
interface AlbumArtistDetailTopSongsListHeaderProps { interface AlbumArtistDetailTopSongsListHeaderProps {
@@ -17,12 +17,11 @@ import { useHandleFavorite } from '/@/renderer/features/shared/hooks/use-handle-
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';
import { import {
AlbumArtist,
AlbumArtistListQuery, AlbumArtistListQuery,
AlbumArtistListResponse, AlbumArtistListResponse,
AlbumArtistListSort, AlbumArtistListSort,
LibraryItem, } from '/@/shared/types/domain/artist-domain-types';
} from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
import { CardRow, ListDisplayType } from '/@/shared/types/types'; import { CardRow, ListDisplayType } from '/@/shared/types/types';
interface AlbumArtistListGridViewProps { interface AlbumArtistListGridViewProps {
@@ -34,91 +34,90 @@ import { Icon } from '/@/shared/components/icon/icon';
import { import {
AlbumArtistListQuery, AlbumArtistListQuery,
AlbumArtistListSort, AlbumArtistListSort,
LibraryItem, } from '/@/shared/types/domain/artist-domain-types';
ServerType, import { ServerType } from '/@/shared/types/domain/server-domain-types';
SortOrder, import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
} from '/@/shared/types/domain-types';
import { ListDisplayType } from '/@/shared/types/types'; import { ListDisplayType } from '/@/shared/types/types';
const FILTERS = { const FILTERS = {
jellyfin: [ jellyfin: [
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.album', { postProcess: 'titleCase' }), name: i18n.t('filter.album', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.ALBUM, value: AlbumArtistListSort.ALBUM,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.duration', { postProcess: 'titleCase' }), name: i18n.t('filter.duration', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.DURATION, value: AlbumArtistListSort.DURATION,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.name', { postProcess: 'titleCase' }), name: i18n.t('filter.name', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.NAME, value: AlbumArtistListSort.NAME,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.random', { postProcess: 'titleCase' }), name: i18n.t('filter.random', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.RANDOM, value: AlbumArtistListSort.RANDOM,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }), name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.RECENTLY_ADDED, value: AlbumArtistListSort.RECENTLY_ADDED,
}, },
// { defaultOrder: SortOrder.DESC, name: 'Release Date', value: AlbumArtistListSort.RELEASE_DATE }, // { defaultOrder: ListSortOrder.DESC, name: 'Release Date', value: AlbumArtistListSort.RELEASE_DATE },
], ],
navidrome: [ navidrome: [
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.albumCount', { postProcess: 'titleCase' }), name: i18n.t('filter.albumCount', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.ALBUM_COUNT, value: AlbumArtistListSort.ALBUM_COUNT,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.isFavorited', { postProcess: 'titleCase' }), name: i18n.t('filter.isFavorited', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.FAVORITED, value: AlbumArtistListSort.FAVORITED,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.mostPlayed', { postProcess: 'titleCase' }), name: i18n.t('filter.mostPlayed', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.PLAY_COUNT, value: AlbumArtistListSort.PLAY_COUNT,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.name', { postProcess: 'titleCase' }), name: i18n.t('filter.name', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.NAME, value: AlbumArtistListSort.NAME,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.rating', { postProcess: 'titleCase' }), name: i18n.t('filter.rating', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.RATING, value: AlbumArtistListSort.RATING,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.songCount', { postProcess: 'titleCase' }), name: i18n.t('filter.songCount', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.SONG_COUNT, value: AlbumArtistListSort.SONG_COUNT,
}, },
], ],
subsonic: [ subsonic: [
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.albumCount', { postProcess: 'titleCase' }), name: i18n.t('filter.albumCount', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.ALBUM_COUNT, value: AlbumArtistListSort.ALBUM_COUNT,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.isFavorited', { postProcess: 'titleCase' }), name: i18n.t('filter.isFavorited', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.FAVORITED, value: AlbumArtistListSort.FAVORITED,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.name', { postProcess: 'titleCase' }), name: i18n.t('filter.name', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.NAME, value: AlbumArtistListSort.NAME,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.rating', { postProcess: 'titleCase' }), name: i18n.t('filter.rating', { postProcess: 'titleCase' }),
value: AlbumArtistListSort.RATING, value: AlbumArtistListSort.RATING,
}, },
@@ -270,7 +269,7 @@ export const AlbumArtistListHeaderFilters = ({
const updatedFilters = setFilter({ const updatedFilters = setFilter({
data: { data: {
sortBy: e.currentTarget.value as AlbumArtistListSort, sortBy: e.currentTarget.value as AlbumArtistListSort,
sortOrder: sortOrder || SortOrder.ASC, sortOrder: sortOrder || ListSortOrder.ASC,
}, },
itemType: LibraryItem.ALBUM_ARTIST, itemType: LibraryItem.ALBUM_ARTIST,
key: pageKey, key: pageKey,
@@ -306,7 +305,8 @@ export const AlbumArtistListHeaderFilters = ({
); );
const handleToggleSortOrder = useCallback(() => { const handleToggleSortOrder = useCallback(() => {
const newSortOrder = filter.sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC; const newSortOrder =
filter.sortOrder === ListSortOrder.ASC ? ListSortOrder.DESC : ListSortOrder.ASC;
const updatedFilters = setFilter({ const updatedFilters = setFilter({
data: { sortOrder: newSortOrder }, data: { sortOrder: newSortOrder },
itemType: LibraryItem.ALBUM_ARTIST, itemType: LibraryItem.ALBUM_ARTIST,
@@ -15,7 +15,8 @@ import { AlbumArtistListFilter, useCurrentServer } from '/@/renderer/store';
import { Flex } from '/@/shared/components/flex/flex'; import { Flex } from '/@/shared/components/flex/flex';
import { Group } from '/@/shared/components/group/group'; import { Group } from '/@/shared/components/group/group';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { AlbumArtistListQuery, LibraryItem } from '/@/shared/types/domain-types'; import { AlbumArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
interface AlbumArtistListHeaderProps { interface AlbumArtistListHeaderProps {
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>; gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
@@ -8,7 +8,7 @@ import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-
import { useListContext } from '/@/renderer/context/list-context'; import { useListContext } from '/@/renderer/context/list-context';
import { ARTIST_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; import { ARTIST_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
interface AlbumArtistListTableViewProps { interface AlbumArtistListTableViewProps {
itemCount?: number; itemCount?: number;
@@ -18,12 +18,11 @@ import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer, useListStoreActions } from '/@/renderer/store'; import { useCurrentServer, useListStoreActions } from '/@/renderer/store';
import { useListStoreByKey } from '/@/renderer/store/list.store'; import { useListStoreByKey } from '/@/renderer/store/list.store';
import { import {
AlbumArtist,
ArtistListQuery, ArtistListQuery,
ArtistListResponse, ArtistListResponse,
ArtistListSort, ArtistListSort,
LibraryItem, } from '/@/shared/types/domain/artist-domain-types';
} from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
import { CardRow, ListDisplayType } from '/@/shared/types/types'; import { CardRow, ListDisplayType } from '/@/shared/types/types';
interface ArtistListGridViewProps { interface ArtistListGridViewProps {
@@ -56,15 +55,11 @@ export const ArtistListGridView = ({ gridRef, itemCount }: ArtistListGridViewPro
const itemData: AlbumArtist[] = []; const itemData: AlbumArtist[] = [];
for (const [, data] of queriesFromCache) { for (const [, data] of queriesFromCache) {
const { items, startIndex } = data || {}; const { items, offset } = data || {};
if (items && items.length !== 1 && startIndex !== undefined) { if (items && items.length !== 1 && offset !== undefined) {
let itemIndex = 0; let itemIndex = 0;
for ( for (let rowIndex = offset; rowIndex < offset + items.length; rowIndex += 1) {
let rowIndex = startIndex;
rowIndex < startIndex + items.length;
rowIndex += 1
) {
itemData[rowIndex] = items[itemIndex]; itemData[rowIndex] = items[itemIndex];
itemIndex += 1; itemIndex += 1;
} }
@@ -79,7 +74,7 @@ export const ArtistListGridView = ({ gridRef, itemCount }: ArtistListGridViewPro
const query: ArtistListQuery = { const query: ArtistListQuery = {
...filter, ...filter,
limit, limit,
startIndex, offset,
}; };
const queryKey = queryKeys.artists.list(server?.id || '', query); const queryKey = queryKeys.artists.list(server?.id || '', query);
@@ -33,93 +33,89 @@ import { Flex } from '/@/shared/components/flex/flex';
import { Group } from '/@/shared/components/group/group'; import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon'; import { Icon } from '/@/shared/components/icon/icon';
import { Select } from '/@/shared/components/select/select'; import { Select } from '/@/shared/components/select/select';
import { import { ArtistListQuery, ArtistListSort } from '/@/shared/types/domain/artist-domain-types';
ArtistListQuery, import { ServerType } from '/@/shared/types/domain/server-domain-types';
ArtistListSort, import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
LibraryItem,
ServerType,
SortOrder,
} from '/@/shared/types/domain-types';
import { ListDisplayType } from '/@/shared/types/types'; import { ListDisplayType } from '/@/shared/types/types';
const FILTERS = { const FILTERS = {
jellyfin: [ jellyfin: [
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.album', { postProcess: 'titleCase' }), name: i18n.t('filter.album', { postProcess: 'titleCase' }),
value: ArtistListSort.ALBUM, value: ArtistListSort.ALBUM,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.duration', { postProcess: 'titleCase' }), name: i18n.t('filter.duration', { postProcess: 'titleCase' }),
value: ArtistListSort.DURATION, value: ArtistListSort.DURATION,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.name', { postProcess: 'titleCase' }), name: i18n.t('filter.name', { postProcess: 'titleCase' }),
value: ArtistListSort.NAME, value: ArtistListSort.NAME,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.random', { postProcess: 'titleCase' }), name: i18n.t('filter.random', { postProcess: 'titleCase' }),
value: ArtistListSort.RANDOM, value: ArtistListSort.RANDOM,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }), name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }),
value: ArtistListSort.RECENTLY_ADDED, value: ArtistListSort.RECENTLY_ADDED,
}, },
], ],
navidrome: [ navidrome: [
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.albumCount', { postProcess: 'titleCase' }), name: i18n.t('filter.albumCount', { postProcess: 'titleCase' }),
value: ArtistListSort.ALBUM_COUNT, value: ArtistListSort.ALBUM_COUNT,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.isFavorited', { postProcess: 'titleCase' }), name: i18n.t('filter.isFavorited', { postProcess: 'titleCase' }),
value: ArtistListSort.FAVORITED, value: ArtistListSort.FAVORITED,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.mostPlayed', { postProcess: 'titleCase' }), name: i18n.t('filter.mostPlayed', { postProcess: 'titleCase' }),
value: ArtistListSort.PLAY_COUNT, value: ArtistListSort.PLAY_COUNT,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.name', { postProcess: 'titleCase' }), name: i18n.t('filter.name', { postProcess: 'titleCase' }),
value: ArtistListSort.NAME, value: ArtistListSort.NAME,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.rating', { postProcess: 'titleCase' }), name: i18n.t('filter.rating', { postProcess: 'titleCase' }),
value: ArtistListSort.RATING, value: ArtistListSort.RATING,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.songCount', { postProcess: 'titleCase' }), name: i18n.t('filter.songCount', { postProcess: 'titleCase' }),
value: ArtistListSort.SONG_COUNT, value: ArtistListSort.SONG_COUNT,
}, },
], ],
subsonic: [ subsonic: [
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.albumCount', { postProcess: 'titleCase' }), name: i18n.t('filter.albumCount', { postProcess: 'titleCase' }),
value: ArtistListSort.ALBUM_COUNT, value: ArtistListSort.ALBUM_COUNT,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.isFavorited', { postProcess: 'titleCase' }), name: i18n.t('filter.isFavorited', { postProcess: 'titleCase' }),
value: ArtistListSort.FAVORITED, value: ArtistListSort.FAVORITED,
}, },
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.name', { postProcess: 'titleCase' }), name: i18n.t('filter.name', { postProcess: 'titleCase' }),
value: ArtistListSort.NAME, value: ArtistListSort.NAME,
}, },
{ {
defaultOrder: SortOrder.DESC, defaultOrder: ListSortOrder.DESC,
name: i18n.t('filter.rating', { postProcess: 'titleCase' }), name: i18n.t('filter.rating', { postProcess: 'titleCase' }),
value: ArtistListSort.RATING, value: ArtistListSort.RATING,
}, },
@@ -144,7 +140,7 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
const cq = useContainerQuery(); const cq = useContainerQuery();
const roles = useRoles({ const roles = useRoles({
options: { options: {
cacheTime: 1000 * 60 * 60 * 2, gcTime: 1000 * 60 * 60 * 2,
staleTime: 1000 * 60 * 60 * 2, staleTime: 1000 * 60 * 60 * 2,
}, },
query: {}, query: {},
@@ -192,7 +188,7 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
}, },
query: { query: {
limit, limit,
startIndex, offset,
...filters, ...filters,
}, },
}), }),
@@ -228,7 +224,7 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
}, },
query: { query: {
limit, limit,
startIndex, offset,
...filters, ...filters,
}, },
}), }),
@@ -276,7 +272,7 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
const updatedFilters = setFilter({ const updatedFilters = setFilter({
data: { data: {
sortBy: e.currentTarget.value as ArtistListSort, sortBy: e.currentTarget.value as ArtistListSort,
sortOrder: sortOrder || SortOrder.ASC, sortOrder: sortOrder || ListSortOrder.ASC,
}, },
itemType: LibraryItem.ARTIST, itemType: LibraryItem.ARTIST,
key: pageKey, key: pageKey,
@@ -312,7 +308,8 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
); );
const handleToggleSortOrder = useCallback(() => { const handleToggleSortOrder = useCallback(() => {
const newSortOrder = filter.sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC; const newSortOrder =
filter.sortOrder === ListSortOrder.ASC ? ListSortOrder.DESC : ListSortOrder.ASC;
const updatedFilters = setFilter({ const updatedFilters = setFilter({
data: { sortOrder: newSortOrder }, data: { sortOrder: newSortOrder },
itemType: LibraryItem.ARTIST, itemType: LibraryItem.ARTIST,
@@ -15,7 +15,8 @@ import { ArtistListFilter, useCurrentServer } from '/@/renderer/store';
import { Flex } from '/@/shared/components/flex/flex'; import { Flex } from '/@/shared/components/flex/flex';
import { Group } from '/@/shared/components/group/group'; import { Group } from '/@/shared/components/group/group';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { ArtistListQuery, LibraryItem } from '/@/shared/types/domain-types'; import { ArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
interface ArtistListHeaderProps { interface ArtistListHeaderProps {
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>; gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
@@ -8,7 +8,7 @@ import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-
import { useListContext } from '/@/renderer/context/list-context'; import { useListContext } from '/@/renderer/context/list-context';
import { ARTIST_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; import { ARTIST_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
interface ArtistListTableViewProps { interface ArtistListTableViewProps {
itemCount?: number; itemCount?: number;
@@ -1,15 +1,14 @@
import type { AlbumArtistDetailQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
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 { QueryHookArgs } from '/@/renderer/lib/react-query'; import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store'; import { useServerById } from '/@/renderer/store';
import { AlbumArtistDetailQuery } from '/@/shared/types/domain/artist-domain-types';
export const useAlbumArtistDetail = (args: QueryHookArgs<AlbumArtistDetailQuery>) => { export const useAlbumArtistDetail = (args: RQueryHookArgs<AlbumArtistDetailQuery>) => {
const { options, query, serverId } = args || {}; const { options, query, serverId } = args || {};
const server = getServerById(serverId); const server = useServerById(serverId);
return useQuery({ return useQuery({
enabled: !!server?.id && !!query.id, enabled: !!server?.id && !!query.id,
@@ -2,13 +2,13 @@ import { useQuery } from '@tanstack/react-query';
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 { QueryHookArgs } from '/@/renderer/lib/react-query'; import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store'; import { useServerById } from '/@/renderer/store';
import { AlbumArtistListQuery } from '/@/shared/types/domain-types'; import { AlbumArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
export const useAlbumArtistListCount = (args: QueryHookArgs<AlbumArtistListQuery>) => { export const useAlbumArtistListCount = (args: RQueryHookArgs<AlbumArtistListQuery>) => {
const { options, query, serverId } = args; const { options, query, serverId } = args;
const server = getServerById(serverId); const server = useServerById(serverId);
return useQuery({ return useQuery({
enabled: !!serverId, enabled: !!serverId,
@@ -1,15 +1,14 @@
import type { AlbumArtistListQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
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 { QueryHookArgs } from '/@/renderer/lib/react-query'; import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store'; import { useServerById } from '/@/renderer/store';
import { AlbumArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
export const useAlbumArtistList = (args: QueryHookArgs<AlbumArtistListQuery>) => { export const useAlbumArtistList = (args: RQueryHookArgs<AlbumArtistListQuery>) => {
const { options, query, serverId } = args || {}; const { options, query, serverId } = args || {};
const server = getServerById(serverId); const server = useServerById(serverId);
return useQuery({ return useQuery({
enabled: !!server?.id, enabled: !!server?.id,
@@ -1,15 +1,14 @@
import type { AlbumArtistDetailQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
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 { QueryHookArgs } from '/@/renderer/lib/react-query'; import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store'; import { useServerById } from '/@/renderer/store';
import { AlbumArtistDetailQuery } from '/@/shared/types/domain/artist-domain-types';
export const useAlbumArtistInfo = (args: QueryHookArgs<AlbumArtistDetailQuery>) => { export const useAlbumArtistInfo = (args: RQueryHookArgs<AlbumArtistDetailQuery>) => {
const { options, query, serverId } = args || {}; const { options, query, serverId } = args || {};
const server = getServerById(serverId); const server = useServerById(serverId);
return useQuery({ return useQuery({
enabled: !!server?.id && !!query.id, enabled: !!server?.id && !!query.id,
@@ -2,13 +2,13 @@ import { useQuery } from '@tanstack/react-query';
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 { QueryHookArgs } from '/@/renderer/lib/react-query'; import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store'; import { useServerById } from '/@/renderer/store';
import { ArtistListQuery } from '/@/shared/types/domain-types'; import { ArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
export const useArtistListCount = (args: QueryHookArgs<ArtistListQuery>) => { export const useArtistListCount = (args: RQueryHookArgs<ArtistListQuery>) => {
const { options, query, serverId } = args; const { options, query, serverId } = args;
const server = getServerById(serverId); const server = useServerById(serverId);
return useQuery({ return useQuery({
enabled: !!serverId, enabled: !!serverId,
@@ -2,12 +2,12 @@ import { useQuery } from '@tanstack/react-query';
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 { QueryHookArgs } from '/@/renderer/lib/react-query'; import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store'; import { useServerById } from '/@/renderer/store';
export const useRoles = (args: QueryHookArgs<object>) => { export const useRoles = (args: RQueryHookArgs<object>) => {
const { options, serverId } = args; const { options, serverId } = args;
const server = getServerById(serverId); const server = useServerById(serverId);
return useQuery({ return useQuery({
enabled: !!serverId, enabled: !!serverId,
@@ -1,15 +1,15 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query'; import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import type { TopSongListQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
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 { getServerById } from '/@/renderer/store'; import { useServerById } from '/@/renderer/store';
import { TopSongListQuery } from '/@/shared/types/domain/song-domain-types';
export const useTopSongsList = (args: QueryHookArgs<TopSongListQuery>) => { export const useTopSongsList = (args: RQueryHookArgs<TopSongListQuery>) => {
const { options, query, serverId } = args || {}; const { options, query, serverId } = args || {};
const server = getServerById(serverId); const server = useServerById(serverId);
return useQuery({ return useQuery({
enabled: !!server?.id, enabled: !!server?.id,
@@ -10,7 +10,7 @@ import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
import { useFastAverageColor } from '/@/renderer/hooks'; import { useFastAverageColor } from '/@/renderer/hooks';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
const AlbumArtistDetailRoute = () => { const AlbumArtistDetailRoute = () => {
const scrollAreaRef = useRef<HTMLDivElement>(null); const scrollAreaRef = useRef<HTMLDivElement>(null);
@@ -10,7 +10,7 @@ import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album
import { useTopSongsList } from '/@/renderer/features/artists/queries/top-songs-list-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/shared-domain-types';
const AlbumArtistDetailTopSongsListRoute = () => { const AlbumArtistDetailTopSongsListRoute = () => {
const tableRef = useRef<AgGridReactType | null>(null); const tableRef = useRef<AgGridReactType | null>(null);
@@ -10,7 +10,8 @@ import { useAlbumArtistListCount } from '/@/renderer/features/artists/queries/al
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';
import { AlbumArtistListQuery, LibraryItem } from '/@/shared/types/domain-types'; import { AlbumArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
const AlbumArtistListRoute = () => { const AlbumArtistListRoute = () => {
const gridRef = useRef<null | VirtualInfiniteGridRef>(null); const gridRef = useRef<null | VirtualInfiniteGridRef>(null);
@@ -22,7 +23,7 @@ const AlbumArtistListRoute = () => {
const itemCountCheck = useAlbumArtistListCount({ const itemCountCheck = useAlbumArtistListCount({
options: { options: {
cacheTime: 1000 * 60, gcTime: 1000 * 60,
staleTime: 1000 * 60, staleTime: 1000 * 60,
}, },
query: albumArtistListFilter, query: albumArtistListFilter,
@@ -10,7 +10,8 @@ import { useArtistListCount } from '/@/renderer/features/artists/queries/artist-
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';
import { ArtistListQuery, LibraryItem } from '/@/shared/types/domain-types'; import { ArtistListQuery } from '/@/shared/types/domain/artist-domain-types';
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
const ArtistListRoute = () => { const ArtistListRoute = () => {
const gridRef = useRef<null | VirtualInfiniteGridRef>(null); const gridRef = useRef<null | VirtualInfiniteGridRef>(null);
@@ -22,7 +23,7 @@ const ArtistListRoute = () => {
const itemCountCheck = useArtistListCount({ const itemCountCheck = useArtistListCount({
options: { options: {
cacheTime: 1000 * 60, gcTime: 1000 * 60,
staleTime: 1000 * 60, staleTime: 1000 * 60,
}, },
query: artistListFilter, query: artistListFilter,
@@ -37,7 +37,7 @@ import { useRemoveFromPlaylist } from '/@/renderer/features/playlists/mutations/
import { useCreateFavorite, useDeleteFavorite, useSetRating } from '/@/renderer/features/shared'; import { useCreateFavorite, useDeleteFavorite, useSetRating } from '/@/renderer/features/shared';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { import {
getServerById, useServerById,
useAuthStore, useAuthStore,
useCurrentServer, useCurrentServer,
usePlayerStore, usePlayerStore,
@@ -57,13 +57,12 @@ import { Rating } from '/@/shared/components/rating/rating';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { toast } from '/@/shared/components/toast/toast'; import { toast } from '/@/shared/components/toast/toast';
import { ServerFeature, ServerType } from '/@/shared/types/domain/server-domain-types';
import { import {
AnyLibraryItem, AnyLibraryItem,
AnyLibraryItems, AnyLibraryItems,
LibraryItem, LibraryItem,
ServerType, } from '/@/shared/types/domain/shared-domain-types';
} from '/@/shared/types/domain-types';
import { ServerFeature } from '/@/shared/types/features-types';
import { Play, PlaybackType } from '/@/shared/types/types'; import { Play, PlaybackType } from '/@/shared/types/types';
type ContextMenuContextProps = { type ContextMenuContextProps = {
@@ -713,7 +712,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
const item = ctx.data[0]; const item = ctx.data[0];
const songs = await controller.getSimilarSongs({ const songs = await controller.getSimilarSongs({
apiClientProps: { apiClientProps: {
server: getServerById(item.serverId), server: useServerById(item.serverId),
signal: undefined, signal: undefined,
}, },
query: { albumArtistIds: item.albumArtistIds, songId: item.id }, query: { albumArtistIds: item.albumArtistIds, songId: item.id },
+1 -1
View File
@@ -1,6 +1,6 @@
import { GridOptions, RowNode } from '@ag-grid-community/core'; import { GridOptions, RowNode } from '@ag-grid-community/core';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
import { createUseExternalEvents } from '/@/shared/utils/create-use-external-events'; import { createUseExternalEvents } from '/@/shared/utils/create-use-external-events';
export type ContextMenuEvents = { export type ContextMenuEvents = {
@@ -2,14 +2,11 @@ import { CellContextMenuEvent, GridApi } from '@ag-grid-community/core';
import sortBy from 'lodash/sortBy'; import sortBy from 'lodash/sortBy';
import { openContextMenu, SetContextMenuItems } from '/@/renderer/features/context-menu/events'; import { openContextMenu, SetContextMenuItems } from '/@/renderer/features/context-menu/events';
import { import { Album } from '/@/shared/types/domain/album-domain-types';
Album, import { Artist } from '/@/shared/types/domain/artist-domain-types';
AlbumArtist, import { QueueSong } from '/@/shared/types/domain/player-domain-types';
Artist, import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
LibraryItem, import { Song } from '/@/shared/types/domain/song-domain-types';
QueueSong,
Song,
} from '/@/shared/types/domain-types';
export const useHandleTableContextMenu = ( export const useHandleTableContextMenu = (
itemType: LibraryItem, itemType: LibraryItem,
@@ -5,13 +5,14 @@ import { useCallback, useEffect, useState } from 'react';
import { controller } from '/@/renderer/api/controller'; import { controller } from '/@/renderer/api/controller';
import { import {
DiscordDisplayType, DiscordDisplayType,
getServerById,
useAppStore, useAppStore,
useDiscordSettings, useDiscordSettings,
useServerById,
useGeneralSettings, useGeneralSettings,
usePlayerStore, usePlayerStore,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { QueueSong, ServerType } from '/@/shared/types/domain-types'; import { QueueSong } from '/@/shared/types/domain/player-domain-types';
import { ServerType } from '/@/shared/types/domain/server-domain-types';
import { PlayerStatus } from '/@/shared/types/types'; import { PlayerStatus } from '/@/shared/types/types';
const discordRpc = isElectron() ? window.api.discordRpc : null; const discordRpc = isElectron() ? window.api.discordRpc : null;
@@ -92,7 +93,7 @@ export const useDiscordRpc = () => {
if (song.serverType === ServerType.JELLYFIN && song.imageUrl) { if (song.serverType === ServerType.JELLYFIN && song.imageUrl) {
activity.largeImageKey = song.imageUrl; activity.largeImageKey = song.imageUrl;
} else if (song.serverType === ServerType.NAVIDROME) { } else if (song.serverType === ServerType.NAVIDROME) {
const server = getServerById(song.serverId); const server = useServerById(song.serverId);
try { try {
const info = await controller.getAlbumInfo({ const info = await controller.getAlbumInfo({
@@ -15,13 +15,13 @@ import { useListContext } from '/@/renderer/context/list-context';
import { usePlayQueueAdd } from '/@/renderer/features/player'; import { usePlayQueueAdd } from '/@/renderer/features/player';
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route'; import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';
import { useCurrentServer, useListStoreActions, useListStoreByKey } from '/@/renderer/store'; import { useCurrentServer, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
import { Album } from '/@/shared/types/domain/album-domain-types';
import { import {
Album,
Genre, Genre,
GenreListQuery, GenreListQuery,
GenreListResponse, GenreListResponse,
LibraryItem, } from '/@/shared/types/domain/genre-domain-types';
} from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
import { CardRow, ListDisplayType } from '/@/shared/types/types'; import { CardRow, ListDisplayType } from '/@/shared/types/types';
export const GenreListGridView = ({ gridRef, itemCount }: any) => { export const GenreListGridView = ({ gridRef, itemCount }: any) => {
@@ -74,15 +74,11 @@ export const GenreListGridView = ({ gridRef, itemCount }: any) => {
const itemData: Genre[] = []; const itemData: Genre[] = [];
for (const [, data] of queriesFromCache) { for (const [, data] of queriesFromCache) {
const { items, startIndex } = data || {}; const { items, offset } = data || {};
if (items && items.length !== 1 && startIndex !== undefined) { if (items && items.length !== 1 && offset !== undefined) {
let itemIndex = 0; let itemIndex = 0;
for ( for (let rowIndex = offset; rowIndex < offset + items.length; rowIndex += 1) {
let rowIndex = startIndex;
rowIndex < startIndex + items.length;
rowIndex += 1
) {
itemData[rowIndex] = items[itemIndex]; itemData[rowIndex] = items[itemIndex];
itemIndex += 1; itemIndex += 1;
} }
@@ -101,7 +97,7 @@ export const GenreListGridView = ({ gridRef, itemCount }: any) => {
const query: GenreListQuery = { const query: GenreListQuery = {
...filter, ...filter,
limit: take, limit: take,
startIndex: skip, offset: skip,
}; };
const queryKey = queryKeys.albums.list(server?.id || '', query); const queryKey = queryKeys.albums.list(server?.id || '', query);
@@ -33,33 +33,29 @@ import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
import { Flex } from '/@/shared/components/flex/flex'; import { Flex } from '/@/shared/components/flex/flex';
import { Group } from '/@/shared/components/group/group'; import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon'; import { Icon } from '/@/shared/components/icon/icon';
import { import { GenreListQuery, GenreListSort } from '/@/shared/types/domain/genre-domain-types';
GenreListQuery, import { ServerType } from '/@/shared/types/domain/server-domain-types';
GenreListSort, import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
LibraryItem,
ServerType,
SortOrder,
} from '/@/shared/types/domain-types';
import { ListDisplayType } from '/@/shared/types/types'; import { ListDisplayType } from '/@/shared/types/types';
const FILTERS = { const FILTERS = {
jellyfin: [ jellyfin: [
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.name', { postProcess: 'titleCase' }), name: i18n.t('filter.name', { postProcess: 'titleCase' }),
value: GenreListSort.NAME, value: GenreListSort.NAME,
}, },
], ],
navidrome: [ navidrome: [
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.name', { postProcess: 'titleCase' }), name: i18n.t('filter.name', { postProcess: 'titleCase' }),
value: GenreListSort.NAME, value: GenreListSort.NAME,
}, },
], ],
subsonic: [ subsonic: [
{ {
defaultOrder: SortOrder.ASC, defaultOrder: ListSortOrder.ASC,
name: i18n.t('filter.name', { postProcess: 'titleCase' }), name: i18n.t('filter.name', { postProcess: 'titleCase' }),
value: GenreListSort.NAME, value: GenreListSort.NAME,
}, },
@@ -137,7 +133,7 @@ export const GenreListHeaderFilters = ({
customFilters, customFilters,
data: { data: {
sortBy: e.currentTarget.value as GenreListSort, sortBy: e.currentTarget.value as GenreListSort,
sortOrder: sortOrder || SortOrder.ASC, sortOrder: sortOrder || ListSortOrder.ASC,
}, },
itemType: LibraryItem.GENRE, itemType: LibraryItem.GENRE,
key: pageKey, key: pageKey,
@@ -175,7 +171,8 @@ export const GenreListHeaderFilters = ({
); );
const handleToggleSortOrder = useCallback(() => { const handleToggleSortOrder = useCallback(() => {
const newSortOrder = filter.sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC; const newSortOrder =
filter.sortOrder === ListSortOrder.ASC ? ListSortOrder.DESC : ListSortOrder.ASC;
const updatedFilters = setFilter({ const updatedFilters = setFilter({
customFilters, customFilters,
data: { sortOrder: newSortOrder }, data: { sortOrder: newSortOrder },
@@ -15,7 +15,8 @@ import { GenreListFilter, useCurrentServer } from '/@/renderer/store';
import { Flex } from '/@/shared/components/flex/flex'; import { Flex } from '/@/shared/components/flex/flex';
import { Group } from '/@/shared/components/group/group'; import { Group } from '/@/shared/components/group/group';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { GenreListQuery, LibraryItem } from '/@/shared/types/domain-types'; import { GenreListQuery } from '/@/shared/types/domain/genre-domain-types';
import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
interface GenreListHeaderProps { interface GenreListHeaderProps {
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>; gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
@@ -11,7 +11,7 @@ import { useListContext } from '/@/renderer/context/list-context';
import { GENRE_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; import { GENRE_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route'; import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types';
interface GenreListTableViewProps { interface GenreListTableViewProps {
itemCount?: number; itemCount?: number;
@@ -1,15 +1,15 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query'; import type { RQueryHookArgs } from '/@/renderer/lib/react-query';
import type { GenreListQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
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 { getServerById } from '/@/renderer/store'; import { useServerById } from '/@/renderer/store';
import { GenreListQuery } from '/@/shared/types/domain/genre-domain-types';
export const useGenreList = (args: QueryHookArgs<GenreListQuery>) => { export const useGenreList = (args: RQueryHookArgs<GenreListQuery>) => {
const { options, query, serverId } = args || {}; const { options, query, serverId } = args || {};
const server = getServerById(serverId); const server = useServerById(serverId);
return useQuery({ return useQuery({
enabled: !!server, enabled: !!server,
@@ -10,7 +10,7 @@ import { useGenreList } from '/@/renderer/features/genres/queries/genre-list-que
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';
import { GenreListQuery } from '/@/shared/types/domain-types'; import { GenreListQuery } from '/@/shared/types/domain/genre-domain-types';
const GenreListRoute = () => { const GenreListRoute = () => {
const gridRef = useRef<null | VirtualInfiniteGridRef>(null); const gridRef = useRef<null | VirtualInfiniteGridRef>(null);
@@ -23,7 +23,7 @@ const GenreListRoute = () => {
query: { query: {
...filter, ...filter,
limit: 1, limit: 1,
startIndex: 0, offset: 0,
}, },
serverId: server?.id, serverId: server?.id,
}); });
@@ -2,19 +2,20 @@ import { useQuery } from '@tanstack/react-query';
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 { QueryHookArgs } from '/@/renderer/lib/react-query'; import { RQueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store'; import { useServerById } from '/@/renderer/store';
import { AlbumListQuery, AlbumListSort, SortOrder } from '/@/shared/types/domain-types'; import { AlbumListQuery, AlbumListSort } from '/@/shared/types/domain/album-domain-types';
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
export const useRecentlyPlayed = (args: QueryHookArgs<Partial<AlbumListQuery>>) => { export const useRecentlyPlayed = (args: RQueryHookArgs<Partial<AlbumListQuery>>) => {
const { options, query, serverId } = args; const { options, query, serverId } = args;
const server = getServerById(serverId); const server = useServerById(serverId);
const requestQuery: AlbumListQuery = { const requestQuery: AlbumListQuery = {
limit: 5, limit: 5,
sortBy: AlbumListSort.RECENTLY_PLAYED, sortBy: AlbumListSort.RECENTLY_PLAYED,
sortOrder: SortOrder.ASC, sortOrder: ListSortOrder.ASC,
startIndex: 0, offset: 0,
...query, ...query,
}; };
+195 -195
View File
@@ -4,32 +4,24 @@ import { useTranslation } from 'react-i18next';
import { queryKeys } from '/@/renderer/api/query-keys'; import { queryKeys } from '/@/renderer/api/query-keys';
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 { 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 { useAlbumList } from '/@/renderer/features/albums';
import { useRecentlyPlayed } from '/@/renderer/features/home/queries/recently-played-query'; import { useRecentlyPlayed } from '/@/renderer/features/home/queries/recently-played-query';
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared'; import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
import { AlbumInfiniteCarousel } from '/@/renderer/features/shared/components/infinite-album-carousel/infinite-album-carousel';
import { useSongList } from '/@/renderer/features/songs'; import { useSongList } from '/@/renderer/features/songs';
import { AppRoute } from '/@/renderer/router/routes';
import { import {
HomeItem, HomeItem,
useCurrentServer, useCurrentServer,
useGeneralSettings, useGeneralSettings,
useWindowSettings, useWindowSettings,
} from '/@/renderer/store'; } from '/@/renderer/store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon';
import { Spinner } from '/@/shared/components/spinner/spinner'; import { Spinner } from '/@/shared/components/spinner/spinner';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { TextTitle } from '/@/shared/components/text-title/text-title'; import { AlbumListSort, AlbumListSortOptions } from '/@/shared/types/domain/album-domain-types';
import { import { ServerType } from '/@/shared/types/domain/server-domain-types';
AlbumListSort, import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
LibraryItem, import { SongListSort } from '/@/shared/types/domain/song-domain-types';
ServerType,
SongListSort,
SortOrder,
} from '/@/shared/types/domain-types';
import { Platform } from '/@/shared/types/types'; import { Platform } from '/@/shared/types/types';
const HomeRoute = () => { const HomeRoute = () => {
@@ -41,198 +33,198 @@ const HomeRoute = () => {
const { windowBarStyle } = useWindowSettings(); const { windowBarStyle } = useWindowSettings();
const { homeFeature, homeItems } = useGeneralSettings(); const { homeFeature, homeItems } = useGeneralSettings();
const feature = useAlbumList({ // const feature = useAlbumList({
options: { // options: {
cacheTime: 1000 * 60, // enabled: homeFeature,
enabled: homeFeature, // gcTime: 1000 * 60,
staleTime: 1000 * 60, // staleTime: 1000 * 60,
}, // },
query: { // query: {
limit: 20, // limit: 20,
sortBy: AlbumListSort.RANDOM, // offset: 0,
sortOrder: SortOrder.DESC, // sortBy: AlbumListSort.RANDOM,
startIndex: 0, // sortOrder: ListSortOrder.DESC,
}, // },
serverId: server?.id, // serverId: server?.id,
}); // });
const featureItemsWithImage = useMemo(() => { // const featureItemsWithImage = useMemo(() => {
return feature.data?.items?.filter((item) => item.imageUrl) ?? []; // return feature.data?.items?.filter((item) => item.imageUrl) ?? [];
}, [feature.data?.items]); // }, [feature.data?.items]);
const random = useAlbumList({ // const random = useAlbumList({
options: { // options: {
staleTime: 1000 * 60 * 5, // staleTime: 1000 * 60 * 5,
}, // },
query: { // query: {
limit: itemsPerPage, // limit: itemsPerPage,
sortBy: AlbumListSort.RANDOM, // offset: 0,
sortOrder: SortOrder.ASC, // sortBy: AlbumListSort.RANDOM,
startIndex: 0, // sortOrder: ListSortOrder.ASC,
}, // },
serverId: server?.id, // serverId: server?.id,
}); // });
const recentlyPlayed = useRecentlyPlayed({ // const recentlyPlayed = useRecentlyPlayed({
options: { // options: {
staleTime: 0, // staleTime: 0,
}, // },
query: { // query: {
limit: itemsPerPage, // limit: itemsPerPage,
sortBy: AlbumListSort.RECENTLY_PLAYED, // offset: 0,
sortOrder: SortOrder.DESC, // sortBy: AlbumListSort.RECENTLY_PLAYED,
startIndex: 0, // sortOrder: ListSortOrder.DESC,
}, // },
serverId: server?.id, // serverId: server?.id,
}); // });
const recentlyAdded = useAlbumList({ // const recentlyAdded = useAlbumList({
options: { // options: {
staleTime: 1000 * 60 * 5, // staleTime: 1000 * 60 * 5,
}, // },
query: { // query: {
limit: itemsPerPage, // limit: itemsPerPage,
sortBy: AlbumListSort.RECENTLY_ADDED, // offset: 0,
sortOrder: SortOrder.DESC, // sortBy: AlbumListSort.RECENTLY_ADDED,
startIndex: 0, // sortOrder: ListSortOrder.DESC,
}, // },
serverId: server?.id, // serverId: server?.id,
}); // });
const mostPlayedAlbums = useAlbumList({ // const mostPlayedAlbums = useAlbumList({
options: { // options: {
enabled: server?.type === ServerType.SUBSONIC || server?.type === ServerType.NAVIDROME, // enabled: server?.type === ServerType.SUBSONIC || server?.type === ServerType.NAVIDROME,
staleTime: 1000 * 60 * 5, // staleTime: 1000 * 60 * 5,
}, // },
query: { // query: {
limit: itemsPerPage, // limit: itemsPerPage,
sortBy: AlbumListSort.PLAY_COUNT, // offset: 0,
sortOrder: SortOrder.DESC, // sortBy: AlbumListSort.PLAY_COUNT,
startIndex: 0, // sortOrder: ListSortOrder.DESC,
}, // },
serverId: server?.id, // serverId: server?.id,
}); // });
const mostPlayedSongs = useSongList( // const mostPlayedSongs = useSongList(
{ // {
options: { // options: {
enabled: server?.type === ServerType.JELLYFIN, // enabled: server?.type === ServerType.JELLYFIN,
staleTime: 1000 * 60 * 5, // staleTime: 1000 * 60 * 5,
}, // },
query: { // query: {
limit: itemsPerPage, // limit: itemsPerPage,
sortBy: SongListSort.PLAY_COUNT, // offset: 0,
sortOrder: SortOrder.DESC, // sortBy: SongListSort.PLAY_COUNT,
startIndex: 0, // sortOrder: ListSortOrder.DESC,
}, // },
serverId: server?.id, // serverId: server?.id,
}, // },
300, // 300,
); // );
const isLoading = // const isLoading =
random.isLoading || // random.isLoading ||
recentlyPlayed.isLoading || // recentlyPlayed.isLoading ||
recentlyAdded.isLoading || // recentlyAdded.isLoading ||
(server?.type === ServerType.JELLYFIN && mostPlayedSongs.isLoading) || // (server?.type === ServerType.JELLYFIN && mostPlayedSongs.isLoading) ||
((server?.type === ServerType.SUBSONIC || server?.type === ServerType.NAVIDROME) && // ((server?.type === ServerType.SUBSONIC || server?.type === ServerType.NAVIDROME) &&
mostPlayedAlbums.isLoading); // mostPlayedAlbums.isLoading);
if (isLoading) { // if (isLoading) {
return <Spinner container />; // return <Spinner container />;
} // }
const carousels = { // const carousels = {
[HomeItem.MOST_PLAYED]: { // [HomeItem.MOST_PLAYED]: {
data: // data:
server?.type === ServerType.JELLYFIN // server?.type === ServerType.JELLYFIN
? mostPlayedSongs?.data?.items // ? mostPlayedSongs?.data?.items
: mostPlayedAlbums?.data?.items, // : mostPlayedAlbums?.data?.items,
itemType: server?.type === ServerType.JELLYFIN ? LibraryItem.SONG : LibraryItem.ALBUM, // itemType: server?.type === ServerType.JELLYFIN ? LibraryItem.SONG : LibraryItem.ALBUM,
pagination: { // pagination: {
itemsPerPage, // itemsPerPage,
}, // },
sortBy: // sortBy:
server?.type === ServerType.JELLYFIN // server?.type === ServerType.JELLYFIN
? SongListSort.PLAY_COUNT // ? SongListSort.PLAY_COUNT
: AlbumListSort.PLAY_COUNT, // : AlbumListSort.PLAY_COUNT,
sortOrder: SortOrder.DESC, // sortOrder: ListSortOrder.DESC,
title: t('page.home.mostPlayed', { postProcess: 'sentenceCase' }), // title: t('page.home.mostPlayed', { postProcess: 'sentenceCase' }),
}, // },
[HomeItem.RANDOM]: { // [HomeItem.RANDOM]: {
data: random?.data?.items, // data: random?.data?.items,
itemType: LibraryItem.ALBUM, // itemType: LibraryItem.ALBUM,
sortBy: AlbumListSort.RANDOM, // sortBy: AlbumListSort.RANDOM,
sortOrder: SortOrder.ASC, // sortOrder: ListSortOrder.ASC,
title: t('page.home.explore', { postProcess: 'sentenceCase' }), // title: t('page.home.explore', { postProcess: 'sentenceCase' }),
}, // },
[HomeItem.RECENTLY_ADDED]: { // [HomeItem.RECENTLY_ADDED]: {
data: recentlyAdded?.data?.items, // data: recentlyAdded?.data?.items,
itemType: LibraryItem.ALBUM, // itemType: LibraryItem.ALBUM,
pagination: { // pagination: {
itemsPerPage, // itemsPerPage,
}, // },
sortBy: AlbumListSort.RECENTLY_ADDED, // sortBy: AlbumListSort.RECENTLY_ADDED,
sortOrder: SortOrder.DESC, // sortOrder: ListSortOrder.DESC,
title: t('page.home.newlyAdded', { postProcess: 'sentenceCase' }), // title: t('page.home.newlyAdded', { postProcess: 'sentenceCase' }),
}, // },
[HomeItem.RECENTLY_PLAYED]: { // [HomeItem.RECENTLY_PLAYED]: {
data: recentlyPlayed?.data?.items, // data: recentlyPlayed?.data?.items,
itemType: LibraryItem.ALBUM, // itemType: LibraryItem.ALBUM,
pagination: { // pagination: {
itemsPerPage, // itemsPerPage,
}, // },
sortBy: AlbumListSort.RECENTLY_PLAYED, // sortBy: AlbumListSort.RECENTLY_PLAYED,
sortOrder: SortOrder.DESC, // sortOrder: ListSortOrder.DESC,
title: t('page.home.recentlyPlayed', { postProcess: 'sentenceCase' }), // title: t('page.home.recentlyPlayed', { postProcess: 'sentenceCase' }),
}, // },
}; // };
const sortedCarousel = homeItems // const sortedCarousel = homeItems
.filter((item) => { // .filter((item) => {
if (item.disabled) { // if (item.disabled) {
return false; // return false;
} // }
if (server?.type === ServerType.JELLYFIN && item.id === HomeItem.RECENTLY_PLAYED) { // if (server?.type === ServerType.JELLYFIN && item.id === HomeItem.RECENTLY_PLAYED) {
return false; // return false;
} // }
return true; // return true;
}) // })
.map((item) => ({ // .map((item) => ({
...carousels[item.id], // ...carousels[item.id],
uniqueId: item.id, // uniqueId: item.id,
})); // }));
const invalidateCarouselQuery = (carousel: { // const invalidateCarouselQuery = (carousel: {
itemType: LibraryItem; // itemType: LibraryItem;
sortBy: AlbumListSort | SongListSort; // sortBy: AlbumListSort | SongListSort;
sortOrder: SortOrder; // sortOrder: ListSortOrder;
}) => { // }) => {
if (carousel.itemType === LibraryItem.ALBUM) { // if (carousel.itemType === LibraryItem.ALBUM) {
queryClient.invalidateQueries({ // queryClient.invalidateQueries({
exact: false, // exact: false,
queryKey: queryKeys.albums.list(server?.id, { // queryKey: queryKeys.albums.list(server?.id, {
limit: itemsPerPage, // limit: itemsPerPage,
sortBy: carousel.sortBy, // sortBy: carousel.sortBy,
sortOrder: carousel.sortOrder, // sortOrder: carousel.sortOrder,
startIndex: 0, // startIndex: 0,
}), // }),
}); // });
} // }
if (carousel.itemType === LibraryItem.SONG) { // if (carousel.itemType === LibraryItem.SONG) {
queryClient.invalidateQueries({ // queryClient.invalidateQueries({
exact: false, // exact: false,
queryKey: queryKeys.songs.list(server?.id, { // queryKey: queryKeys.songs.list(server?.id, {
limit: itemsPerPage, // limit: itemsPerPage,
sortBy: carousel.sortBy, // sortBy: carousel.sortBy,
sortOrder: carousel.sortOrder, // sortOrder: carousel.sortOrder,
startIndex: 0, // startIndex: 0,
}), // }),
}); // });
} // }
}; // };
return ( return (
<AnimatedPage> <AnimatedPage>
@@ -255,8 +247,16 @@ const HomeRoute = () => {
pt={windowBarStyle === Platform.WEB ? '5rem' : '3rem'} pt={windowBarStyle === Platform.WEB ? '5rem' : '3rem'}
px="2rem" px="2rem"
> >
{homeFeature && <FeatureCarousel data={featureItemsWithImage} />} {/* {homeFeature && <FeatureCarousel data={featureItemsWithImage} />} */}
{sortedCarousel.map((carousel) => (
<AlbumInfiniteCarousel
serverId={server?.id ?? ''}
sortBy={AlbumListSortOptions.NAME}
sortOrder={ListSortOrder.ASC}
title={t('page.home.explore', { postProcess: 'sentenceCase' })}
/>
{/* {sortedCarousel.map((carousel) => (
<MemoizedSwiperGridCarousel <MemoizedSwiperGridCarousel
cardRows={[ cardRows={[
{ {
@@ -320,7 +320,7 @@ const HomeRoute = () => {
}} }}
uniqueId={carousel.uniqueId} uniqueId={carousel.uniqueId}
/> />
))} ))} */}
</Stack> </Stack>
</NativeScrollArea> </NativeScrollArea>
</AnimatedPage> </AnimatedPage>
@@ -16,18 +16,14 @@ import { Separator } from '/@/shared/components/separator/separator';
import { Spoiler } from '/@/shared/components/spoiler/spoiler'; import { Spoiler } from '/@/shared/components/spoiler/spoiler';
import { Table } from '/@/shared/components/table/table'; import { Table } from '/@/shared/components/table/table';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { import { Album } from '/@/shared/types/domain/album-domain-types';
Album, import { RelatedArtist } from '/@/shared/types/domain/artist-domain-types';
AlbumArtist, import { Playlist } from '/@/shared/types/domain/playlist-domain-types';
AnyLibraryItem, import { AnyLibraryItem, LibraryItem } from '/@/shared/types/domain/shared-domain-types';
LibraryItem, import { Song } from '/@/shared/types/domain/song-domain-types';
Playlist,
RelatedArtist,
Song,
} from '/@/shared/types/domain-types';
export type ItemDetailsModalProps = { export type ItemDetailsModalProps = {
item: Album | AlbumArtist | Playlist | Song; item: Album | Playlist | Song;
}; };
type ItemDetailRow<T> = { type ItemDetailRow<T> = {
@@ -20,7 +20,7 @@ import {
InternetProviderLyricSearchResponse, InternetProviderLyricSearchResponse,
LyricSource, LyricSource,
LyricsOverride, LyricsOverride,
} from '/@/shared/types/domain-types'; } from '/@/shared/types/domain/lyric-domain-types';
interface SearchResultProps { interface SearchResultProps {
data: InternetProviderLyricSearchResponse; data: InternetProviderLyricSearchResponse;
@@ -15,7 +15,7 @@ import { Group } from '/@/shared/components/group/group';
import { NumberInput } from '/@/shared/components/number-input/number-input'; import { NumberInput } from '/@/shared/components/number-input/number-input';
import { Select } from '/@/shared/components/select/select'; import { Select } from '/@/shared/components/select/select';
import { Tooltip } from '/@/shared/components/tooltip/tooltip'; import { Tooltip } from '/@/shared/components/tooltip/tooltip';
import { LyricsOverride } from '/@/shared/types/domain-types'; import { LyricsOverride } from '/@/shared/types/domain/lyric-domain-types';
interface LyricsActionsProps { interface LyricsActionsProps {
index: number; index: number;

Some files were not shown because too many files have changed in this diff Show More