upgrade and refactor for react-query v5

This commit is contained in:
jeffvli 2025-11-02 01:16:53 -07:00
parent dd70d30cd3
commit 8115963264
94 changed files with 1650 additions and 1750 deletions

View file

@ -163,20 +163,23 @@ export const useVirtualTable = <TFilter extends BaseQuery<any>>({
},
);
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<any>;
if (isClientSideSort && results?.items) {

View file

@ -0,0 +1,54 @@
import { queryOptions } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import { AlbumDetailQuery, AlbumListQuery } from '/@/shared/types/domain-types';
export const albumQueries = {
detail: (args: QueryHookArgs<AlbumDetailQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
return api.controller.getAlbumDetail({
apiClientProps: { server: getServerById(args.serverId), signal },
query: args.query,
});
},
queryKey: queryKeys.albums.detail(args.serverId, args.query),
...args.options,
});
},
list: (args: QueryHookArgs<AlbumListQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
return api.controller.getAlbumList({
apiClientProps: { server: getServerById(args.serverId), signal },
query: args.query,
});
},
queryKey: queryKeys.albums.list(
args.serverId,
args.query,
args.query?.artistIds?.length === 1 ? args.query?.artistIds[0] : undefined,
),
...args.options,
});
},
listCount: (args: QueryHookArgs<AlbumListQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
return api.controller.getAlbumListCount({
apiClientProps: { server: getServerById(args.serverId), signal },
query: args.query,
});
},
queryKey: queryKeys.albums.count(
args.serverId,
args.query,
args.query?.artistIds?.length === 1 ? args.query?.artistIds[0] : undefined,
),
...args.options,
});
},
};

View file

@ -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"

View file

@ -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<HTMLDivElement>) => {
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}
/>

View file

@ -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;
},

View file

@ -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]);

View file

@ -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 [];

View file

@ -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 [];

View file

@ -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<string>('');
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 [];

View file

@ -1,22 +0,0 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { AlbumDetailQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query';
import { controller } from '/@/renderer/api/controller';
import { queryKeys } from '/@/renderer/api/query-keys';
import { getServerById } from '/@/renderer/store';
export const useAlbumDetail = (args: QueryHookArgs<AlbumDetailQuery>) => {
const { options, query, serverId } = args;
const server = getServerById(serverId);
return useQuery({
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return controller.getAlbumDetail({ apiClientProps: { server, signal }, query });
},
queryKey: queryKeys.albums.detail(server?.id || '', query),
...options,
});
};

View file

@ -1,32 +0,0 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { AlbumListQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { getServerById } from '/@/renderer/store';
export const useAlbumListCount = (args: QueryHookArgs<AlbumListQuery>) => {
const { options, query, serverId } = args;
const server = getServerById(serverId);
return useQuery({
enabled: !!serverId,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getAlbumListCount({
apiClientProps: {
server,
signal,
},
query,
});
},
queryKey: queryKeys.albums.count(
serverId || '',
Object.keys(query).length === 0 ? undefined : query,
),
...options,
});
};

View file

@ -1,67 +0,0 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { AlbumListQuery, AlbumListResponse } from '/@/shared/types/domain-types';
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { controller } from '/@/renderer/api/controller';
import { queryKeys } from '/@/renderer/api/query-keys';
import { getServerById } from '/@/renderer/store';
export const useAlbumList = (args: QueryHookArgs<AlbumListQuery>) => {
const { options, query, serverId } = args;
const server = getServerById(serverId);
return useQuery({
enabled: !!serverId,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return controller.getAlbumList({
apiClientProps: {
server,
signal,
},
query,
});
},
queryKey: queryKeys.albums.list(
serverId || '',
query,
query?.artistIds?.length === 1 ? query?.artistIds[0] : undefined,
),
...options,
});
};
export const useAlbumListInfinite = (args: QueryHookArgs<AlbumListQuery>) => {
const { options, query, serverId } = args;
const server = getServerById(serverId);
return useInfiniteQuery({
enabled: !!serverId,
getNextPageParam: (lastPage: AlbumListResponse | undefined, pages) => {
if (!lastPage?.items) return undefined;
if (lastPage?.items?.length >= (query?.limit || 50)) {
return pages?.length;
}
return undefined;
},
queryFn: ({ pageParam = 0, signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getAlbumList({
apiClientProps: {
server,
signal,
},
query: {
...query,
limit: query.limit || 50,
startIndex: pageParam * (query.limit || 50),
},
});
},
queryKey: queryKeys.albums.list(server?.id || '', query),
...options,
});
};

View file

@ -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,

View file

@ -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;

View file

@ -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"

View file

@ -0,0 +1,81 @@
import { queryOptions } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import {
AlbumArtistDetailQuery,
AlbumArtistListQuery,
ArtistListQuery,
TopSongListQuery,
} from '/@/shared/types/domain-types';
export const artistsQueries = {
albumArtistDetail: (args: QueryHookArgs<AlbumArtistDetailQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
return api.controller.getAlbumArtistDetail({
apiClientProps: { server: getServerById(args.serverId), signal },
query: args.query,
});
},
queryKey: queryKeys.albumArtists.detail(args.serverId || '', args.query),
...args.options,
});
},
albumArtistList: (args: QueryHookArgs<AlbumArtistListQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
return api.controller.getAlbumArtistList({
apiClientProps: { server: getServerById(args.serverId), signal },
query: args.query,
});
},
queryKey: queryKeys.albumArtists.list(args.serverId || '', args.query),
...args.options,
});
},
albumArtistListCount: (args: QueryHookArgs<AlbumArtistListQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
return api.controller.getAlbumArtistListCount({
apiClientProps: { server: getServerById(args.serverId), signal },
query: args.query,
});
},
queryKey: queryKeys.albumArtists.count(
args.serverId || '',
Object.keys(args.query).length === 0 ? undefined : args.query,
),
...args.options,
});
},
artistListCount: (args: QueryHookArgs<ArtistListQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
return api.controller.getArtistListCount({
apiClientProps: { server: getServerById(args.serverId), signal },
query: args.query,
});
},
queryKey: queryKeys.albumArtists.count(
args.serverId || '',
Object.keys(args.query).length === 0 ? undefined : args.query,
),
...args.options,
});
},
topSongs: (args: QueryHookArgs<TopSongListQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
return api.controller.getTopSongs({
apiClientProps: { server: getServerById(args.serverId), signal },
query: args.query,
});
},
queryKey: queryKeys.albumArtists.topSongs(args.serverId || '', args.query),
...args.options,
});
},
};

