mirror of
https://github.com/antebudimir/feishin.git
synced 2025-12-31 10:03:33 +00:00
upgrade and refactor for react-query v5
This commit is contained in:
parent
dd70d30cd3
commit
8115963264
94 changed files with 1650 additions and 1750 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
54
src/renderer/features/albums/api/album-api.ts
Normal file
54
src/renderer/features/albums/api/album-api.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { queryOptions } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { AlbumDetailQuery, AlbumListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
export const albumQueries = {
|
||||
detail: (args: QueryHookArgs<AlbumDetailQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
return api.controller.getAlbumDetail({
|
||||
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albums.detail(args.serverId, args.query),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
list: (args: QueryHookArgs<AlbumListQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
return api.controller.getAlbumList({
|
||||
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albums.list(
|
||||
args.serverId,
|
||||
args.query,
|
||||
args.query?.artistIds?.length === 1 ? args.query?.artistIds[0] : undefined,
|
||||
),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
listCount: (args: QueryHookArgs<AlbumListQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
return api.controller.getAlbumListCount({
|
||||
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albums.count(
|
||||
args.serverId,
|
||||
args.query,
|
||||
args.query?.artistIds?.length === 1 ? args.query?.artistIds[0] : undefined,
|
||||
),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -2,6 +2,7 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/li
|
|||
|
||||
import { RowDoubleClickedEvent, RowHeightParams, RowNode } from '@ag-grid-community/core';
|
||||
import { 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"
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 [];
|
||||
|
|
|
|||
|
|
@ -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 [];
|
||||
|
|
|
|||
|
|
@ -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 [];
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import type { AlbumDetailQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useAlbumDetail = (args: QueryHookArgs<AlbumDetailQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return controller.getAlbumDetail({ apiClientProps: { server, signal }, query });
|
||||
},
|
||||
queryKey: queryKeys.albums.detail(server?.id || '', query),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import type { AlbumListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useAlbumListCount = (args: QueryHookArgs<AlbumListQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getAlbumListCount({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albums.count(
|
||||
serverId || '',
|
||||
Object.keys(query).length === 0 ? undefined : query,
|
||||
),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import type { AlbumListQuery, AlbumListResponse } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useAlbumList = (args: QueryHookArgs<AlbumListQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return controller.getAlbumList({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albums.list(
|
||||
serverId || '',
|
||||
query,
|
||||
query?.artistIds?.length === 1 ? query?.artistIds[0] : undefined,
|
||||
),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useAlbumListInfinite = (args: QueryHookArgs<AlbumListQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useInfiniteQuery({
|
||||
enabled: !!serverId,
|
||||
getNextPageParam: (lastPage: AlbumListResponse | undefined, pages) => {
|
||||
if (!lastPage?.items) return undefined;
|
||||
if (lastPage?.items?.length >= (query?.limit || 50)) {
|
||||
return pages?.length;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
queryFn: ({ pageParam = 0, signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getAlbumList({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query: {
|
||||
...query,
|
||||
limit: query.limit || 50,
|
||||
startIndex: pageParam * (query.limit || 50),
|
||||
},
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albums.list(server?.id || '', query),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { 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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
81
src/renderer/features/artists/api/artists-api.ts
Normal file
81
src/renderer/features/artists/api/artists-api.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { queryOptions } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import {
|
||||
AlbumArtistDetailQuery,
|
||||
AlbumArtistListQuery,
|
||||
ArtistListQuery,
|
||||
TopSongListQuery,
|
||||
} from '/@/shared/types/domain-types';
|
||||
|
||||
export const artistsQueries = {
|
||||
albumArtistDetail: (args: QueryHookArgs<AlbumArtistDetailQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
return api.controller.getAlbumArtistDetail({
|
||||
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albumArtists.detail(args.serverId || '', args.query),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
albumArtistList: (args: QueryHookArgs<AlbumArtistListQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
return api.controller.getAlbumArtistList({
|
||||
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albumArtists.list(args.serverId || '', args.query),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
albumArtistListCount: (args: QueryHookArgs<AlbumArtistListQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
return api.controller.getAlbumArtistListCount({
|
||||
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albumArtists.count(
|
||||
args.serverId || '',
|
||||
Object.keys(args.query).length === 0 ? undefined : args.query,
|
||||
),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
artistListCount: (args: QueryHookArgs<ArtistListQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
return api.controller.getArtistListCount({
|
||||
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albumArtists.count(
|
||||
args.serverId || '',
|
||||
Object.keys(args.query).length === 0 ? undefined : args.query,
|
||||
),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
topSongs: (args: QueryHookArgs<TopSongListQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
return api.controller.getTopSongs({
|
||||
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albumArtists.topSongs(args.serverId || '', args.query),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
|
||||
import { 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"
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
import type { AlbumArtistDetailQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useAlbumArtistDetail = (args: QueryHookArgs<AlbumArtistDetailQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server?.id && !!query.id,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getAlbumArtistDetail({
|
||||
apiClientProps: { server, signal },
|
||||
query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albumArtists.detail(server?.id || '', query),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { AlbumArtistListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
export const useAlbumArtistListCount = (args: QueryHookArgs<AlbumArtistListQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getAlbumArtistListCount({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albumArtists.count(
|
||||
serverId || '',
|
||||
Object.keys(query).length === 0 ? undefined : query,
|
||||
),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import type { AlbumArtistListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useAlbumArtistList = (args: QueryHookArgs<AlbumArtistListQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server?.id,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getAlbumArtistList({ apiClientProps: { server, signal }, query });
|
||||
},
|
||||
queryKey: queryKeys.albumArtists.list(server?.id || '', query),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import type { AlbumArtistDetailQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useAlbumArtistInfo = (args: QueryHookArgs<AlbumArtistDetailQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server?.id && !!query.id,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getAlbumArtistDetail({
|
||||
apiClientProps: { server, signal },
|
||||
query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albumArtists.detail(server?.id || '', query),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { ArtistListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
export const useArtistListCount = (args: QueryHookArgs<ArtistListQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getArtistListCount({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albumArtists.count(
|
||||
serverId || '',
|
||||
Object.keys(query).length === 0 ? undefined : query,
|
||||
),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useRoles = (args: QueryHookArgs<object>) => {
|
||||
const { options, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getRoles({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.roles.list(serverId || ''),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import type { TopSongListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useTopSongsList = (args: QueryHookArgs<TopSongListQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server?.id,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getTopSongs({ apiClientProps: { server, signal }, query });
|
||||
},
|
||||
queryKey: queryKeys.albumArtists.topSongs(server?.id || '', query),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useRef } from 'react';
|
||||
import { 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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
),
|
||||
|
|
|
|||
25
src/renderer/features/genres/api/genres-api.ts
Normal file
25
src/renderer/features/genres/api/genres-api.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { queryOptions } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { GenreListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
export const genresQueries = {
|
||||
list: (args: QueryHookArgs<GenreListQuery>) => {
|
||||
return queryOptions({
|
||||
gcTime: 1000 * 5,
|
||||
queryFn: ({ signal }) => {
|
||||
const server = getServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getGenreList({
|
||||
apiClientProps: { server, signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.genres.list(args.serverId || '', args.query),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { 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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import type { GenreListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useGenreList = (args: QueryHookArgs<GenreListQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getGenreList({ apiClientProps: { server, signal }, query });
|
||||
},
|
||||
queryKey: queryKeys.genres.list(server?.id || '', query),
|
||||
staleTime: 1000 * 5,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { 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
|
||||
|
|
|
|||
32
src/renderer/features/home/api/home-api.ts
Normal file
32
src/renderer/features/home/api/home-api.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { queryOptions } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { AlbumListQuery, AlbumListSort, SortOrder } from '/@/shared/types/domain-types';
|
||||
|
||||
export const homeQueries = {
|
||||
recentlyPlayed: (args: QueryHookArgs<Partial<AlbumListQuery>>) => {
|
||||
const requestQuery: AlbumListQuery = {
|
||||
limit: 5,
|
||||
sortBy: AlbumListSort.RECENTLY_PLAYED,
|
||||
sortOrder: SortOrder.ASC,
|
||||
startIndex: 0,
|
||||
...args.query,
|
||||
};
|
||||
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
const server = getServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getAlbumList({
|
||||
apiClientProps: { server, signal },
|
||||
query: requestQuery,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.albums.list(args.serverId || '', requestQuery),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { AlbumListQuery, AlbumListSort, SortOrder } from '/@/shared/types/domain-types';
|
||||
|
||||
export const useRecentlyPlayed = (args: QueryHookArgs<Partial<AlbumListQuery>>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
|
||||
const requestQuery: AlbumListQuery = {
|
||||
limit: 5,
|
||||
sortBy: AlbumListSort.RECENTLY_PLAYED,
|
||||
sortOrder: SortOrder.ASC,
|
||||
startIndex: 0,
|
||||
...query,
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server?.id,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getAlbumList({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query: requestQuery,
|
||||
});
|
||||
},
|
||||
|
||||
queryKey: queryKeys.albums.list(server?.id || '', requestQuery),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { 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]) ||
|
||||
|
|
|
|||
200
src/renderer/features/lyrics/api/lyrics-api.ts
Normal file
200
src/renderer/features/lyrics/api/lyrics-api.ts
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
import { queryOptions } from '@tanstack/react-query';
|
||||
import isElectron from 'is-electron';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById, useSettingsStore } from '/@/renderer/store';
|
||||
import { hasFeature } from '/@/shared/api/utils';
|
||||
import {
|
||||
FullLyricsMetadata,
|
||||
InternetProviderLyricResponse,
|
||||
InternetProviderLyricSearchResponse,
|
||||
LyricGetQuery,
|
||||
LyricSearchQuery,
|
||||
LyricsQuery,
|
||||
QueueSong,
|
||||
ServerType,
|
||||
StructuredLyric,
|
||||
SynchronizedLyricsArray,
|
||||
} from '/@/shared/types/domain-types';
|
||||
import { LyricSource } from '/@/shared/types/domain-types';
|
||||
import { ServerFeature } from '/@/shared/types/features-types';
|
||||
|
||||
const lyricsIpc = isElectron() ? window.api.lyrics : null;
|
||||
|
||||
// Match LRC lyrics format by https://github.com/ustbhuangyi/lyric-parser
|
||||
// [mm:ss.SSS] text
|
||||
const timeExp = /\[(\d{2,}):(\d{2})(?:\.(\d{2,3}))?]([^\n]+)(\n|$)/g;
|
||||
|
||||
// Match karaoke lyrics format returned by NetEase
|
||||
// [SSS,???] text
|
||||
const alternateTimeExp = /\[(\d*),(\d*)]([^\n]+)(\n|$)/g;
|
||||
|
||||
const formatLyrics = (lyrics: string) => {
|
||||
const synchronizedLines = lyrics.matchAll(timeExp);
|
||||
const formattedLyrics: SynchronizedLyricsArray = [];
|
||||
|
||||
for (const line of synchronizedLines) {
|
||||
const [, minute, sec, ms, text] = line;
|
||||
const minutes = parseInt(minute, 10);
|
||||
const seconds = parseInt(sec, 10);
|
||||
const milis = ms?.length === 3 ? parseInt(ms, 10) : parseInt(ms, 10) * 10;
|
||||
|
||||
const timeInMilis = (minutes * 60 + seconds) * 1000 + milis;
|
||||
|
||||
formattedLyrics.push([timeInMilis, text]);
|
||||
}
|
||||
|
||||
if (formattedLyrics.length > 0) return formattedLyrics;
|
||||
|
||||
const alternateSynchronizedLines = lyrics.matchAll(alternateTimeExp);
|
||||
for (const line of alternateSynchronizedLines) {
|
||||
const [, timeInMilis, , text] = line;
|
||||
const cleanText = text
|
||||
.replaceAll(/\(\d+,\d+\)/g, '')
|
||||
.replaceAll(/\s,/g, ',')
|
||||
.replaceAll(/\s\./g, '.');
|
||||
formattedLyrics.push([Number(timeInMilis), cleanText]);
|
||||
}
|
||||
|
||||
if (formattedLyrics.length > 0) return formattedLyrics;
|
||||
|
||||
// If no synchronized lyrics were found, return the original lyrics
|
||||
return lyrics;
|
||||
};
|
||||
|
||||
export const lyricsQueries = {
|
||||
search: (args: Omit<QueryHookArgs<LyricSearchQuery>, 'serverId'>) => {
|
||||
return queryOptions({
|
||||
gcTime: 1000 * 60 * 1,
|
||||
queryFn: () => {
|
||||
if (lyricsIpc) {
|
||||
return lyricsIpc.searchRemoteLyrics(args.query);
|
||||
}
|
||||
return {} as Record<LyricSource, InternetProviderLyricSearchResponse[]>;
|
||||
},
|
||||
queryKey: queryKeys.songs.lyricsSearch(args.query),
|
||||
staleTime: 1000 * 60 * 1,
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
serverLyrics: (args: QueryHookArgs<LyricsQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
const server = getServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
// This should only be called for Jellyfin. Return null to ignore errors
|
||||
if (server.type !== ServerType.JELLYFIN) return null;
|
||||
return api.controller.getLyrics({
|
||||
apiClientProps: { server, signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.songs.lyrics(args.serverId || '', args.query),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
songLyrics: (args: QueryHookArgs<LyricsQuery>, song: QueueSong | undefined) => {
|
||||
return queryOptions({
|
||||
gcTime: Infinity,
|
||||
queryFn: async ({ signal }): Promise<FullLyricsMetadata | null | StructuredLyric[]> => {
|
||||
const server = getServerById(song?.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
if (!song) return null;
|
||||
|
||||
const { preferLocalLyrics } = useSettingsStore.getState().lyrics;
|
||||
|
||||
let localLyrics: FullLyricsMetadata | null | StructuredLyric[] = null;
|
||||
let remoteLyrics: FullLyricsMetadata | null | StructuredLyric[] = null;
|
||||
|
||||
if (hasFeature(server, ServerFeature.LYRICS_MULTIPLE_STRUCTURED)) {
|
||||
const subsonicLyrics = await api.controller
|
||||
.getStructuredLyrics({
|
||||
apiClientProps: { server, signal },
|
||||
query: { songId: song.id },
|
||||
})
|
||||
.catch(console.error);
|
||||
|
||||
if (subsonicLyrics?.length) {
|
||||
localLyrics = subsonicLyrics;
|
||||
}
|
||||
} else if (hasFeature(server, ServerFeature.LYRICS_SINGLE_STRUCTURED)) {
|
||||
const jfLyrics = await api.controller
|
||||
.getLyrics({
|
||||
apiClientProps: { server, signal },
|
||||
query: { songId: song.id },
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
|
||||
if (jfLyrics) {
|
||||
localLyrics = {
|
||||
artist: song.artists?.[0]?.name,
|
||||
lyrics: jfLyrics,
|
||||
name: song.name,
|
||||
remote: false,
|
||||
source: server?.name ?? 'music server',
|
||||
};
|
||||
}
|
||||
} else if (song.lyrics) {
|
||||
localLyrics = {
|
||||
artist: song.artists?.[0]?.name,
|
||||
lyrics: formatLyrics(song.lyrics),
|
||||
name: song.name,
|
||||
remote: false,
|
||||
source: server?.name ?? 'music server',
|
||||
};
|
||||
}
|
||||
|
||||
if (preferLocalLyrics && localLyrics) {
|
||||
return localLyrics;
|
||||
}
|
||||
|
||||
const { fetch } = useSettingsStore.getState().lyrics;
|
||||
|
||||
if (fetch) {
|
||||
const remoteLyricsResult: InternetProviderLyricResponse | null =
|
||||
await lyricsIpc?.getRemoteLyricsBySong(song);
|
||||
|
||||
if (remoteLyricsResult) {
|
||||
remoteLyrics = {
|
||||
...remoteLyricsResult,
|
||||
lyrics: formatLyrics(remoteLyricsResult.lyrics),
|
||||
remote: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (remoteLyrics) {
|
||||
return remoteLyrics;
|
||||
}
|
||||
|
||||
if (localLyrics) {
|
||||
return localLyrics;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
queryKey: queryKeys.songs.lyrics(args.serverId || '', args.query),
|
||||
staleTime: Infinity,
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
songLyricsByRemoteId: (args: QueryHookArgs<Partial<LyricGetQuery>>) => {
|
||||
return queryOptions({
|
||||
queryFn: async () => {
|
||||
const remoteLyricsResult = await lyricsIpc?.getRemoteLyricsByRemoteId(
|
||||
args.query as any,
|
||||
);
|
||||
|
||||
if (remoteLyricsResult) {
|
||||
return formatLyrics(remoteLyricsResult);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
queryKey: queryKeys.songs.lyricsByRemoteId(args.query),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { useForm } from '@mantine/form';
|
||||
import { 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 [];
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -1,211 +0,0 @@
|
|||
import { useQuery, useQueryClient, UseQueryResult } from '@tanstack/react-query';
|
||||
import isElectron from 'is-electron';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById, useLyricsSettings } from '/@/renderer/store';
|
||||
import { hasFeature } from '/@/shared/api/utils';
|
||||
import {
|
||||
FullLyricsMetadata,
|
||||
InternetProviderLyricResponse,
|
||||
LyricGetQuery,
|
||||
LyricsQuery,
|
||||
QueueSong,
|
||||
ServerType,
|
||||
StructuredLyric,
|
||||
SynchronizedLyricsArray,
|
||||
} from '/@/shared/types/domain-types';
|
||||
import { ServerFeature } from '/@/shared/types/features-types';
|
||||
|
||||
const lyricsIpc = isElectron() ? window.api.lyrics : null;
|
||||
|
||||
// Match LRC lyrics format by https://github.com/ustbhuangyi/lyric-parser
|
||||
// [mm:ss.SSS] text
|
||||
const timeExp = /\[(\d{2,}):(\d{2})(?:\.(\d{2,3}))?]([^\n]+)(\n|$)/g;
|
||||
|
||||
// Match karaoke lyrics format returned by NetEase
|
||||
// [SSS,???] text
|
||||
const alternateTimeExp = /\[(\d*),(\d*)]([^\n]+)(\n|$)/g;
|
||||
|
||||
const formatLyrics = (lyrics: string) => {
|
||||
const synchronizedLines = lyrics.matchAll(timeExp);
|
||||
const formattedLyrics: SynchronizedLyricsArray = [];
|
||||
|
||||
for (const line of synchronizedLines) {
|
||||
const [, minute, sec, ms, text] = line;
|
||||
const minutes = parseInt(minute, 10);
|
||||
const seconds = parseInt(sec, 10);
|
||||
const milis = ms?.length === 3 ? parseInt(ms, 10) : parseInt(ms, 10) * 10;
|
||||
|
||||
const timeInMilis = (minutes * 60 + seconds) * 1000 + milis;
|
||||
|
||||
formattedLyrics.push([timeInMilis, text]);
|
||||
}
|
||||
|
||||
if (formattedLyrics.length > 0) return formattedLyrics;
|
||||
|
||||
const alternateSynchronizedLines = lyrics.matchAll(alternateTimeExp);
|
||||
for (const line of alternateSynchronizedLines) {
|
||||
const [, timeInMilis, , text] = line;
|
||||
const cleanText = text
|
||||
.replaceAll(/\(\d+,\d+\)/g, '')
|
||||
.replaceAll(/\s,/g, ',')
|
||||
.replaceAll(/\s\./g, '.');
|
||||
formattedLyrics.push([Number(timeInMilis), cleanText]);
|
||||
}
|
||||
|
||||
if (formattedLyrics.length > 0) return formattedLyrics;
|
||||
|
||||
// If no synchronized lyrics were found, return the original lyrics
|
||||
return lyrics;
|
||||
};
|
||||
|
||||
export const useServerLyrics = (
|
||||
args: QueryHookArgs<LyricsQuery>,
|
||||
): UseQueryResult<null | string> => {
|
||||
const { query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
// Note: This currently fetches for every song, even if it shouldn't have
|
||||
// lyrics, because for some reason HasLyrics is not exposed. Thus, ignore the error
|
||||
onError: () => {},
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
// This should only be called for Jellyfin. Return null to ignore errors
|
||||
if (server.type !== ServerType.JELLYFIN) return null;
|
||||
return api.controller.getLyrics({ apiClientProps: { server, signal }, query });
|
||||
},
|
||||
queryKey: queryKeys.songs.lyrics(server?.id || '', query),
|
||||
});
|
||||
};
|
||||
|
||||
export const useSongLyricsBySong = (
|
||||
args: QueryHookArgs<LyricsQuery>,
|
||||
song: QueueSong | undefined,
|
||||
): UseQueryResult<FullLyricsMetadata | StructuredLyric[]> => {
|
||||
const { query } = args;
|
||||
const { fetch, preferLocalLyrics } = useLyricsSettings();
|
||||
const server = getServerById(song?.serverId);
|
||||
|
||||
return useQuery({
|
||||
cacheTime: Infinity,
|
||||
enabled: !!song && !!server,
|
||||
onError: () => {},
|
||||
queryFn: async ({ signal }): Promise<FullLyricsMetadata | null | StructuredLyric[]> => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
if (!song) return null;
|
||||
|
||||
let localLyrics: FullLyricsMetadata | null | StructuredLyric[] = null;
|
||||
let remoteLyrics: FullLyricsMetadata | null | StructuredLyric[] = null;
|
||||
|
||||
if (hasFeature(server, ServerFeature.LYRICS_MULTIPLE_STRUCTURED)) {
|
||||
const subsonicLyrics = await api.controller
|
||||
.getStructuredLyrics({
|
||||
apiClientProps: { server, signal },
|
||||
query: { songId: song.id },
|
||||
})
|
||||
.catch(console.error);
|
||||
|
||||
if (subsonicLyrics?.length) {
|
||||
localLyrics = subsonicLyrics;
|
||||
}
|
||||
} else if (hasFeature(server, ServerFeature.LYRICS_SINGLE_STRUCTURED)) {
|
||||
const jfLyrics = await api.controller
|
||||
.getLyrics({
|
||||
apiClientProps: { server, signal },
|
||||
query: { songId: song.id },
|
||||
})
|
||||
.catch((err) => console.error(err));
|
||||
|
||||
if (jfLyrics) {
|
||||
localLyrics = {
|
||||
artist: song.artists?.[0]?.name,
|
||||
lyrics: jfLyrics,
|
||||
name: song.name,
|
||||
remote: false,
|
||||
source: server?.name ?? 'music server',
|
||||
};
|
||||
}
|
||||
} else if (song.lyrics) {
|
||||
localLyrics = {
|
||||
artist: song.artists?.[0]?.name,
|
||||
lyrics: formatLyrics(song.lyrics),
|
||||
name: song.name,
|
||||
remote: false,
|
||||
source: server?.name ?? 'music server',
|
||||
};
|
||||
}
|
||||
|
||||
if (preferLocalLyrics && localLyrics) {
|
||||
return localLyrics;
|
||||
}
|
||||
|
||||
if (fetch) {
|
||||
const remoteLyricsResult: InternetProviderLyricResponse | null =
|
||||
await lyricsIpc?.getRemoteLyricsBySong(song);
|
||||
|
||||
if (remoteLyricsResult) {
|
||||
remoteLyrics = {
|
||||
...remoteLyricsResult,
|
||||
lyrics: formatLyrics(remoteLyricsResult.lyrics),
|
||||
remote: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (remoteLyrics) {
|
||||
return remoteLyrics;
|
||||
}
|
||||
|
||||
if (localLyrics) {
|
||||
return localLyrics;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
queryKey: queryKeys.songs.lyrics(server?.id || '', query),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
};
|
||||
|
||||
export const useSongLyricsByRemoteId = (
|
||||
args: QueryHookArgs<Partial<LyricGetQuery>>,
|
||||
): UseQueryResult<null | string> => {
|
||||
const queryClient = useQueryClient();
|
||||
const { query, serverId } = args;
|
||||
|
||||
return useQuery({
|
||||
enabled: !!query.remoteSongId && !!query.remoteSource,
|
||||
onError: () => {},
|
||||
onSuccess: (data) => {
|
||||
if (!data || !query.song) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lyricsResult = {
|
||||
artist: query.song.artists?.[0]?.name,
|
||||
lyrics: data,
|
||||
name: query.song.name,
|
||||
remote: false,
|
||||
source: query.remoteSource,
|
||||
};
|
||||
|
||||
queryClient.setQueryData(
|
||||
queryKeys.songs.lyrics(serverId, { songId: query.song.id }),
|
||||
lyricsResult,
|
||||
);
|
||||
},
|
||||
queryFn: async () => {
|
||||
const remoteLyricsResult = await lyricsIpc?.getRemoteLyricsByRemoteId(query as any);
|
||||
|
||||
if (remoteLyricsResult) {
|
||||
return formatLyrics(remoteLyricsResult);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
queryKey: queryKeys.songs.lyricsByRemoteId(query),
|
||||
});
|
||||
};
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import isElectron from 'is-electron';
|
||||
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import {
|
||||
InternetProviderLyricSearchResponse,
|
||||
LyricSearchQuery,
|
||||
LyricSource,
|
||||
} from '/@/shared/types/domain-types';
|
||||
|
||||
const lyricsIpc = isElectron() ? window.api.lyrics : null;
|
||||
|
||||
export const useLyricSearch = (args: Omit<QueryHookArgs<LyricSearchQuery>, 'serverId'>) => {
|
||||
const { options, query } = args;
|
||||
|
||||
return useQuery<Record<LyricSource, InternetProviderLyricSearchResponse[]>>({
|
||||
cacheTime: 1000 * 60 * 1,
|
||||
enabled: !!query.artist || !!query.name,
|
||||
queryFn: () => {
|
||||
if (lyricsIpc) {
|
||||
return lyricsIpc.searchRemoteLyrics(query);
|
||||
}
|
||||
return {} as Record<LyricSource, InternetProviderLyricSearchResponse[]>;
|
||||
},
|
||||
queryKey: queryKeys.songs.lyricsSearch(query),
|
||||
staleTime: 1000 * 60 * 1,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -94,7 +94,7 @@ export const ShuffleAllModal = ({
|
|||
|
||||
const handlePlay = async (playType: Play) => {
|
||||
const 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({
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
57
src/renderer/features/playlists/api/playlists-api.ts
Normal file
57
src/renderer/features/playlists/api/playlists-api.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { queryOptions } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import {
|
||||
PlaylistDetailQuery,
|
||||
PlaylistListQuery,
|
||||
PlaylistSongListQuery,
|
||||
} from '/@/shared/types/domain-types';
|
||||
|
||||
export const playlistsQueries = {
|
||||
detail: (args: QueryHookArgs<PlaylistDetailQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
const server = getServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getPlaylistDetail({
|
||||
apiClientProps: { server, signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.playlists.detail(args.serverId || '', args.query.id, args.query),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
list: (args: QueryHookArgs<PlaylistListQuery>) => {
|
||||
return queryOptions({
|
||||
gcTime: 1000 * 60 * 60,
|
||||
queryFn: ({ signal }) => {
|
||||
const server = getServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getPlaylistList({
|
||||
apiClientProps: { server, signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.playlists.list(args.serverId || '', args.query),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
songList: (args: QueryHookArgs<PlaylistSongListQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
const server = getServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getPlaylistSongList({
|
||||
apiClientProps: { server, signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.playlists.songList(args.serverId || '', args.query.id),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { useForm } from '@mantine/form';
|
||||
import { 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 [];
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import type { PlaylistDetailQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const usePlaylistDetail = (args: QueryHookArgs<PlaylistDetailQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server?.id,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getPlaylistDetail({ apiClientProps: { server, signal }, query });
|
||||
},
|
||||
queryKey: queryKeys.playlists.detail(server?.id || '', query.id, query),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import type { QueryOptions } from '/@/renderer/lib/react-query';
|
||||
import type { PlaylistListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const usePlaylistList = (args: {
|
||||
options?: QueryOptions;
|
||||
query: PlaylistListQuery;
|
||||
serverId?: string;
|
||||
}) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
cacheTime: 1000 * 60 * 60,
|
||||
enabled: !!server?.id,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getPlaylistList({ apiClientProps: { server, signal }, query });
|
||||
},
|
||||
queryKey: queryKeys.playlists.list(server?.id || '', query),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import type { PlaylistSongListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const usePlaylistSongList = (args: QueryHookArgs<PlaylistSongListQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getPlaylistSongList({
|
||||
apiClientProps: { server, signal },
|
||||
query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.playlists.songList(server?.id || '', query.id),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,20 +1,21 @@
|
|||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { 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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
22
src/renderer/features/search/api/search-api.ts
Normal file
22
src/renderer/features/search/api/search-api.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { queryOptions } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { SearchQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
export const searchQueries = {
|
||||
search: (args: QueryHookArgs<SearchQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
return api.controller.search({
|
||||
apiClientProps: { server: getServerById(args.serverId), signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.search.list(args.serverId || '', args.query),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
||||
import { 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);
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { SearchQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
export const useSearch = (args: QueryHookArgs<SearchQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.search({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.search.list(serverId || '', query),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
63
src/renderer/features/shared/api/shared-api.ts
Normal file
63
src/renderer/features/shared/api/shared-api.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { queryOptions } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { MusicFolderListQuery, TagQuery, UserListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
export const sharedQueries = {
|
||||
musicFolders: (args: QueryHookArgs<MusicFolderListQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
const server = getServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getMusicFolderList({ apiClientProps: { server, signal } });
|
||||
},
|
||||
queryKey: queryKeys.musicFolders.list(args.serverId || ''),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
roles: (args: QueryHookArgs<object>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
const server = getServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getRoles({
|
||||
apiClientProps: { server, signal },
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.roles.list(args.serverId || ''),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
tags: (args: QueryHookArgs<TagQuery>) => {
|
||||
return queryOptions({
|
||||
gcTime: 1000 * 60,
|
||||
queryFn: ({ signal }) => {
|
||||
const server = getServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getTags({
|
||||
apiClientProps: { server, signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.tags.list(args.serverId || '', args.query.type),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
users: (args: QueryHookArgs<UserListQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
const server = getServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getUserList({
|
||||
apiClientProps: { server, signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.users.list(args.serverId || '', args.query),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { MusicFolderListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
export const useMusicFolders = (args: QueryHookArgs<MusicFolderListQuery>) => {
|
||||
const { options, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
|
||||
const query = useQuery({
|
||||
enabled: !!server,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getMusicFolderList({ apiClientProps: { server, signal } });
|
||||
},
|
||||
queryKey: queryKeys.musicFolders.list(server?.id || ''),
|
||||
...options,
|
||||
});
|
||||
|
||||
return query;
|
||||
};
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { closeAllModals, openModal } from '@mantine/modals';
|
||||
import { 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) => {
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { SimilarSongsQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
export const useSimilarSongs = (args: QueryHookArgs<SimilarSongsQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
|
||||
return api.controller.getSimilarSongs({
|
||||
apiClientProps: { server, signal },
|
||||
query: {
|
||||
albumArtistIds: query.albumArtistIds,
|
||||
count: query.count ?? 50,
|
||||
songId: query.songId,
|
||||
},
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.songs.similar(server?.id || '', query),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
60
src/renderer/features/songs/api/songs-api.ts
Normal file
60
src/renderer/features/songs/api/songs-api.ts
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import { queryOptions } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { SimilarSongsQuery, SongListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
export const songsQueries = {
|
||||
list: (args: QueryHookArgs<SongListQuery>, imageSize?: number) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
const server = getServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return controller.getSongList({
|
||||
apiClientProps: { server, signal },
|
||||
query: { ...args.query, imageSize },
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.songs.list(args.serverId || '', { ...args.query, imageSize }),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
listCount: (args: QueryHookArgs<SongListQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
const server = getServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getSongListCount({
|
||||
apiClientProps: { server, signal },
|
||||
query: args.query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.songs.count(
|
||||
args.serverId || '',
|
||||
Object.keys(args.query).length === 0 ? undefined : args.query,
|
||||
),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
similar: (args: QueryHookArgs<SimilarSongsQuery>) => {
|
||||
return queryOptions({
|
||||
queryFn: ({ signal }) => {
|
||||
const server = getServerById(args.serverId);
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getSimilarSongs({
|
||||
apiClientProps: { server, signal },
|
||||
query: {
|
||||
albumArtistIds: args.query.albumArtistIds,
|
||||
count: args.query.count ?? 50,
|
||||
songId: args.query.songId,
|
||||
},
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.songs.similar(args.serverId || '', args.query),
|
||||
...args.options,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { 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(',');
|
||||
|
|
|
|||
|
|
@ -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 [];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 [];
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import type { SongListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useSongListCount = (args: QueryHookArgs<SongListQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getSongListCount({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.songs.count(
|
||||
serverId || '',
|
||||
Object.keys(query).length === 0 ? undefined : query,
|
||||
),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import type { SongListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useSongList = (args: QueryHookArgs<SongListQuery>, imageSize?: number) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server?.id,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return controller.getSongList({
|
||||
apiClientProps: { server, signal },
|
||||
query: { ...query, imageSize },
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.songs.list(server?.id || '', { ...query, imageSize }),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,17 +1,18 @@
|
|||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import { hasFeature } from '/@/shared/api/utils';
|
||||
import { TagQuery } from '/@/shared/types/domain-types';
|
||||
import { ServerFeature } from '/@/shared/types/features-types';
|
||||
|
||||
export const useTagList = (args: QueryHookArgs<TagQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server && hasFeature(server, ServerFeature.TAGS),
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.getTags({ apiClientProps: { server, signal }, query });
|
||||
},
|
||||
queryKey: queryKeys.tags.list(server?.id || '', query.type),
|
||||
staleTime: 1000 * 60,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import type { UserListQuery } from '/@/shared/types/domain-types';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useUserList = (args: QueryHookArgs<UserListQuery>) => {
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!server,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
api.controller.getUserList({ apiClientProps: { server, signal }, query });
|
||||
},
|
||||
queryKey: queryKeys.users.list(server?.id || '', query),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import type {
|
||||
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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue