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