View file

@ -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"

View file

@ -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(
<Rating
onChange={handleUpdateRating}
readOnly={
detailQuery?.isFetching || updateRatingMutation.isLoading
detailQuery?.isFetching || updateRatingMutation.isPending
}
value={detailQuery?.data?.userRating || 0}
/>

View file

@ -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;
},

View file

@ -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]);

View file

@ -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;
},

View file

@ -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]);

View file

@ -1,26 +0,0 @@
import type { AlbumArtistDetailQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
export const useAlbumArtistDetail = (args: QueryHookArgs<AlbumArtistDetailQuery>) => {
const { options, query, serverId } = args || {};
const server = getServerById(serverId);
return useQuery({
enabled: !!server?.id && !!query.id,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getAlbumArtistDetail({
apiClientProps: { server, signal },
query,
});
},
queryKey: queryKeys.albumArtists.detail(server?.id || '', query),
...options,
});
};

View file

@ -1,31 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import { AlbumArtistListQuery } from '/@/shared/types/domain-types';
export const useAlbumArtistListCount = (args: QueryHookArgs<AlbumArtistListQuery>) => {
const { options, query, serverId } = args;
const server = getServerById(serverId);
return useQuery({
enabled: !!serverId,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getAlbumArtistListCount({
apiClientProps: {
server,
signal,
},
query,
});
},
queryKey: queryKeys.albumArtists.count(
serverId || '',
Object.keys(query).length === 0 ? undefined : query,
),
...options,
});
};

View file

@ -1,23 +0,0 @@
import type { AlbumArtistListQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
export const useAlbumArtistList = (args: QueryHookArgs<AlbumArtistListQuery>) => {
const { options, query, serverId } = args || {};
const server = getServerById(serverId);
return useQuery({
enabled: !!server?.id,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getAlbumArtistList({ apiClientProps: { server, signal }, query });
},
queryKey: queryKeys.albumArtists.list(server?.id || '', query),
...options,
});
};

View file

@ -1,26 +0,0 @@
import type { AlbumArtistDetailQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
export const useAlbumArtistInfo = (args: QueryHookArgs<AlbumArtistDetailQuery>) => {
const { options, query, serverId } = args || {};
const server = getServerById(serverId);
return useQuery({
enabled: !!server?.id && !!query.id,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getAlbumArtistDetail({
apiClientProps: { server, signal },
query,
});
},
queryKey: queryKeys.albumArtists.detail(server?.id || '', query),
...options,
});
};

View file

@ -1,31 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import { ArtistListQuery } from '/@/shared/types/domain-types';
export const useArtistListCount = (args: QueryHookArgs<ArtistListQuery>) => {
const { options, query, serverId } = args;
const server = getServerById(serverId);
return useQuery({
enabled: !!serverId,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getArtistListCount({
apiClientProps: {
server,
signal,
},
query,
});
},
queryKey: queryKeys.albumArtists.count(
serverId || '',
Object.keys(query).length === 0 ? undefined : query,
),
...options,
});
};

View file

@ -1,26 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
export const useRoles = (args: QueryHookArgs<object>) => {
const { options, serverId } = args;
const server = getServerById(serverId);
return useQuery({
enabled: !!serverId,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getRoles({
apiClientProps: {
server,
signal,
},
});
},
queryKey: queryKeys.roles.list(serverId || ''),
...options,
});
};

View file

@ -1,23 +0,0 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { TopSongListQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { getServerById } from '/@/renderer/store';
export const useTopSongsList = (args: QueryHookArgs<TopSongListQuery>) => {
const { options, query, serverId } = args || {};
const server = getServerById(serverId);
return useQuery({
enabled: !!server?.id,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getTopSongs({ apiClientProps: { server, signal }, query });
},
queryKey: queryKeys.albumArtists.topSongs(server?.id || '', query),
...options,
});
};

