diff --git a/src/renderer/events/events.ts b/src/renderer/events/events.ts index 9e093ca5c..f6024f5ab 100644 --- a/src/renderer/events/events.ts +++ b/src/renderer/events/events.ts @@ -21,10 +21,12 @@ export type UserFavoriteEventPayload = { favorite: boolean; id: string[]; itemType: LibraryItem; + serverId: string; }; export type UserRatingEventPayload = { id: string[]; itemType: LibraryItem; rating: null | number; + serverId: string; }; diff --git a/src/renderer/features/player/audio-player/hooks/use-player-events.ts b/src/renderer/features/player/audio-player/hooks/use-player-events.ts index 24674cb37..5fc6380cb 100644 --- a/src/renderer/features/player/audio-player/hooks/use-player-events.ts +++ b/src/renderer/features/player/audio-player/hooks/use-player-events.ts @@ -1,5 +1,6 @@ import { useEffect } from 'react'; +import { eventEmitter } from '/@/renderer/events/event-emitter'; import { subscribeCurrentTrack, subscribePlayerMute, @@ -12,7 +13,7 @@ import { subscribePlayerStatus, subscribePlayerVolume, } from '/@/renderer/store'; -import { QueueData, QueueSong } from '/@/shared/types/domain-types'; +import { LibraryItem, QueueData, QueueSong } from '/@/shared/types/domain-types'; import { PlayerRepeat, PlayerShuffle, PlayerStatus } from '/@/shared/types/types'; interface PlayerEvents { @@ -40,6 +41,18 @@ interface PlayerEventsCallbacks { onPlayerSpeed?: (properties: { speed: number }, prev: { speed: number }) => void; onPlayerStatus?: (properties: { status: PlayerStatus }, prev: { status: PlayerStatus }) => void; onPlayerVolume?: (properties: { volume: number }, prev: { volume: number }) => void; + onUserFavorite?: (properties: { + favorite: boolean; + id: string[]; + itemType: LibraryItem; + serverId: string; + }) => void; + onUserRating?: (properties: { + id: string[]; + itemType: LibraryItem; + rating: null | number; + serverId: string; + }) => void; } export function usePlayerEvents(callbacks: PlayerEventsCallbacks, deps: React.DependencyList) { @@ -116,6 +129,14 @@ function createPlayerEvents(callbacks: PlayerEventsCallbacks): PlayerEvents { unsubscribers.push(unsubscribe); } + if (callbacks.onUserRating) { + eventEmitter.on('USER_RATING', callbacks.onUserRating); + } + + if (callbacks.onUserFavorite) { + eventEmitter.on('USER_FAVORITE', callbacks.onUserFavorite); + } + return { cleanup: () => { unsubscribers.forEach((unsubscribe) => unsubscribe()); diff --git a/src/renderer/features/player/context/player-context.tsx b/src/renderer/features/player/context/player-context.tsx index b5ca69667..c112bbd3f 100644 --- a/src/renderer/features/player/context/player-context.tsx +++ b/src/renderer/features/player/context/player-context.tsx @@ -5,6 +5,9 @@ import { useTranslation } from 'react-i18next'; import { queryKeys } from '/@/renderer/api/query-keys'; import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api'; +import { useCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation'; +import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation'; +import { useSetRating } from '/@/renderer/features/shared/mutations/set-rating-mutation'; import { songsQueries } from '/@/renderer/features/songs/api/songs-api'; import { AddToQueueType, usePlayerActions } from '/@/renderer/store'; import { toast } from '/@/shared/components/toast/toast'; @@ -46,6 +49,13 @@ interface PlayerContext { moveSelectedToBottom: (items: QueueSong[]) => void; moveSelectedToNext: (items: QueueSong[]) => void; moveSelectedToTop: (items: QueueSong[]) => void; + setFavorite: ( + serverId: string, + id: string[], + itemType: LibraryItem, + isFavorite: boolean, + ) => void; + setRating: (serverId: string, id: string[], itemType: LibraryItem, rating: number) => void; setRepeat: (repeat: PlayerRepeat) => void; setShuffle: (shuffle: PlayerShuffle) => void; setSpeed: (speed: number) => void; @@ -78,6 +88,8 @@ export const PlayerContext = createContext({ moveSelectedToBottom: () => {}, moveSelectedToNext: () => {}, moveSelectedToTop: () => {}, + setFavorite: () => {}, + setRating: () => {}, setRepeat: () => {}, setShuffle: () => {}, setSpeed: () => {}, @@ -347,6 +359,38 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => { storeActions.toggleShuffle(); }, [storeActions]); + const createFavoriteMutation = useCreateFavorite({}); + const deleteFavoriteMutation = useDeleteFavorite({}); + + const setFavorite = useCallback( + (serverId: string, id: string[], itemType: LibraryItem, isFavorite: boolean) => { + if (isFavorite) { + createFavoriteMutation.mutate({ + apiClientProps: { serverId }, + query: { id, type: itemType }, + }); + } else { + deleteFavoriteMutation.mutate({ + apiClientProps: { serverId }, + query: { id, type: itemType }, + }); + } + }, + [createFavoriteMutation, deleteFavoriteMutation], + ); + + const setRatingMutation = useSetRating({}); + + const setRating = useCallback( + (serverId: string, id: string[], itemType: LibraryItem, rating: number) => { + setRatingMutation.mutate({ + apiClientProps: { serverId }, + query: { id, rating, type: itemType }, + }); + }, + [setRatingMutation], + ); + const contextValue: PlayerContext = useMemo( () => ({ addToQueueByData, @@ -369,6 +413,8 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => { moveSelectedToBottom, moveSelectedToNext, moveSelectedToTop, + setFavorite, + setRating, setRepeat, setShuffle, setSpeed, @@ -401,6 +447,8 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => { moveSelectedToBottom, moveSelectedToNext, moveSelectedToTop, + setFavorite, + setRating, setRepeat, setShuffle, setVolume, diff --git a/src/renderer/features/remote/hooks/use-remote.tsx b/src/renderer/features/remote/hooks/use-remote.tsx index bec1c1c3b..fc0325d3f 100644 --- a/src/renderer/features/remote/hooks/use-remote.tsx +++ b/src/renderer/features/remote/hooks/use-remote.tsx @@ -7,7 +7,7 @@ import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete- import { useSetRating } from '/@/renderer/features/shared/mutations/set-rating-mutation'; import { usePlayerActions, useRemoteSettings } from '/@/renderer/store'; import { toast } from '/@/shared/components/toast/toast'; -import { LibraryItem, Song } from '/@/shared/types/domain-types'; +import { LibraryItem } from '/@/shared/types/domain-types'; import { PlayerShuffle } from '/@/shared/types/types'; const remote = isElectron() ? window.api.remote : null; @@ -60,14 +60,9 @@ export const useRemote = () => { updateRatingMutation.mutate({ apiClientProps: { serverId: data.serverId }, query: { - item: [ - { - _serverId: data.serverId, - id: data.id, - itemType: LibraryItem.SONG, - } as Song, - ], + id: [data.id], rating: data.rating, + type: LibraryItem.SONG, }, }); }, @@ -146,6 +141,20 @@ export const useRemote = () => { remote.updateVolume(properties.volume); }, + onUserFavorite: (properties) => { + if (!remote) { + return; + } + + remote.updateFavorite(properties.favorite, properties.serverId, properties.id); + }, + onUserRating: (properties) => { + if (!remote) { + return; + } + + remote.updateRating(properties.rating || 0, properties.serverId, properties.id); + }, }, [], ); diff --git a/src/renderer/features/shared/mutations/set-rating-mutation.ts b/src/renderer/features/shared/mutations/set-rating-mutation.ts index 3a05f4871..309dec7f0 100644 --- a/src/renderer/features/shared/mutations/set-rating-mutation.ts +++ b/src/renderer/features/shared/mutations/set-rating-mutation.ts @@ -1,38 +1,19 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useMutation } from '@tanstack/react-query'; import { AxiosError } from 'axios'; -import isElectron from 'is-electron'; 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 { useRatingEvent } from '/@/renderer/store/event.store'; -import { - Album, - AlbumArtist, - AlbumArtistDetailResponse, - AlbumDetailResponse, - AnyLibraryItems, - LibraryItem, - RatingResponse, - SetRatingArgs, -} from '/@/shared/types/domain-types'; - -const remote = isElectron() ? window.api.remote : null; +import { LibraryItem, RatingResponse, SetRatingArgs } from '/@/shared/types/domain-types'; export const useSetRating = (args: MutationHookArgs) => { const { options } = args || {}; - const queryClient = useQueryClient(); - const setAlbumListData = useSetAlbumListItemDataById(); - // const setQueueRating = useSetQueueRating(); - const setRatingEvent = useRatingEvent(); return useMutation< RatingResponse, AxiosError, SetRatingArgs, - { previous: undefined | { items: AnyLibraryItems } } + { previous: undefined | { id: string[]; rating: number; type: LibraryItem } } >({ mutationFn: (args) => { return api.controller.setRating({ @@ -40,92 +21,13 @@ export const useSetRating = (args: MutationHookArgs) => { apiClientProps: { serverId: args.apiClientProps.serverId }, }); }, - onError: (_error, _variables, context) => { - for (const item of context?.previous?.items || []) { - switch (item.itemType) { - case LibraryItem.ALBUM: - setAlbumListData(item.id, { userRating: item.userRating }); - break; - case LibraryItem.SONG: - // setQueueRating([item.id], item.userRating); - setRatingEvent([item.id], item.userRating); - break; - } - } - }, - onMutate: (variables) => { - eventEmitter.emit('USER_RATING', { - id: variables.query.item.map((item) => item.id), - itemType: variables.query.item[0].itemType, - rating: variables.query.rating, - }); - - const songIds: string[] = []; - for (const item of variables.query.item) { - switch (item.itemType) { - case LibraryItem.ALBUM: - setAlbumListData(item.id, { userRating: variables.query.rating }); - break; - case LibraryItem.SONG: - songIds.push(item.id); - - break; - } - } - - if (songIds.length > 0) { - // setQueueRating(songIds, variables.query.rating); - setRatingEvent(songIds, variables.query.rating); - } - - if (remote) { - remote.updateRating( - variables.query.rating, - variables.query.item[0]._serverId, - songIds, - ); - } - - return { previous: { items: variables.query.item } }; - }, onSuccess: (_data, variables) => { - // We only need to set if we're already on the album detail page - const isAlbumDetailPage = - variables.query.item.length === 1 && - variables.query.item[0].itemType === LibraryItem.ALBUM; - - if (isAlbumDetailPage) { - const { id: albumId, _serverId } = variables.query.item[0] as Album; - - const queryKey = queryKeys.albums.detail(_serverId || '', { id: albumId }); - const previous = queryClient.getQueryData(queryKey); - if (previous) { - queryClient.setQueryData(queryKey, { - ...previous, - userRating: variables.query.rating, - }); - } - } - - // We only need to set if we're already on the album artist detail page - const isAlbumArtistDetailPage = - variables.query.item.length === 1 && - variables.query.item[0].itemType === LibraryItem.ALBUM_ARTIST; - - if (isAlbumArtistDetailPage) { - const { id: albumArtistId, serverId } = variables.query.item[0] as AlbumArtist; - - const queryKey = queryKeys.albumArtists.detail(serverId || '', { - id: albumArtistId, - }); - const previous = queryClient.getQueryData(queryKey); - if (previous) { - queryClient.setQueryData(queryKey, { - ...previous, - userRating: variables.query.rating, - }); - } - } + eventEmitter.emit('USER_RATING', { + id: variables.query.id, + itemType: variables.query.type, + rating: variables.query.rating, + serverId: variables.apiClientProps.serverId, + }); }, ...options, }); diff --git a/src/shared/types/domain-types.ts b/src/shared/types/domain-types.ts index ed34b4d1a..11abc269b 100644 --- a/src/shared/types/domain-types.ts +++ b/src/shared/types/domain-types.ts @@ -887,8 +887,9 @@ export interface PlaylistListQuery extends BaseQuery { export type PlaylistListResponse = BasePaginatedResponse; export type RatingQuery = { - item: AnyLibraryItems; + id: string[]; rating: number; + type: LibraryItem; }; // Rating