diff --git a/src/renderer/components/virtual-table/cells/favorite-cell.tsx b/src/renderer/components/virtual-table/cells/favorite-cell.tsx index e834a68c..d1cf25e9 100644 --- a/src/renderer/components/virtual-table/cells/favorite-cell.tsx +++ b/src/renderer/components/virtual-table/cells/favorite-cell.tsx @@ -2,15 +2,90 @@ import type { ICellRendererParams } from '@ag-grid-community/core'; import { RiHeartFill, RiHeartLine } from 'react-icons/ri'; import { Button } from '/@/renderer/components/button'; import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell'; +import { useMutation } from '@tanstack/react-query'; +import { HTTPError } from 'ky'; +import { api } from '/@/renderer/api'; +import { RawFavoriteResponse, FavoriteArgs, LibraryItem } from '/@/renderer/api/types'; +import { useCurrentServer, useSetQueueFavorite } from '/@/renderer/store'; + +const useCreateFavorite = () => { + const server = useCurrentServer(); + + return useMutation, null>({ + mutationFn: (args) => api.controller.createFavorite({ ...args, server }), + }); +}; + +const useDeleteFavorite = () => { + const server = useCurrentServer(); + + return useMutation, null>({ + mutationFn: (args) => api.controller.deleteFavorite({ ...args, server }), + }); +}; + +export const FavoriteCell = ({ value, data, node }: ICellRendererParams) => { + const createMutation = useCreateFavorite(); + const deleteMutation = useDeleteFavorite(); + + // Since the queue is using client-side state, we need to update it manually + const setFavorite = useSetQueueFavorite(); + + const handleToggleFavorite = () => { + const newFavoriteValue = !value; + + if (newFavoriteValue) { + createMutation.mutate( + { + query: { + id: [data.id], + type: data.itemType, + }, + }, + { + onSuccess: () => { + if (data.itemType === LibraryItem.SONG) { + setFavorite([data.id], newFavoriteValue); + } + + node.setData({ ...data, userFavorite: newFavoriteValue }); + }, + }, + ); + } else { + deleteMutation.mutate( + { + query: { + id: [data.id], + type: data.itemType, + }, + }, + { + onSuccess: () => { + if (data.itemType === LibraryItem.SONG) { + setFavorite([data.id], newFavoriteValue); + } + + node.setData({ ...data, userFavorite: newFavoriteValue }); + }, + }, + ); + } + }; -export const FavoriteCell = ({ value }: ICellRendererParams) => { return ( ); diff --git a/src/renderer/components/virtual-table/table-config-dropdown.tsx b/src/renderer/components/virtual-table/table-config-dropdown.tsx index 31cdff36..d2a72d36 100644 --- a/src/renderer/components/virtual-table/table-config-dropdown.tsx +++ b/src/renderer/components/virtual-table/table-config-dropdown.tsx @@ -28,9 +28,9 @@ export const SONG_TABLE_COLUMNS = [ { label: 'Date Added', value: TableColumn.DATE_ADDED }, { label: 'Path', value: TableColumn.PATH }, { label: 'Plays', value: TableColumn.PLAY_COUNT }, + { label: 'Size', value: TableColumn.SIZE }, { label: 'Favorite', value: TableColumn.USER_FAVORITE }, { label: 'Rating', value: TableColumn.USER_RATING }, - { label: 'Size', value: TableColumn.SIZE }, // { label: 'Skip', value: TableColumn.SKIP }, ]; @@ -47,6 +47,8 @@ export const ALBUM_TABLE_COLUMNS = [ { label: 'Last Played', value: TableColumn.LAST_PLAYED }, { label: 'Date Added', value: TableColumn.DATE_ADDED }, { label: 'Plays', value: TableColumn.PLAY_COUNT }, + { label: 'Favorite', value: TableColumn.USER_FAVORITE }, + { label: 'Rating', value: TableColumn.USER_RATING }, ]; export const ALBUMARTIST_TABLE_COLUMNS = [ @@ -60,6 +62,8 @@ export const ALBUMARTIST_TABLE_COLUMNS = [ { label: 'Plays', value: TableColumn.PLAY_COUNT }, { label: 'Album Count', value: TableColumn.ALBUM_COUNT }, { label: 'Song Count', value: TableColumn.SONG_COUNT }, + { label: 'Favorite', value: TableColumn.USER_FAVORITE }, + { label: 'Rating', value: TableColumn.USER_RATING }, ]; export const PLAYLIST_TABLE_COLUMNS = [ diff --git a/src/renderer/store/player.store.ts b/src/renderer/store/player.store.ts index 7316b41c..e0ffdd7b 100644 --- a/src/renderer/store/player.store.ts +++ b/src/renderer/store/player.store.ts @@ -75,7 +75,9 @@ export interface PlayerSlice extends PlayerState { setCurrentIndex: (index: number) => PlayerData; setCurrentTime: (time: number) => void; setCurrentTrack: (uniqueId: string) => PlayerData; + setFavorite: (ids: string[], favorite: boolean) => string[]; setMuted: (muted: boolean) => void; + setRating: (ids: string[], rating: number | null) => string[]; setRepeat: (type: PlayerRepeat) => PlayerData; setShuffle: (type: PlayerShuffle) => PlayerData; setShuffledIndex: (index: number) => PlayerData; @@ -643,11 +645,43 @@ export const usePlayerStore = create()( return get().actions.getPlayerData(); }, + setFavorite: (ids, favorite) => { + const { default: queue } = get().queue; + const foundUniqueIds = []; + + for (const id of ids) { + const foundIndex = queue.findIndex((song) => song.id === id); + if (foundIndex !== -1) { + foundUniqueIds.push(queue[foundIndex].uniqueId); + set((state) => { + state.queue.default[foundIndex].userFavorite = favorite; + }); + } + } + + return foundUniqueIds; + }, setMuted: (muted: boolean) => { set((state) => { state.muted = muted; }); }, + setRating: (ids, rating) => { + const { default: queue } = get().queue; + const foundUniqueIds = []; + + for (const id of ids) { + const foundIndex = queue.findIndex((song) => song.id === id); + if (foundIndex !== -1) { + foundUniqueIds.push(queue[foundIndex].uniqueId); + set((state) => { + state.queue.default[foundIndex].userRating = rating; + }); + } + } + + return foundUniqueIds; + }, setRepeat: (type: PlayerRepeat) => { set((state) => { state.repeat = type; @@ -825,3 +859,7 @@ export const useCurrentTime = () => usePlayerStore((state) => state.current.time export const useVolume = () => usePlayerStore((state) => state.volume); export const useMuted = () => usePlayerStore((state) => state.muted); + +export const useSetQueueFavorite = () => usePlayerStore((state) => state.actions.setFavorite); + +export const useSetQueueRating = () => usePlayerStore((state) => state.actions.setRating);