View file

@ -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,

View file

@ -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;

View file

@ -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<AlbumArtistListQuery>({ 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;

View file

@ -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<ArtistListQuery>({ 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;

View file

@ -549,7 +549,7 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
openModal({
children: (
<ConfirmModal loading={removeFromPlaylistMutation.isLoading} onConfirm={confirm}>
<ConfirmModal loading={removeFromPlaylistMutation.isPending} onConfirm={confirm}>
{t('common.areYouSure', { postProcess: 'sentenceCase' })}
</ConfirmModal>
),

View file

@ -0,0 +1,25 @@
import { queryOptions } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import { GenreListQuery } from '/@/shared/types/domain-types';
export const genresQueries = {
list: (args: QueryHookArgs<GenreListQuery>) => {
return queryOptions({
gcTime: 1000 * 5,
queryFn: ({ signal }) => {
const server = getServerById(args.serverId);
if (!server) throw new Error('Server not found');
return api.controller.getGenreList({
apiClientProps: { server, signal },
query: args.query,
});
},
queryKey: queryKeys.genres.list(args.serverId || '', args.query),
...args.options,
});
},
};

View file

@ -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]);

View file

@ -1,24 +0,0 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { GenreListQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { getServerById } from '/@/renderer/store';
export const useGenreList = (args: QueryHookArgs<GenreListQuery>) => {
const { options, query, serverId } = args || {};
const server = getServerById(serverId);
return useQuery({
enabled: !!server,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getGenreList({ apiClientProps: { server, signal }, query });
},
queryKey: queryKeys.genres.list(server?.id || '', query),
staleTime: 1000 * 5,
...options,
});
};

View file

@ -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<GenreListQuery>({ 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

View file

@ -0,0 +1,32 @@
import { queryOptions } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import { AlbumListQuery, AlbumListSort, SortOrder } from '/@/shared/types/domain-types';
export const homeQueries = {
recentlyPlayed: (args: QueryHookArgs<Partial<AlbumListQuery>>) => {
const requestQuery: AlbumListQuery = {
limit: 5,
sortBy: AlbumListSort.RECENTLY_PLAYED,
sortOrder: SortOrder.ASC,
startIndex: 0,
...args.query,
};
return queryOptions({
queryFn: ({ signal }) => {
const server = getServerById(args.serverId);
if (!server) throw new Error('Server not found');
return api.controller.getAlbumList({
apiClientProps: { server, signal },
query: requestQuery,
});
},
queryKey: queryKeys.albums.list(args.serverId || '', requestQuery),
...args.options,
});
},
};

View file

@ -1,37 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import { AlbumListQuery, AlbumListSort, SortOrder } from '/@/shared/types/domain-types';
export const useRecentlyPlayed = (args: QueryHookArgs<Partial<AlbumListQuery>>) => {
const { options, query, serverId } = args;
const server = getServerById(serverId);
const requestQuery: AlbumListQuery = {
limit: 5,
sortBy: AlbumListSort.RECENTLY_PLAYED,
sortOrder: SortOrder.ASC,
startIndex: 0,
...query,
};
return useQuery({
enabled: !!server?.id,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getAlbumList({
apiClientProps: {
server,
signal,
},
query: requestQuery,
});
},
queryKey: queryKeys.albums.list(server?.id || '', requestQuery),
...options,
});
};

View file

@ -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]) ||

View file

@ -0,0 +1,200 @@
import { queryOptions } from '@tanstack/react-query';
import isElectron from 'is-electron';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById, useSettingsStore } from '/@/renderer/store';
import { hasFeature } from '/@/shared/api/utils';
import {
FullLyricsMetadata,
InternetProviderLyricResponse,
InternetProviderLyricSearchResponse,
LyricGetQuery,
LyricSearchQuery,
LyricsQuery,
QueueSong,
ServerType,
StructuredLyric,
SynchronizedLyricsArray,
} from '/@/shared/types/domain-types';
import { LyricSource } from '/@/shared/types/domain-types';
import { ServerFeature } from '/@/shared/types/features-types';
const lyricsIpc = isElectron() ? window.api.lyrics : null;
// Match LRC lyrics format by https://github.com/ustbhuangyi/lyric-parser
// [mm:ss.SSS] text
const timeExp = /\[(\d{2,}):(\d{2})(?:\.(\d{2,3}))?]([^\n]+)(\n|$)/g;
// Match karaoke lyrics format returned by NetEase
// [SSS,???] text
const alternateTimeExp = /\[(\d*),(\d*)]([^\n]+)(\n|$)/g;
const formatLyrics = (lyrics: string) => {
const synchronizedLines = lyrics.matchAll(timeExp);
const formattedLyrics: SynchronizedLyricsArray = [];
for (const line of synchronizedLines) {
const [, minute, sec, ms, text] = line;
const minutes = parseInt(minute, 10);
const seconds = parseInt(sec, 10);
const milis = ms?.length === 3 ? parseInt(ms, 10) : parseInt(ms, 10) * 10;
const timeInMilis = (minutes * 60 + seconds) * 1000 + milis;
formattedLyrics.push([timeInMilis, text]);
}
if (formattedLyrics.length > 0) return formattedLyrics;
const alternateSynchronizedLines = lyrics.matchAll(alternateTimeExp);
for (const line of alternateSynchronizedLines) {
const [, timeInMilis, , text] = line;
const cleanText = text
.replaceAll(/\(\d+,\d+\)/g, '')
.replaceAll(/\s,/g, ',')
.replaceAll(/\s\./g, '.');
formattedLyrics.push([Number(timeInMilis), cleanText]);
}
if (formattedLyrics.length > 0) return formattedLyrics;
// If no synchronized lyrics were found, return the original lyrics
return lyrics;
};
export const lyricsQueries = {
search: (args: Omit<QueryHookArgs<LyricSearchQuery>, 'serverId'>) => {
return queryOptions({
gcTime: 1000 * 60 * 1,
queryFn: () => {
if (lyricsIpc) {
return lyricsIpc.searchRemoteLyrics(args.query);
}
return {} as Record<LyricSource, InternetProviderLyricSearchResponse[]>;
},
queryKey: queryKeys.songs.lyricsSearch(args.query),
staleTime: 1000 * 60 * 1,
...args.options,
});
},
serverLyrics: (args: QueryHookArgs<LyricsQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
const server = getServerById(args.serverId);
if (!server) throw new Error('Server not found');
// This should only be called for Jellyfin. Return null to ignore errors
if (server.type !== ServerType.JELLYFIN) return null;
return api.controller.getLyrics({
apiClientProps: { server, signal },
query: args.query,
});
},
queryKey: queryKeys.songs.lyrics(args.serverId || '', args.query),
...args.options,
});
},
songLyrics: (args: QueryHookArgs<LyricsQuery>, song: QueueSong | undefined) => {
return queryOptions({
gcTime: Infinity,
queryFn: async ({ signal }): Promise<FullLyricsMetadata | null | StructuredLyric[]> => {
const server = getServerById(song?.serverId);
if (!server) throw new Error('Server not found');
if (!song) return null;
const { preferLocalLyrics } = useSettingsStore.getState().lyrics;
let localLyrics: FullLyricsMetadata | null | StructuredLyric[] = null;
let remoteLyrics: FullLyricsMetadata | null | StructuredLyric[] = null;
if (hasFeature(server, ServerFeature.LYRICS_MULTIPLE_STRUCTURED)) {
const subsonicLyrics = await api.controller
.getStructuredLyrics({
apiClientProps: { server, signal },
query: { songId: song.id },
})
.catch(console.error);
if (subsonicLyrics?.length) {
localLyrics = subsonicLyrics;
}
} else if (hasFeature(server, ServerFeature.LYRICS_SINGLE_STRUCTURED)) {
const jfLyrics = await api.controller
.getLyrics({
apiClientProps: { server, signal },
query: { songId: song.id },
})
.catch((err) => console.error(err));
if (jfLyrics) {
localLyrics = {
artist: song.artists?.[0]?.name,
lyrics: jfLyrics,
name: song.name,
remote: false,
source: server?.name ?? 'music server',
};
}
} else if (song.lyrics) {
localLyrics = {
artist: song.artists?.[0]?.name,
lyrics: formatLyrics(song.lyrics),
name: song.name,
remote: false,
source: server?.name ?? 'music server',
};
}
if (preferLocalLyrics && localLyrics) {
return localLyrics;
}
const { fetch } = useSettingsStore.getState().lyrics;
if (fetch) {
const remoteLyricsResult: InternetProviderLyricResponse | null =
await lyricsIpc?.getRemoteLyricsBySong(song);
if (remoteLyricsResult) {
remoteLyrics = {
...remoteLyricsResult,
lyrics: formatLyrics(remoteLyricsResult.lyrics),
remote: true,
};
}
}
if (remoteLyrics) {
return remoteLyrics;
}
if (localLyrics) {
return localLyrics;
}
return null;
},
queryKey: queryKeys.songs.lyrics(args.serverId || '', args.query),
staleTime: Infinity,
...args.options,
});
},
songLyricsByRemoteId: (args: QueryHookArgs<Partial<LyricGetQuery>>) => {
return queryOptions({
queryFn: async () => {
const remoteLyricsResult = await lyricsIpc?.getRemoteLyricsByRemoteId(
args.query as any,
);
if (remoteLyricsResult) {
return formatLyrics(remoteLyricsResult);
}
return null;
},
queryKey: queryKeys.songs.lyricsByRemoteId(args.query),
...args.options,
});
},
};

