fix favorite/rating handlers on detail page

This commit is contained in:
jeffvli
2025-11-21 20:25:35 -08:00
parent b6e4302087
commit 676f963e19
5 changed files with 186 additions and 105 deletions
@@ -10,6 +10,9 @@ import {
WidePlayButton,
WideShuffleButton,
} from '/@/renderer/features/shared/components/play-button';
import { useIsMutatingCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation';
import { useIsMutatingDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation';
import { useIsMutatingRating } from '/@/renderer/features/shared/mutations/set-rating-mutation';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Center } from '/@/shared/components/center/center';
import { Group } from '/@/shared/components/group/group';
@@ -149,6 +152,10 @@ export const LibraryHeaderMenu = ({
onShuffle,
rating,
}: LibraryHeaderMenuProps) => {
const isMutatingRating = useIsMutatingRating();
const isMutatingCreateFavorite = useIsMutatingCreateFavorite();
const isMutatingDeleteFavorite = useIsMutatingDeleteFavorite();
return (
<div className={styles.libraryHeaderMenu}>
<Group wrap="nowrap">
@@ -156,12 +163,20 @@ export const LibraryHeaderMenu = ({
{onShuffle && <WideShuffleButton onClick={onShuffle} />}
</Group>
<Group gap="sm" wrap="nowrap">
{onRating && <Rating onChange={onRating} size="lg" value={rating || 0} />}
{onRating && (
<Rating
onChange={onRating}
readOnly={isMutatingRating}
size="lg"
value={rating || 0}
/>
)}
{onFavorite && (
<ActionIcon
disabled={isMutatingCreateFavorite || isMutatingDeleteFavorite}
icon="favorite"
iconProps={{
fill: favorite ? 'primary' : undefined,
fill: favorite ? 'favorite' : undefined,
}}
onClick={onFavorite}
size="lg"
@@ -1,4 +1,4 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useIsMutating, useMutation, useQueryClient } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import isElectron from 'is-electron';
@@ -6,23 +6,17 @@ import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { eventEmitter } from '/@/renderer/events/event-emitter';
import { MutationHookArgs } from '/@/renderer/lib/react-query';
import { useSetAlbumListItemDataById } from '/@/renderer/store';
import { useFavoriteEvent } from '/@/renderer/store/event.store';
import {
AlbumArtistDetailResponse,
AlbumDetailResponse,
FavoriteArgs,
FavoriteResponse,
LibraryItem,
} from '/@/shared/types/domain-types';
import { FavoriteArgs, FavoriteResponse, LibraryItem } from '/@/shared/types/domain-types';
const remote = isElectron() ? window.api.remote : null;
const createFavoriteQueryKey = ['set-favorite', true];
export const useCreateFavorite = (args: MutationHookArgs) => {
const { options } = args || {};
const queryClient = useQueryClient();
const setAlbumListData = useSetAlbumListItemDataById();
// const setQueueFavorite = useSetQueueFavorite();
const setFavoriteEvent = useFavoriteEvent();
return useMutation<FavoriteResponse, AxiosError, FavoriteArgs, null>({
@@ -32,6 +26,7 @@ export const useCreateFavorite = (args: MutationHookArgs) => {
apiClientProps: { serverId: args.apiClientProps.serverId },
});
},
mutationKey: createFavoriteQueryKey,
onError: (_error, variables) => {
eventEmitter.emit('USER_FAVORITE', {
favorite: false,
@@ -51,56 +46,61 @@ export const useCreateFavorite = (args: MutationHookArgs) => {
return null;
},
onSuccess: (_data, variables) => {
const { apiClientProps } = variables;
const serverId = apiClientProps.serverId;
if (!serverId) return;
for (const id of variables.query.id) {
// Set the userFavorite property to true for the album in the album list data store
if (variables.query.type === LibraryItem.ALBUM) {
setAlbumListData(id, { userFavorite: true });
}
}
if (variables.query.type === LibraryItem.SONG) {
remote?.updateFavorite(true, serverId, variables.query.id);
remote?.updateFavorite(true, variables.apiClientProps.serverId, variables.query.id);
// setQueueFavorite(variables.query.id, true);
setFavoriteEvent(variables.query.id, true);
}
// We only need to set if we're already on the album detail page
if (variables.query.type === LibraryItem.ALBUM && variables.query.id.length === 1) {
const queryKey = queryKeys.albums.detail(serverId, { id: variables.query.id[0] });
const previous = queryClient.getQueryData<AlbumDetailResponse>(queryKey);
if (previous) {
queryClient.setQueryData<AlbumDetailResponse>(queryKey, {
...previous,
userFavorite: true,
switch (variables.query.type) {
case LibraryItem.ALBUM: {
const queryKey = queryKeys.albums.detail(variables.apiClientProps.serverId);
queryClient.invalidateQueries({
exact: false,
queryKey,
});
break;
}
}
case LibraryItem.ALBUM_ARTIST: {
const queryKey = queryKeys.albumArtists.detail(
variables.apiClientProps.serverId,
);
// We only need to set if we're already on the album detail page
if (
variables.query.type === LibraryItem.ALBUM_ARTIST &&
variables.query.id.length === 1
) {
const queryKey = queryKeys.albumArtists.detail(serverId, {
id: variables.query.id[0],
});
const previous = queryClient.getQueryData<AlbumArtistDetailResponse>(queryKey);
if (previous) {
queryClient.setQueryData<AlbumArtistDetailResponse>(queryKey, {
...previous,
userFavorite: true,
queryClient.invalidateQueries({
exact: false,
queryKey,
});
break;
}
case LibraryItem.ARTIST: {
const queryKey = queryKeys.artists.detail(variables.apiClientProps.serverId);
queryClient.invalidateQueries({
exact: false,
queryKey,
});
break;
}
case LibraryItem.SONG: {
const queryKey = queryKeys.songs.detail(variables.apiClientProps.serverId);
queryClient.invalidateQueries({
exact: false,
queryKey,
});
break;
}
}
},
...options,
});
};
export const useIsMutatingCreateFavorite = () => {
const mutatingCount = useIsMutating({ mutationKey: createFavoriteQueryKey });
return mutatingCount > 0;
};
@@ -1,4 +1,4 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useIsMutating, useMutation, useQueryClient } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import isElectron from 'is-electron';
@@ -6,25 +6,17 @@ import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { eventEmitter } from '/@/renderer/events/event-emitter';
import { MutationHookArgs } from '/@/renderer/lib/react-query';
import { useSetAlbumListItemDataById } from '/@/renderer/store';
import { useFavoriteEvent } from '/@/renderer/store/event.store';
import {
AlbumArtistDetailResponse,
AlbumDetailResponse,
FavoriteArgs,
FavoriteResponse,
LibraryItem,
} from '/@/shared/types/domain-types';
import { FavoriteArgs, FavoriteResponse, LibraryItem } from '/@/shared/types/domain-types';
const remote = isElectron() ? window.api.remote : null;
const deleteFavoriteQueryKey = ['set-favorite', false];
export const useDeleteFavorite = (args: MutationHookArgs) => {
const { options } = args || {};
const queryClient = useQueryClient();
const setAlbumListData = useSetAlbumListItemDataById();
// const setQueueFavorite = useSetQueueFavorite();
const setFavoriteEvent = useFavoriteEvent();
return useMutation<FavoriteResponse, AxiosError, FavoriteArgs, null>({
mutationFn: (args) => {
return api.controller.deleteFavorite({
@@ -32,6 +24,7 @@ export const useDeleteFavorite = (args: MutationHookArgs) => {
apiClientProps: { serverId: args.apiClientProps.serverId },
});
},
mutationKey: deleteFavoriteQueryKey,
onError: (_error, variables) => {
eventEmitter.emit('USER_FAVORITE', {
favorite: true,
@@ -51,55 +44,64 @@ export const useDeleteFavorite = (args: MutationHookArgs) => {
return null;
},
onSuccess: (_data, variables) => {
const { apiClientProps } = variables;
const serverId = apiClientProps.serverId;
for (const id of variables.query.id) {
// Set the userFavorite property to false for the album in the album list data store
if (variables.query.type === LibraryItem.ALBUM) {
setAlbumListData(id, { userFavorite: false });
}
}
if (variables.query.type === LibraryItem.SONG) {
remote?.updateFavorite(false, serverId, variables.query.id);
// setQueueFavorite(variables.query.id, false);
remote?.updateFavorite(
false,
variables.apiClientProps.serverId,
variables.query.id,
);
setFavoriteEvent(variables.query.id, false);
}
// We only need to set if we're already on the album detail page
if (variables.query.type === LibraryItem.ALBUM && variables.query.id.length === 1) {
const queryKey = queryKeys.albums.detail(serverId, {
id: variables.query.id[0],
});
const previous = queryClient.getQueryData<AlbumDetailResponse>(queryKey);
if (previous) {
queryClient.setQueryData<AlbumDetailResponse>(queryKey, {
...previous,
userFavorite: false,
switch (variables.query.type) {
case LibraryItem.ALBUM: {
const queryKey = queryKeys.albums.detail(variables.apiClientProps.serverId);
queryClient.invalidateQueries({
exact: false,
queryKey,
});
break;
}
}
case LibraryItem.ALBUM_ARTIST: {
const queryKey = queryKeys.albumArtists.detail(
variables.apiClientProps.serverId,
);
// We only need to set if we're already on the album artist detail page
if (
variables.query.type === LibraryItem.ALBUM_ARTIST &&
variables.query.id.length === 1
) {
const queryKey = queryKeys.albumArtists.detail(serverId, {
id: variables.query.id[0],
});
const previous = queryClient.getQueryData<AlbumArtistDetailResponse>(queryKey);
if (previous) {
queryClient.setQueryData<AlbumArtistDetailResponse>(queryKey, {
...previous,
userFavorite: false,
queryClient.invalidateQueries({
exact: false,
queryKey,
});
break;
}
case LibraryItem.ARTIST: {
const queryKey = queryKeys.artists.detail(variables.apiClientProps.serverId);
queryClient.invalidateQueries({
exact: false,
queryKey,
});
break;
}
case LibraryItem.SONG: {
const queryKey = queryKeys.songs.detail(variables.apiClientProps.serverId);
queryClient.invalidateQueries({
exact: false,
queryKey,
});
break;
}
}
},
...options,
});
};
export const useIsMutatingDeleteFavorite = () => {
const mutatingCount = useIsMutating({ mutationKey: deleteFavoriteQueryKey });
return mutatingCount > 0;
};
@@ -1,13 +1,17 @@
import { useMutation } from '@tanstack/react-query';
import { useIsMutating, useMutation, useQueryClient } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { eventEmitter } from '/@/renderer/events/event-emitter';
import { MutationHookArgs } from '/@/renderer/lib/react-query';
import { LibraryItem, RatingResponse, SetRatingArgs } from '/@/shared/types/domain-types';
const setRatingQueryKey = ['set-rating'];
export const useSetRating = (args: MutationHookArgs) => {
const { options } = args || {};
const queryClient = useQueryClient();
return useMutation<
RatingResponse,
@@ -21,7 +25,8 @@ export const useSetRating = (args: MutationHookArgs) => {
apiClientProps: { serverId: args.apiClientProps.serverId },
});
},
onSuccess: (_data, variables) => {
mutationKey: setRatingQueryKey,
onError: (_error, variables) => {
eventEmitter.emit('USER_RATING', {
id: variables.query.id,
itemType: variables.query.type,
@@ -29,6 +34,66 @@ export const useSetRating = (args: MutationHookArgs) => {
serverId: variables.apiClientProps.serverId,
});
},
onMutate: (variables) => {
eventEmitter.emit('USER_RATING', {
id: variables.query.id,
itemType: variables.query.type,
rating: variables.query.rating,
serverId: variables.apiClientProps.serverId,
});
return { previous: undefined };
},
onSuccess: (_data, variables) => {
switch (variables.query.type) {
case LibraryItem.ALBUM: {
const queryKey = queryKeys.albums.detail(variables.apiClientProps.serverId);
queryClient.invalidateQueries({
exact: false,
queryKey,
});
break;
}
case LibraryItem.ALBUM_ARTIST: {
const queryKey = queryKeys.albumArtists.detail(
variables.apiClientProps.serverId,
);
queryClient.invalidateQueries({
exact: false,
queryKey,
});
break;
}
case LibraryItem.ARTIST: {
const queryKey = queryKeys.artists.detail(variables.apiClientProps.serverId);
queryClient.invalidateQueries({
exact: false,
queryKey,
});
break;
}
case LibraryItem.SONG: {
const queryKey = queryKeys.songs.detail(variables.apiClientProps.serverId);
queryClient.invalidateQueries({
exact: false,
queryKey,
});
break;
}
}
},
...options,
});
};
export const useIsMutatingRating = () => {
const mutatingCount = useIsMutating({ mutationKey: setRatingQueryKey });
return mutatingCount > 0;
};
+1 -2
View File
@@ -20,14 +20,13 @@ const queryCache = new QueryCache({
const queryConfig: DefaultOptions = {
mutations: {
gcTime: 1000 * 20, // 20 seconds
retry: process.env.NODE_ENV === 'production',
},
queries: {
gcTime: 1000 * 5, // 5 seconds
refetchOnWindowFocus: false,
retry: process.env.NODE_ENV === 'production',
staleTime: 1000 * 5, // 5 seconds
staleTime: 0, // 5 seconds
throwOnError: (error: any) => {
return error?.response?.status >= 500;
},