mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-16 16:34:24 +02:00
Prevent double fetching when force refreshing paginated views (#1637)
* Prevent double fetching when force refreshing paginated views * remove await from infinite list loader query invalidation * add mutation and loading state to list refresh * add non-suspense query to list genre filters to add loading state * remove list count data set on random queries --------- Co-authored-by: jeffvli <jeffvictorli@gmail.com>
This commit is contained in:
@@ -62,7 +62,11 @@ export const getOptimizedListCount = async <
|
|||||||
query: pageQuery,
|
query: pageQuery,
|
||||||
});
|
});
|
||||||
|
|
||||||
client.setQueryData(pageQueryKey, pageResult);
|
const keyContainsRandom = JSON.stringify(pageQueryKey).toLowerCase().includes('random');
|
||||||
|
|
||||||
|
if (!keyContainsRandom) {
|
||||||
|
client.setQueryData(pageQueryKey, pageResult);
|
||||||
|
}
|
||||||
|
|
||||||
return pageResult.totalRecordCount ?? 0;
|
return pageResult.totalRecordCount ?? 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
useMutation,
|
||||||
useQuery,
|
useQuery,
|
||||||
useQueryClient,
|
useQueryClient,
|
||||||
useSuspenseQuery,
|
useSuspenseQuery,
|
||||||
@@ -11,6 +12,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
|||||||
import { useListContext } from '/@/renderer/context/list-context';
|
import { useListContext } from '/@/renderer/context/list-context';
|
||||||
import { eventEmitter } from '/@/renderer/events/event-emitter';
|
import { eventEmitter } from '/@/renderer/events/event-emitter';
|
||||||
import { UserFavoriteEventPayload, UserRatingEventPayload } from '/@/renderer/events/events';
|
import { UserFavoriteEventPayload, UserRatingEventPayload } from '/@/renderer/events/events';
|
||||||
|
import { getListRefreshMutationKey } from '/@/renderer/features/shared/components/list-refresh-button';
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export const getListQueryKeyName = (itemType: LibraryItem): string => {
|
export const getListQueryKeyName = (itemType: LibraryItem): string => {
|
||||||
@@ -293,10 +295,10 @@ export const useItemListInfiniteLoader = ({
|
|||||||
[onRangeChangedBase],
|
[onRangeChangedBase],
|
||||||
);
|
);
|
||||||
|
|
||||||
const refresh = useCallback(
|
const refreshMutation = useMutation({
|
||||||
async (force?: boolean) => {
|
mutationFn: async (force?: boolean) => {
|
||||||
// Invalidate all queries to ensure fresh data
|
// Invalidate all queries to ensure fresh data
|
||||||
await queryClient.invalidateQueries();
|
queryClient.invalidateQueries();
|
||||||
|
|
||||||
// Reset the infinite list data
|
// Reset the infinite list data
|
||||||
const currentData = queryClient.getQueryData<{
|
const currentData = queryClient.getQueryData<{
|
||||||
@@ -320,7 +322,7 @@ export const useItemListInfiniteLoader = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add a delay to make the refresh visually clear
|
// Add a delay to make the refresh visually clear
|
||||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
// await new Promise((resolve) => setTimeout(resolve, 150));
|
||||||
|
|
||||||
// Determine which page to refetch based on current visible range
|
// Determine which page to refetch based on current visible range
|
||||||
let pageToFetch = 0;
|
let pageToFetch = 0;
|
||||||
@@ -344,7 +346,12 @@ export const useItemListInfiniteLoader = ({
|
|||||||
stopIndex,
|
stopIndex,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[queryClient, itemsPerPage, onRangeChangedBase, dataQueryKey, totalItemCount, fetchPage],
|
mutationKey: getListRefreshMutationKey(eventKey),
|
||||||
|
});
|
||||||
|
|
||||||
|
const refresh = useCallback(
|
||||||
|
async (force?: boolean) => refreshMutation.mutateAsync(force),
|
||||||
|
[refreshMutation],
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateItems = useCallback(
|
const updateItems = useCallback(
|
||||||
@@ -376,7 +383,7 @@ export const useItemListInfiniteLoader = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return refresh(true);
|
refreshMutation.mutate(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
eventEmitter.on('ITEM_LIST_REFRESH', handleRefresh);
|
eventEmitter.on('ITEM_LIST_REFRESH', handleRefresh);
|
||||||
@@ -384,7 +391,7 @@ export const useItemListInfiniteLoader = ({
|
|||||||
return () => {
|
return () => {
|
||||||
eventEmitter.off('ITEM_LIST_REFRESH', handleRefresh);
|
eventEmitter.off('ITEM_LIST_REFRESH', handleRefresh);
|
||||||
};
|
};
|
||||||
}, [eventKey, refresh]);
|
}, [eventKey, refreshMutation]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleFavorite = (payload: UserFavoriteEventPayload) => {
|
const handleFavorite = (payload: UserFavoriteEventPayload) => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
useMutation,
|
||||||
useQuery,
|
useQuery,
|
||||||
useQueryClient,
|
useQueryClient,
|
||||||
useSuspenseQuery,
|
useSuspenseQuery,
|
||||||
@@ -10,6 +11,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
|
|||||||
import { useListContext } from '/@/renderer/context/list-context';
|
import { useListContext } from '/@/renderer/context/list-context';
|
||||||
import { eventEmitter } from '/@/renderer/events/event-emitter';
|
import { eventEmitter } from '/@/renderer/events/event-emitter';
|
||||||
import { UserFavoriteEventPayload, UserRatingEventPayload } from '/@/renderer/events/events';
|
import { UserFavoriteEventPayload, UserRatingEventPayload } from '/@/renderer/events/events';
|
||||||
|
import { getListRefreshMutationKey } from '/@/renderer/features/shared/components/list-refresh-button';
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
const getQueryKeyName = (itemType: LibraryItem): string => {
|
const getQueryKeyName = (itemType: LibraryItem): string => {
|
||||||
@@ -83,7 +85,7 @@ export const useItemListPaginatedLoader = ({
|
|||||||
[itemsPerPage, startIndex, query],
|
[itemsPerPage, startIndex, query],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, refetch: queryRefetch } = useQuery({
|
const { data } = useQuery({
|
||||||
gcTime: 1000 * 15,
|
gcTime: 1000 * 15,
|
||||||
placeholderData: { items: getInitialData(itemsPerPage) },
|
placeholderData: { items: getInitialData(itemsPerPage) },
|
||||||
queryFn: async ({ signal }) => {
|
queryFn: async ({ signal }) => {
|
||||||
@@ -98,22 +100,20 @@ export const useItemListPaginatedLoader = ({
|
|||||||
staleTime: 1000 * 15,
|
staleTime: 1000 * 15,
|
||||||
});
|
});
|
||||||
|
|
||||||
const refresh = useCallback(
|
const refreshMutation = useMutation({
|
||||||
async (force?: boolean) => {
|
mutationFn: async (force?: boolean) => {
|
||||||
const queryKey = queryKeys[getQueryKeyName(itemType)].list(serverId, queryParams);
|
const queryKey = queryKeys[getQueryKeyName(itemType)].list(serverId, queryParams);
|
||||||
|
|
||||||
await queryClient.invalidateQueries();
|
|
||||||
|
|
||||||
if (force) {
|
if (force) {
|
||||||
queryClient.setQueryData(queryKey, {
|
queryClient.setQueryData(queryKey, {
|
||||||
items: getInitialData(itemsPerPage),
|
items: getInitialData(itemsPerPage),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryRefetch();
|
await queryClient.invalidateQueries();
|
||||||
},
|
},
|
||||||
[queryClient, queryRefetch, queryParams, serverId, itemType, itemsPerPage],
|
mutationKey: getListRefreshMutationKey(eventKey ?? 'paginated'),
|
||||||
);
|
});
|
||||||
|
|
||||||
const updateItems = useCallback(
|
const updateItems = useCallback(
|
||||||
(indexes: number[], value: object) => {
|
(indexes: number[], value: object) => {
|
||||||
@@ -153,7 +153,7 @@ export const useItemListPaginatedLoader = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return refresh(true);
|
refreshMutation.mutate(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFavorite = (payload: UserFavoriteEventPayload) => {
|
const handleFavorite = (payload: UserFavoriteEventPayload) => {
|
||||||
@@ -220,7 +220,7 @@ export const useItemListPaginatedLoader = ({
|
|||||||
eventEmitter.off('USER_FAVORITE', handleFavorite);
|
eventEmitter.off('USER_FAVORITE', handleFavorite);
|
||||||
eventEmitter.off('USER_RATING', handleRating);
|
eventEmitter.off('USER_RATING', handleRating);
|
||||||
};
|
};
|
||||||
}, [data, eventKey, itemType, serverId, refresh, updateItems]);
|
}, [data, eventKey, itemType, refreshMutation, serverId, updateItems]);
|
||||||
|
|
||||||
return { data: data?.items || [], pageCount, totalItemCount };
|
return { data: data?.items || [], pageCount, totalItemCount };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,9 +52,12 @@ export const JellyfinAlbumFilters = ({
|
|||||||
setMinYear,
|
setMinYear,
|
||||||
} = useAlbumListFilters();
|
} = useAlbumListFilters();
|
||||||
|
|
||||||
// TODO - eventually replace with /items/filters endpoint to fetch genres and tags specific to the selected library
|
|
||||||
const genreListQuery = useQuery(
|
const genreListQuery = useQuery(
|
||||||
genresQueries.list({
|
genresQueries.list({
|
||||||
|
options: {
|
||||||
|
gcTime: 1000 * 60 * 2,
|
||||||
|
staleTime: 1000 * 60 * 1,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
sortBy: GenreListSort.NAME,
|
sortBy: GenreListSort.NAME,
|
||||||
sortOrder: SortOrder.ASC,
|
sortOrder: SortOrder.ASC,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { getItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
import { getItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||||
import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters';
|
import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters';
|
||||||
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { useGenreList } from '/@/renderer/features/genres/api/genres-api';
|
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||||
import {
|
import {
|
||||||
ArtistMultiSelectRow,
|
ArtistMultiSelectRow,
|
||||||
GenreMultiSelectRow,
|
GenreMultiSelectRow,
|
||||||
@@ -22,7 +22,12 @@ import { Stack } from '/@/shared/components/stack/stack';
|
|||||||
import { Switch } from '/@/shared/components/switch/switch';
|
import { Switch } from '/@/shared/components/switch/switch';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback';
|
import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback';
|
||||||
import { AlbumArtistListSort, LibraryItem, SortOrder } from '/@/shared/types/domain-types';
|
import {
|
||||||
|
AlbumArtistListSort,
|
||||||
|
GenreListSort,
|
||||||
|
LibraryItem,
|
||||||
|
SortOrder,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
interface NavidromeAlbumFiltersProps {
|
interface NavidromeAlbumFiltersProps {
|
||||||
disableArtistFilter?: boolean;
|
disableArtistFilter?: boolean;
|
||||||
@@ -54,7 +59,20 @@ export const NavidromeAlbumFilters = ({
|
|||||||
setRecentlyPlayed,
|
setRecentlyPlayed,
|
||||||
} = useAlbumListFilters();
|
} = useAlbumListFilters();
|
||||||
|
|
||||||
const genreListQuery = useGenreList();
|
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(() => {
|
const genreList = useMemo(() => {
|
||||||
if (!genreListQuery?.data) return [];
|
if (!genreListQuery?.data) return [];
|
||||||
@@ -333,6 +351,7 @@ export const NavidromeAlbumFilters = ({
|
|||||||
<VirtualMultiSelect
|
<VirtualMultiSelect
|
||||||
displayCountType="album"
|
displayCountType="album"
|
||||||
height={220}
|
height={220}
|
||||||
|
isLoading={genreListQuery.isFetching}
|
||||||
label={genreFilterLabel}
|
label={genreFilterLabel}
|
||||||
onChange={handleGenreChange}
|
onChange={handleGenreChange}
|
||||||
options={genreList}
|
options={genreList}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
import { ChangeEvent, useCallback, useMemo } from 'react';
|
import { ChangeEvent, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { getItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
import { getItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||||
import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters';
|
import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters';
|
||||||
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { useGenreList } from '/@/renderer/features/genres/api/genres-api';
|
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||||
import {
|
import {
|
||||||
ArtistMultiSelectRow,
|
ArtistMultiSelectRow,
|
||||||
GenreMultiSelectRow,
|
GenreMultiSelectRow,
|
||||||
@@ -21,7 +21,12 @@ import { Stack } from '/@/shared/components/stack/stack';
|
|||||||
import { Switch } from '/@/shared/components/switch/switch';
|
import { Switch } from '/@/shared/components/switch/switch';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback';
|
import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback';
|
||||||
import { AlbumArtistListSort, LibraryItem, SortOrder } from '/@/shared/types/domain-types';
|
import {
|
||||||
|
AlbumArtistListSort,
|
||||||
|
GenreListSort,
|
||||||
|
LibraryItem,
|
||||||
|
SortOrder,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
interface SubsonicAlbumFiltersProps {
|
interface SubsonicAlbumFiltersProps {
|
||||||
disableArtistFilter?: boolean;
|
disableArtistFilter?: boolean;
|
||||||
@@ -90,7 +95,20 @@ export const SubsonicAlbumFilters = ({
|
|||||||
[isArtistDisabled, setAlbumArtist],
|
[isArtistDisabled, setAlbumArtist],
|
||||||
);
|
);
|
||||||
|
|
||||||
const genreListQuery = useGenreList();
|
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(() => {
|
const genreList = useMemo(() => {
|
||||||
if (!genreListQuery?.data) return [];
|
if (!genreListQuery?.data) return [];
|
||||||
@@ -252,6 +270,7 @@ export const SubsonicAlbumFilters = ({
|
|||||||
disabled={isArtistDisabled}
|
disabled={isArtistDisabled}
|
||||||
displayCountType="album"
|
displayCountType="album"
|
||||||
height={300}
|
height={300}
|
||||||
|
isLoading={albumArtistListQuery.isFetching}
|
||||||
label={artistFilterLabel}
|
label={artistFilterLabel}
|
||||||
onChange={handleAlbumArtistFilter}
|
onChange={handleAlbumArtistFilter}
|
||||||
options={selectableAlbumArtists}
|
options={selectableAlbumArtists}
|
||||||
@@ -268,6 +287,7 @@ export const SubsonicAlbumFilters = ({
|
|||||||
disabled={isGenreDisabled}
|
disabled={isGenreDisabled}
|
||||||
displayCountType="album"
|
displayCountType="album"
|
||||||
height={220}
|
height={220}
|
||||||
|
isLoading={genreListQuery.isFetching}
|
||||||
label={genreFilterLabel}
|
label={genreFilterLabel}
|
||||||
onChange={handleGenresFilter}
|
onChange={handleGenresFilter}
|
||||||
options={genreList}
|
options={genreList}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useIsMutating } from '@tanstack/react-query';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { eventEmitter } from '/@/renderer/events/event-emitter';
|
import { eventEmitter } from '/@/renderer/events/event-emitter';
|
||||||
@@ -10,9 +11,16 @@ interface ListRefreshButtonProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ListRefreshButton = ({ disabled, listKey }: ListRefreshButtonProps) => {
|
export const ListRefreshButton = ({ disabled, listKey }: ListRefreshButtonProps) => {
|
||||||
|
const isRefreshing = useIsMutating({ mutationKey: getListRefreshMutationKey(listKey) }) > 0;
|
||||||
|
|
||||||
const handleRefresh = useCallback(() => {
|
const handleRefresh = useCallback(() => {
|
||||||
eventEmitter.emit('ITEM_LIST_REFRESH', { key: listKey });
|
eventEmitter.emit('ITEM_LIST_REFRESH', { key: listKey });
|
||||||
}, [listKey]);
|
}, [listKey]);
|
||||||
|
|
||||||
return <RefreshButton disabled={disabled} onClick={handleRefresh} />;
|
return <RefreshButton disabled={disabled} loading={isRefreshing} onClick={handleRefresh} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const LIST_REFRESH_MUTATION_KEY = 'item-list-refresh';
|
||||||
|
|
||||||
|
export const getListRefreshMutationKey = (listKey: string) =>
|
||||||
|
[LIST_REFRESH_MUTATION_KEY, listKey] as const;
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import { ActionIcon, ActionIconProps } from '/@/shared/components/action-icon/action-icon';
|
import { ActionIcon, ActionIconProps } from '/@/shared/components/action-icon/action-icon';
|
||||||
|
|
||||||
interface RefreshButtonProps extends ActionIconProps {}
|
interface RefreshButtonProps extends ActionIconProps {
|
||||||
|
loading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export const RefreshButton = ({ onClick, ...props }: RefreshButtonProps) => {
|
export const RefreshButton = ({ loading, onClick, ...props }: RefreshButtonProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -14,6 +16,7 @@ export const RefreshButton = ({ onClick, ...props }: RefreshButtonProps) => {
|
|||||||
size: 'lg',
|
size: 'lg',
|
||||||
...props.iconProps,
|
...props.iconProps,
|
||||||
}}
|
}}
|
||||||
|
loading={loading}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
label: t('common.refresh', { postProcess: 'sentenceCase' }),
|
label: t('common.refresh', { postProcess: 'sentenceCase' }),
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { getItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
import { getItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||||
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { useGenreList } from '/@/renderer/features/genres/api/genres-api';
|
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||||
import {
|
import {
|
||||||
ArtistMultiSelectRow,
|
ArtistMultiSelectRow,
|
||||||
GenreMultiSelectRow,
|
GenreMultiSelectRow,
|
||||||
@@ -22,7 +22,12 @@ import { Stack } from '/@/shared/components/stack/stack';
|
|||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import { YesNoSelect } from '/@/shared/components/yes-no-select/yes-no-select';
|
import { YesNoSelect } from '/@/shared/components/yes-no-select/yes-no-select';
|
||||||
import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback';
|
import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback';
|
||||||
import { AlbumArtistListSort, LibraryItem, SortOrder } from '/@/shared/types/domain-types';
|
import {
|
||||||
|
AlbumArtistListSort,
|
||||||
|
GenreListSort,
|
||||||
|
LibraryItem,
|
||||||
|
SortOrder,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
interface JellyfinSongFiltersProps {
|
interface JellyfinSongFiltersProps {
|
||||||
disableArtistFilter?: boolean;
|
disableArtistFilter?: boolean;
|
||||||
@@ -41,7 +46,20 @@ export const JellyfinSongFilters = ({
|
|||||||
|
|
||||||
// Despite the fact that getTags returns genres, it only returns genre names.
|
// Despite the fact that getTags returns genres, it only returns genre names.
|
||||||
// We prefer using IDs, hence the double query
|
// We prefer using IDs, hence the double query
|
||||||
const genreListQuery = useGenreList();
|
const genreListQuery = useQuery(
|
||||||
|
genresQueries.list({
|
||||||
|
options: {
|
||||||
|
gcTime: 1000 * 60 * 2,
|
||||||
|
staleTime: 1000 * 60 * 1,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
sortBy: GenreListSort.NAME,
|
||||||
|
sortOrder: SortOrder.ASC,
|
||||||
|
startIndex: 0,
|
||||||
|
},
|
||||||
|
serverId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const genreList = useMemo(() => {
|
const genreList = useMemo(() => {
|
||||||
if (!genreListQuery.data) return [];
|
if (!genreListQuery.data) return [];
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { getItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
import { getItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||||
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
|
||||||
import { useGenreList } from '/@/renderer/features/genres/api/genres-api';
|
import { genresQueries } from '/@/renderer/features/genres/api/genres-api';
|
||||||
import {
|
import {
|
||||||
ArtistMultiSelectRow,
|
ArtistMultiSelectRow,
|
||||||
GenreMultiSelectRow,
|
GenreMultiSelectRow,
|
||||||
@@ -21,7 +21,12 @@ import { SegmentedControl } from '/@/shared/components/segmented-control/segment
|
|||||||
import { Stack } from '/@/shared/components/stack/stack';
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback';
|
import { useDebouncedCallback } from '/@/shared/hooks/use-debounced-callback';
|
||||||
import { AlbumArtistListSort, LibraryItem, SortOrder } from '/@/shared/types/domain-types';
|
import {
|
||||||
|
AlbumArtistListSort,
|
||||||
|
GenreListSort,
|
||||||
|
LibraryItem,
|
||||||
|
SortOrder,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
interface NavidromeSongFiltersProps {
|
interface NavidromeSongFiltersProps {
|
||||||
disableArtistFilter?: boolean;
|
disableArtistFilter?: boolean;
|
||||||
@@ -38,7 +43,20 @@ export const NavidromeSongFilters = ({
|
|||||||
const { query, setArtistIds, setCustom, setFavorite, setGenreId, setMaxYear, setMinYear } =
|
const { query, setArtistIds, setCustom, setFavorite, setGenreId, setMaxYear, setMinYear } =
|
||||||
useSongListFilters();
|
useSongListFilters();
|
||||||
|
|
||||||
const genreListQuery = useGenreList();
|
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(() => {
|
const genreList = useMemo(() => {
|
||||||
if (!genreListQuery?.data) return [];
|
if (!genreListQuery?.data) return [];
|
||||||
@@ -279,6 +297,7 @@ export const NavidromeSongFilters = ({
|
|||||||
<VirtualMultiSelect
|
<VirtualMultiSelect
|
||||||
displayCountType="song"
|
displayCountType="song"
|
||||||
height={220}
|
height={220}
|
||||||
|
isLoading={genreListQuery.isFetching}
|
||||||
label={genreFilterLabel}
|
label={genreFilterLabel}
|
||||||
onChange={handleGenreChange}
|
onChange={handleGenreChange}
|
||||||
options={genreList}
|
options={genreList}
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ export const SubsonicSongFilters = ({
|
|||||||
disabled={isArtistDisabled}
|
disabled={isArtistDisabled}
|
||||||
displayCountType="song"
|
displayCountType="song"
|
||||||
height={300}
|
height={300}
|
||||||
|
isLoading={albumArtistListQuery.isFetching}
|
||||||
label={artistFilterLabel}
|
label={artistFilterLabel}
|
||||||
onChange={handleArtistFilter}
|
onChange={handleArtistFilter}
|
||||||
options={selectableAlbumArtists}
|
options={selectableAlbumArtists}
|
||||||
@@ -175,6 +176,7 @@ export const SubsonicSongFilters = ({
|
|||||||
disabled={isGenreDisabled}
|
disabled={isGenreDisabled}
|
||||||
displayCountType="song"
|
displayCountType="song"
|
||||||
height={220}
|
height={220}
|
||||||
|
isLoading={genreListQuery.isFetching}
|
||||||
label={genreFilterLabel}
|
label={genreFilterLabel}
|
||||||
onChange={handleGenresFilter}
|
onChange={handleGenresFilter}
|
||||||
options={genreList}
|
options={genreList}
|
||||||
|
|||||||
Reference in New Issue
Block a user