View file

@ -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 [];

View file

@ -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 | string>(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<LyricsOverride | undefined>(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(

View file

@ -1,211 +0,0 @@
import { useQuery, useQueryClient, UseQueryResult } from '@tanstack/react-query';
import isElectron from 'is-electron';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById, useLyricsSettings } from '/@/renderer/store';
import { hasFeature } from '/@/shared/api/utils';
import {
FullLyricsMetadata,
InternetProviderLyricResponse,
LyricGetQuery,
LyricsQuery,
QueueSong,
ServerType,
StructuredLyric,
SynchronizedLyricsArray,
} from '/@/shared/types/domain-types';
import { ServerFeature } from '/@/shared/types/features-types';
const lyricsIpc = isElectron() ? window.api.lyrics : null;
// Match LRC lyrics format by https://github.com/ustbhuangyi/lyric-parser
// [mm:ss.SSS] text
const timeExp = /\[(\d{2,}):(\d{2})(?:\.(\d{2,3}))?]([^\n]+)(\n|$)/g;
// Match karaoke lyrics format returned by NetEase
// [SSS,???] text
const alternateTimeExp = /\[(\d*),(\d*)]([^\n]+)(\n|$)/g;
const formatLyrics = (lyrics: string) => {
const synchronizedLines = lyrics.matchAll(timeExp);
const formattedLyrics: SynchronizedLyricsArray = [];
for (const line of synchronizedLines) {
const [, minute, sec, ms, text] = line;
const minutes = parseInt(minute, 10);
const seconds = parseInt(sec, 10);
const milis = ms?.length === 3 ? parseInt(ms, 10) : parseInt(ms, 10) * 10;
const timeInMilis = (minutes * 60 + seconds) * 1000 + milis;
formattedLyrics.push([timeInMilis, text]);
}
if (formattedLyrics.length > 0) return formattedLyrics;
const alternateSynchronizedLines = lyrics.matchAll(alternateTimeExp);
for (const line of alternateSynchronizedLines) {
const [, timeInMilis, , text] = line;
const cleanText = text
.replaceAll(/\(\d+,\d+\)/g, '')
.replaceAll(/\s,/g, ',')
.replaceAll(/\s\./g, '.');
formattedLyrics.push([Number(timeInMilis), cleanText]);
}
if (formattedLyrics.length > 0) return formattedLyrics;
// If no synchronized lyrics were found, return the original lyrics
return lyrics;
};
export const useServerLyrics = (
args: QueryHookArgs<LyricsQuery>,
): UseQueryResult<null | string> => {
const { query, serverId } = args;
const server = getServerById(serverId);
return useQuery({
// Note: This currently fetches for every song, even if it shouldn't have
// lyrics, because for some reason HasLyrics is not exposed. Thus, ignore the error
onError: () => {},
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
// This should only be called for Jellyfin. Return null to ignore errors
if (server.type !== ServerType.JELLYFIN) return null;
return api.controller.getLyrics({ apiClientProps: { server, signal }, query });
},
queryKey: queryKeys.songs.lyrics(server?.id || '', query),
});
};
export const useSongLyricsBySong = (
args: QueryHookArgs<LyricsQuery>,
song: QueueSong | undefined,
): UseQueryResult<FullLyricsMetadata | StructuredLyric[]> => {
const { query } = args;
const { fetch, preferLocalLyrics } = useLyricsSettings();
const server = getServerById(song?.serverId);
return useQuery({
cacheTime: Infinity,
enabled: !!song && !!server,
onError: () => {},
queryFn: async ({ signal }): Promise<FullLyricsMetadata | null | StructuredLyric[]> => {
if (!server) throw new Error('Server not found');
if (!song) return null;
let localLyrics: FullLyricsMetadata | null | StructuredLyric[] = null;
let remoteLyrics: FullLyricsMetadata | null | StructuredLyric[] = null;
if (hasFeature(server, ServerFeature.LYRICS_MULTIPLE_STRUCTURED)) {
const subsonicLyrics = await api.controller
.getStructuredLyrics({
apiClientProps: { server, signal },
query: { songId: song.id },
})
.catch(console.error);
if (subsonicLyrics?.length) {
localLyrics = subsonicLyrics;
}
} else if (hasFeature(server, ServerFeature.LYRICS_SINGLE_STRUCTURED)) {
const jfLyrics = await api.controller
.getLyrics({
apiClientProps: { server, signal },
query: { songId: song.id },
})
.catch((err) => console.error(err));
if (jfLyrics) {
localLyrics = {
artist: song.artists?.[0]?.name,
lyrics: jfLyrics,
name: song.name,
remote: false,
source: server?.name ?? 'music server',
};
}
} else if (song.lyrics) {
localLyrics = {
artist: song.artists?.[0]?.name,
lyrics: formatLyrics(song.lyrics),
name: song.name,
remote: false,
source: server?.name ?? 'music server',
};
}
if (preferLocalLyrics && localLyrics) {
return localLyrics;
}
if (fetch) {
const remoteLyricsResult: InternetProviderLyricResponse | null =
await lyricsIpc?.getRemoteLyricsBySong(song);
if (remoteLyricsResult) {
remoteLyrics = {
...remoteLyricsResult,
lyrics: formatLyrics(remoteLyricsResult.lyrics),
remote: true,
};
}
}
if (remoteLyrics) {
return remoteLyrics;
}
if (localLyrics) {
return localLyrics;
}
return null;
},
queryKey: queryKeys.songs.lyrics(server?.id || '', query),
staleTime: Infinity,
});
};
export const useSongLyricsByRemoteId = (
args: QueryHookArgs<Partial<LyricGetQuery>>,
): UseQueryResult<null | string> => {
const queryClient = useQueryClient();
const { query, serverId } = args;
return useQuery({
enabled: !!query.remoteSongId && !!query.remoteSource,
onError: () => {},
onSuccess: (data) => {
if (!data || !query.song) {
return;
}
const lyricsResult = {
artist: query.song.artists?.[0]?.name,
lyrics: data,
name: query.song.name,
remote: false,
source: query.remoteSource,
};
queryClient.setQueryData(
queryKeys.songs.lyrics(serverId, { songId: query.song.id }),
lyricsResult,
);
},
queryFn: async () => {
const remoteLyricsResult = await lyricsIpc?.getRemoteLyricsByRemoteId(query as any);
if (remoteLyricsResult) {
return formatLyrics(remoteLyricsResult);
}
return null;
},
queryKey: queryKeys.songs.lyricsByRemoteId(query),
});
};

View file

@ -1,30 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import isElectron from 'is-electron';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import {
InternetProviderLyricSearchResponse,
LyricSearchQuery,
LyricSource,
} from '/@/shared/types/domain-types';
const lyricsIpc = isElectron() ? window.api.lyrics : null;
export const useLyricSearch = (args: Omit<QueryHookArgs<LyricSearchQuery>, 'serverId'>) => {
const { options, query } = args;
return useQuery<Record<LyricSource, InternetProviderLyricSearchResponse[]>>({
cacheTime: 1000 * 60 * 1,
enabled: !!query.artist || !!query.name,
queryFn: () => {
if (lyricsIpc) {
return lyricsIpc.searchRemoteLyrics(query);
}
return {} as Record<LyricSource, InternetProviderLyricSearchResponse[]>;
},
queryKey: queryKeys.songs.lyricsSearch(query),
staleTime: 1000 * 60 * 1,
...options,
});
};

View file

@ -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({

View file

@ -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');

View file

@ -0,0 +1,57 @@
import { queryOptions } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import {
PlaylistDetailQuery,
PlaylistListQuery,
PlaylistSongListQuery,
} from '/@/shared/types/domain-types';
export const playlistsQueries = {
detail: (args: QueryHookArgs<PlaylistDetailQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
const server = getServerById(args.serverId);
if (!server) throw new Error('Server not found');
return api.controller.getPlaylistDetail({
apiClientProps: { server, signal },
query: args.query,
});
},
queryKey: queryKeys.playlists.detail(args.serverId || '', args.query.id, args.query),
...args.options,
});
},
list: (args: QueryHookArgs<PlaylistListQuery>) => {
return queryOptions({
gcTime: 1000 * 60 * 60,
queryFn: ({ signal }) => {
const server = getServerById(args.serverId);
if (!server) throw new Error('Server not found');
return api.controller.getPlaylistList({
apiClientProps: { server, signal },
query: args.query,
});
},
queryKey: queryKeys.playlists.list(args.serverId || '', args.query),
...args.options,
});
},
songList: (args: QueryHookArgs<PlaylistSongListQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
const server = getServerById(args.serverId);
if (!server) throw new Error('Server not found');
return api.controller.getPlaylistSongList({
apiClientProps: { server, signal },
query: args.query,
});
},
queryKey: queryKeys.playlists.songList(args.serverId || '', args.query.id),
...args.options,
});
},
};

View file

@ -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<Playlist & { label: string; value: string }>();
@ -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 = ({
/>
<Group justify="flex-end">
<ModalButton
disabled={isLoading || addToPlaylistMutation.isLoading}
disabled={isLoading || addToPlaylistMutation.isPending}
onClick={() => closeModal(id)}
uppercase
variant="subtle"
@ -518,7 +533,7 @@ export const AddToPlaylistContextModal = ({
<ModalButton
disabled={
isLoading ||
addToPlaylistMutation.isLoading ||
addToPlaylistMutation.isPending ||
(form.values.selectedPlaylistIds.length === 0 &&
form.values.newPlaylists.length === 0)
}

View file

@ -95,7 +95,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
});
const isPublicDisplayed = hasFeature(server, ServerFeature.PUBLIC_PLAYLIST);
const isSubmitDisabled = !form.values.name || mutation.isLoading;
const isSubmitDisabled = !form.values.name || mutation.isPending;
return (
<form onSubmit={handleSubmit}>
@ -160,7 +160,7 @@ export const CreatePlaylistForm = ({ onCancel }: CreatePlaylistFormProps) => {
</ModalButton>
<ModalButton
disabled={isSubmitDisabled}
loading={mutation.isLoading}
loading={mutation.isPending}
type="submit"
variant="filled"
>

View file

@ -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 = {

View file

@ -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();
};

View file

@ -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<PlaylistSongListQueryClientSide> = {
sortBy: page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID,
sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC,
};
const handlePlay = async (playType: Play) => {
handlePlayQueueAdd?.({
byItemType: { id: [playlistId], type: LibraryItem.PLAYLIST },
playType,
query: filters,
});
};
const playButtonBehavior = usePlayButtonBehavior();

View file

@ -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;
},

View file

@ -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);
};

View file

@ -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 [];

View file

@ -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 (
<form onSubmit={handleSubmit}>
@ -106,7 +106,7 @@ export const SaveAsPlaylistForm = ({
<ModalButton onClick={onCancel}>{t('common.cancel')}</ModalButton>
<ModalButton
disabled={isSubmitDisabled}
loading={mutation.isLoading}
loading={mutation.isPending}
type="submit"
variant="filled"
>

View file

@ -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 (
<form onSubmit={handleSubmit}>
@ -143,7 +143,7 @@ export const UpdatePlaylistForm = ({ body, onCancel, query, users }: UpdatePlayl
<ModalButton onClick={onCancel}>{t('common.cancel')}</ModalButton>
<ModalButton
disabled={isSubmitDisabled}
loading={mutation.isLoading}
loading={mutation.isPending}
type="submit"
variant="filled"
>

View file

@ -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,
});

View file

@ -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,

View file

@ -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,
});

View file

@ -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,
});

View file

@ -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,

View file

@ -1,23 +0,0 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { PlaylistDetailQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { getServerById } from '/@/renderer/store';
export const usePlaylistDetail = (args: QueryHookArgs<PlaylistDetailQuery>) => {
const { options, query, serverId } = args || {};
const server = getServerById(serverId);
return useQuery({
enabled: !!server?.id,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getPlaylistDetail({ apiClientProps: { server, signal }, query });
},
queryKey: queryKeys.playlists.detail(server?.id || '', query.id, query),
...options,
});
};

View file

@ -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,
});
};

View file

@ -1,26 +0,0 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { PlaylistSongListQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { getServerById } from '/@/renderer/store';
export const usePlaylistSongList = (args: QueryHookArgs<PlaylistSongListQuery>) => {
const { options, query, serverId } = args || {};
const server = getServerById(serverId);
return useQuery({
enabled: !!server,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getPlaylistSongList({
apiClientProps: { server, signal },
query,
});
},
queryKey: queryKeys.playlists.songList(server?.id || '', query.id),
...options,
});
};

View file

@ -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;

View file

@ -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<PlaylistSongListQuery>({ 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

View file

@ -0,0 +1,22 @@
import { queryOptions } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import { SearchQuery } from '/@/shared/types/domain-types';
export const searchQueries = {
search: (args: QueryHookArgs<SearchQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
return api.controller.search({
apiClientProps: { server: getServerById(args.serverId), signal },
query: args.query,
});
},
queryKey: queryKeys.search.list(args.serverId || '', args.query),
...args.options,
});
},
};

View file

@ -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);

View file

@ -1,28 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import { SearchQuery } from '/@/shared/types/domain-types';
export const useSearch = (args: QueryHookArgs<SearchQuery>) => {
const { options, query, serverId } = args;
const server = getServerById(serverId);
return useQuery({
enabled: !!serverId,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.search({
apiClientProps: {
server,
signal,
},
query,
});
},
queryKey: queryKeys.search.list(serverId || '', query),
...options,
});
};

View file

@ -0,0 +1,63 @@
import { queryOptions } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import { MusicFolderListQuery, TagQuery, UserListQuery } from '/@/shared/types/domain-types';
export const sharedQueries = {
musicFolders: (args: QueryHookArgs<MusicFolderListQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
const server = getServerById(args.serverId);
if (!server) throw new Error('Server not found');
return api.controller.getMusicFolderList({ apiClientProps: { server, signal } });
},
queryKey: queryKeys.musicFolders.list(args.serverId || ''),
...args.options,
});
},
roles: (args: QueryHookArgs<object>) => {
return queryOptions({
queryFn: ({ signal }) => {
const server = getServerById(args.serverId);
if (!server) throw new Error('Server not found');
return api.controller.getRoles({
apiClientProps: { server, signal },
});
},
queryKey: queryKeys.roles.list(args.serverId || ''),
...args.options,
});
},
tags: (args: QueryHookArgs<TagQuery>) => {
return queryOptions({
gcTime: 1000 * 60,
queryFn: ({ signal }) => {
const server = getServerById(args.serverId);
if (!server) throw new Error('Server not found');
return api.controller.getTags({
apiClientProps: { server, signal },
query: args.query,
});
},
queryKey: queryKeys.tags.list(args.serverId || '', args.query.type),
...args.options,
});
},
users: (args: QueryHookArgs<UserListQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
const server = getServerById(args.serverId);
if (!server) throw new Error('Server not found');
return api.controller.getUserList({
apiClientProps: { server, signal },
query: args.query,
});
},
queryKey: queryKeys.users.list(args.serverId || '', args.query),
...args.options,
});
},
};

View file

@ -1,24 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import { MusicFolderListQuery } from '/@/shared/types/domain-types';
export const useMusicFolders = (args: QueryHookArgs<MusicFolderListQuery>) => {
const { options, serverId } = args || {};
const server = getServerById(serverId);
const query = useQuery({
enabled: !!server,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getMusicFolderList({ apiClientProps: { server, signal } });
},
queryKey: queryKeys.musicFolders.list(server?.id || ''),
...options,
});
return query;
};

View file

@ -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) => {

View file

@ -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'),

View file

@ -1,30 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import { SimilarSongsQuery } from '/@/shared/types/domain-types';
export const useSimilarSongs = (args: QueryHookArgs<SimilarSongsQuery>) => {
const { options, query, serverId } = args || {};
const server = getServerById(serverId);
return useQuery({
enabled: !!server,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getSimilarSongs({
apiClientProps: { server, signal },
query: {
albumArtistIds: query.albumArtistIds,
count: query.count ?? 50,
songId: query.songId,
},
});
},
queryKey: queryKeys.songs.similar(server?.id || '', query),
...options,
});
};

View file

@ -0,0 +1,60 @@
import { queryOptions } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { controller } from '/@/renderer/api/controller';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import { SimilarSongsQuery, SongListQuery } from '/@/shared/types/domain-types';
export const songsQueries = {
list: (args: QueryHookArgs<SongListQuery>, imageSize?: number) => {
return queryOptions({
queryFn: ({ signal }) => {
const server = getServerById(args.serverId);
if (!server) throw new Error('Server not found');
return controller.getSongList({
apiClientProps: { server, signal },
query: { ...args.query, imageSize },
});
},
queryKey: queryKeys.songs.list(args.serverId || '', { ...args.query, imageSize }),
...args.options,
});
},
listCount: (args: QueryHookArgs<SongListQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
const server = getServerById(args.serverId);
if (!server) throw new Error('Server not found');
return api.controller.getSongListCount({
apiClientProps: { server, signal },
query: args.query,
});
},
queryKey: queryKeys.songs.count(
args.serverId || '',
Object.keys(args.query).length === 0 ? undefined : args.query,
),
...args.options,
});
},
similar: (args: QueryHookArgs<SimilarSongsQuery>) => {
return queryOptions({
queryFn: ({ signal }) => {
const server = getServerById(args.serverId);
if (!server) throw new Error('Server not found');
return api.controller.getSimilarSongs({
apiClientProps: { server, signal },
query: {
albumArtistIds: args.query.albumArtistIds,
count: args.query.count ?? 50,
songId: args.query.songId,
},
});
},
queryKey: queryKeys.songs.similar(args.serverId || '', args.query),
...args.options,
});
},
};

View file

@ -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<SongListFilter>;
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(',');

View file

@ -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<SongListFilter>;
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 [];

View file

@ -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<null | VirtualInfiniteGridRef>;
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;
},

View file

@ -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 {

View file

@ -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<SongListFilter>;
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 [];

View file

@ -1,32 +0,0 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { SongListQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { getServerById } from '/@/renderer/store';
export const useSongListCount = (args: QueryHookArgs<SongListQuery>) => {
const { options, query, serverId } = args;
const server = getServerById(serverId);
return useQuery({
enabled: !!serverId,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getSongListCount({
apiClientProps: {
server,
signal,
},
query,
});
},
queryKey: queryKeys.songs.count(
serverId || '',
Object.keys(query).length === 0 ? undefined : query,
),
...options,
});
};

View file

@ -1,26 +0,0 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { SongListQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query';
import { controller } from '/@/renderer/api/controller';
import { queryKeys } from '/@/renderer/api/query-keys';
import { getServerById } from '/@/renderer/store';
export const useSongList = (args: QueryHookArgs<SongListQuery>, imageSize?: number) => {
const { options, query, serverId } = args || {};
const server = getServerById(serverId);
return useQuery({
enabled: !!server?.id,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return controller.getSongList({
apiClientProps: { server, signal },
query: { ...query, imageSize },
});
},
queryKey: queryKeys.songs.list(server?.id || '', { ...query, imageSize }),
...options,
});
};

View file

@ -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;

View file

@ -1,25 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { QueryHookArgs } from '/@/renderer/lib/react-query';
import { getServerById } from '/@/renderer/store';
import { hasFeature } from '/@/shared/api/utils';
import { TagQuery } from '/@/shared/types/domain-types';
import { ServerFeature } from '/@/shared/types/features-types';
export const useTagList = (args: QueryHookArgs<TagQuery>) => {
const { options, query, serverId } = args || {};
const server = getServerById(serverId);
return useQuery({
enabled: !!server && hasFeature(server, ServerFeature.TAGS),
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
return api.controller.getTags({ apiClientProps: { server, signal }, query });
},
queryKey: queryKeys.tags.list(server?.id || '', query.type),
staleTime: 1000 * 60,
...options,
});
};

View file

@ -1,23 +0,0 @@
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
import type { UserListQuery } from '/@/shared/types/domain-types';
import { useQuery } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { getServerById } from '/@/renderer/store';
export const useUserList = (args: QueryHookArgs<UserListQuery>) => {
const { options, query, serverId } = args || {};
const server = getServerById(serverId);
return useQuery({
enabled: !!server,
queryFn: ({ signal }) => {
if (!server) throw new Error('Server not found');
api.controller.getUserList({ apiClientProps: { server, signal }, query });
},
queryKey: queryKeys.users.list(server?.id || '', query),
...options,
});
};

View file

@ -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<T> = {
options?: UseInfiniteQueryOptions;
query: T;
serverId: string | undefined;
};
export type MutationHookArgs = {
@ -74,26 +59,34 @@ export type MutationOptions = {
};
export type QueryHookArgs<T> = {
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;
};

View file

@ -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,
},
},
},

View file

@ -91,7 +91,8 @@ export const useAuthStore = createWithEqualityFn<AuthSlice>()(
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);