Add favorite handler to grid cards

This commit is contained in:
jeffvli 2023-01-08 00:52:53 -08:00
parent 7a3bdb531d
commit d17f30f5e6
12 changed files with 114 additions and 7 deletions

View file

@ -101,6 +101,7 @@ interface BaseGridCardProps {
columnIndex: number; columnIndex: number;
controls: { controls: {
cardRows: CardRow<Album | AlbumArtist | Artist>[]; cardRows: CardRow<Album | AlbumArtist | Artist>[];
handleFavorite: (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => void;
handlePlayQueueAdd: (options: PlayQueueAddOptions) => void; handlePlayQueueAdd: (options: PlayQueueAddOptions) => void;
itemType: LibraryItem; itemType: LibraryItem;
playButtonBehavior: Play; playButtonBehavior: Play;
@ -179,6 +180,7 @@ export const DefaultCard = ({
)} )}
<ControlsContainer> <ControlsContainer>
<GridCardControls <GridCardControls
handleFavorite={controls.handleFavorite}
handlePlayQueueAdd={handlePlayQueueAdd} handlePlayQueueAdd={handlePlayQueueAdd}
itemData={data} itemData={data}
itemType={itemType} itemType={itemType}

View file

@ -118,7 +118,9 @@ export const GridCardControls = ({
itemData, itemData,
itemType, itemType,
handlePlayQueueAdd, handlePlayQueueAdd,
handleFavorite,
}: { }: {
handleFavorite: (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => void;
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void; handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
itemData: any; itemData: any;
itemType: LibraryItem; itemType: LibraryItem;
@ -138,6 +140,17 @@ export const GridCardControls = ({
}); });
}; };
const handleFavorites = async (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
e.stopPropagation();
handleFavorite?.({
id: [itemData.id],
isFavorite: itemData.userFavorite,
itemType,
});
};
return ( return (
<GridCardControlsContainer> <GridCardControlsContainer>
{/* <TopControls /> */} {/* <TopControls /> */}
@ -148,13 +161,13 @@ export const GridCardControls = ({
</PlayButton> </PlayButton>
<Group spacing="xs"> <Group spacing="xs">
<SecondaryButton <SecondaryButton
disabled
p={5} p={5}
sx={{ svg: { fill: 'white !important' } }} sx={{ svg: { fill: 'white !important' } }}
variant="subtle" variant="subtle"
onClick={handleFavorites}
> >
<FavoriteWrapper isFavorite={itemData?.isFavorite}> <FavoriteWrapper isFavorite={itemData?.isFavorite}>
{itemData?.isFavorite ? ( {itemData?.userFavorite ? (
<RiHeartFill size={20} /> <RiHeartFill size={20} />
) : ( ) : (
<RiHeartLine <RiHeartLine

View file

@ -18,6 +18,7 @@ export const GridCard = memo(({ data, index, style }: ListChildComponentProps) =
itemType, itemType,
playButtonBehavior, playButtonBehavior,
handlePlayQueueAdd, handlePlayQueueAdd,
handleFavorite,
route, route,
display, display,
} = data as GridCardData; } = data as GridCardData;
@ -35,6 +36,7 @@ export const GridCard = memo(({ data, index, style }: ListChildComponentProps) =
columnIndex={i} columnIndex={i}
controls={{ controls={{
cardRows, cardRows,
handleFavorite,
handlePlayQueueAdd, handlePlayQueueAdd,
itemType, itemType,
playButtonBehavior, playButtonBehavior,

View file

@ -105,6 +105,7 @@ interface BaseGridCardProps {
columnIndex: number; columnIndex: number;
controls: { controls: {
cardRows: CardRow<Album | AlbumArtist | Artist>[]; cardRows: CardRow<Album | AlbumArtist | Artist>[];
handleFavorite: (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => void;
handlePlayQueueAdd: (options: PlayQueueAddOptions) => void; handlePlayQueueAdd: (options: PlayQueueAddOptions) => void;
itemType: LibraryItem; itemType: LibraryItem;
playButtonBehavior: Play; playButtonBehavior: Play;
@ -173,6 +174,7 @@ export const PosterCard = ({
)} )}
<ControlsContainer> <ControlsContainer>
<GridCardControls <GridCardControls
handleFavorite={controls.handleFavorite}
handlePlayQueueAdd={controls.handlePlayQueueAdd} handlePlayQueueAdd={controls.handlePlayQueueAdd}
itemData={data} itemData={data}
itemType={controls.itemType} itemType={controls.itemType}

View file

@ -21,10 +21,12 @@ const createItemData = memoize(
itemWidth, itemWidth,
route, route,
handlePlayQueueAdd, handlePlayQueueAdd,
handleFavorite,
) => ({ ) => ({
cardRows, cardRows,
columnCount, columnCount,
display, display,
handleFavorite,
handlePlayQueueAdd, handlePlayQueueAdd,
itemCount, itemCount,
itemData, itemData,
@ -50,6 +52,7 @@ export const VirtualGridWrapper = ({
columnCount, columnCount,
rowCount, rowCount,
initialScrollOffset, initialScrollOffset,
handleFavorite,
handlePlayQueueAdd, handlePlayQueueAdd,
itemData, itemData,
route, route,
@ -59,6 +62,7 @@ export const VirtualGridWrapper = ({
cardRows: CardRow<Album | AlbumArtist | Artist>[]; cardRows: CardRow<Album | AlbumArtist | Artist>[];
columnCount: number; columnCount: number;
display: ListDisplayType; display: ListDisplayType;
handleFavorite?: (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => void;
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void; handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
itemData: any[]; itemData: any[];
itemGap: number; itemGap: number;
@ -81,6 +85,7 @@ export const VirtualGridWrapper = ({
itemWidth, itemWidth,
route, route,
handlePlayQueueAdd, handlePlayQueueAdd,
handleFavorite,
); );
const memoizedOnScroll = createScrollHandler(onScroll); const memoizedOnScroll = createScrollHandler(onScroll);

View file

@ -17,6 +17,7 @@ interface VirtualGridProps extends Omit<FixedSizeListProps, 'children' | 'itemSi
cardRows: CardRow<any>[]; cardRows: CardRow<any>[];
display?: ListDisplayType; display?: ListDisplayType;
fetchFn: (options: { columnCount: number; skip: number; take: number }) => Promise<any>; fetchFn: (options: { columnCount: number; skip: number; take: number }) => Promise<any>;
handleFavorite?: (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => void;
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void; handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
itemData: any[]; itemData: any[];
itemGap: number; itemGap: number;
@ -54,6 +55,7 @@ export const VirtualInfiniteGrid = forwardRef(
fetchFn, fetchFn,
loading, loading,
initialScrollOffset, initialScrollOffset,
handleFavorite,
height, height,
width, width,
}: VirtualGridProps, }: VirtualGridProps,
@ -146,6 +148,7 @@ export const VirtualInfiniteGrid = forwardRef(
cardRows={cardRows} cardRows={cardRows}
columnCount={columnCount} columnCount={columnCount}
display={display || ListDisplayType.CARD} display={display || ListDisplayType.CARD}
handleFavorite={handleFavorite}
handlePlayQueueAdd={handlePlayQueueAdd} handlePlayQueueAdd={handlePlayQueueAdd}
height={height} height={height}
initialScrollOffset={initialScrollOffset} initialScrollOffset={initialScrollOffset}

View file

@ -6,21 +6,43 @@ import { useMutation } from '@tanstack/react-query';
import { HTTPError } from 'ky'; import { HTTPError } from 'ky';
import { api } from '/@/renderer/api'; import { api } from '/@/renderer/api';
import { RawFavoriteResponse, FavoriteArgs, LibraryItem } from '/@/renderer/api/types'; import { RawFavoriteResponse, FavoriteArgs, LibraryItem } from '/@/renderer/api/types';
import { useCurrentServer, useSetQueueFavorite } from '/@/renderer/store'; import {
useCurrentServer,
useSetAlbumListItemDataById,
useSetQueueFavorite,
} from '/@/renderer/store';
const useCreateFavorite = () => { const useCreateFavorite = () => {
const server = useCurrentServer(); const server = useCurrentServer();
const setAlbumListData = useSetAlbumListItemDataById();
return useMutation<RawFavoriteResponse, HTTPError, Omit<FavoriteArgs, 'server'>, null>({ return useMutation<RawFavoriteResponse, HTTPError, Omit<FavoriteArgs, 'server'>, null>({
mutationFn: (args) => api.controller.createFavorite({ ...args, server }), mutationFn: (args) => api.controller.createFavorite({ ...args, server }),
onSuccess: (_data, variables) => {
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 });
}
}
},
}); });
}; };
const useDeleteFavorite = () => { const useDeleteFavorite = () => {
const server = useCurrentServer(); const server = useCurrentServer();
const setAlbumListData = useSetAlbumListItemDataById();
return useMutation<RawFavoriteResponse, HTTPError, Omit<FavoriteArgs, 'server'>, null>({ return useMutation<RawFavoriteResponse, HTTPError, Omit<FavoriteArgs, 'server'>, null>({
mutationFn: (args) => api.controller.deleteFavorite({ ...args, server }), mutationFn: (args) => api.controller.deleteFavorite({ ...args, server }),
onSuccess: (_data, variables) => {
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 });
}
}
},
}); });
}; };

View file

@ -41,6 +41,7 @@ import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
import { ALBUM_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; import { ALBUM_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
import { generatePath, useNavigate } from 'react-router'; import { generatePath, useNavigate } from 'react-router';
import { usePlayQueueAdd } from '/@/renderer/features/player'; import { usePlayQueueAdd } from '/@/renderer/features/player';
import { useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared';
interface AlbumListContentProps { interface AlbumListContentProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>; gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
@ -265,6 +266,32 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: e.data.id })); navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: e.data.id }));
}; };
const createFavoriteMutation = useCreateFavorite();
const deleteFavoriteMutation = useDeleteFavorite();
const handleFavorite = (options: {
id: string[];
isFavorite: boolean;
itemType: LibraryItem;
}) => {
const { id, itemType, isFavorite } = options;
if (isFavorite) {
deleteFavoriteMutation.mutate({
query: {
id,
type: itemType,
},
});
} else {
createFavoriteMutation.mutate({
query: {
id,
type: itemType,
},
});
}
};
return ( return (
<> <>
<VirtualGridAutoSizerContainer> <VirtualGridAutoSizerContainer>
@ -278,6 +305,7 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont
cardRows={cardRows} cardRows={cardRows}
display={page.display || ListDisplayType.CARD} display={page.display || ListDisplayType.CARD}
fetchFn={fetch} fetchFn={fetch}
handleFavorite={handleFavorite}
handlePlayQueueAdd={handlePlayQueueAdd} handlePlayQueueAdd={handlePlayQueueAdd}
height={height} height={height}
initialScrollOffset={page?.grid.scrollOffset || 0} initialScrollOffset={page?.grid.scrollOffset || 0}

View file

@ -1,15 +1,24 @@
import { useMutation } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import { HTTPError } from 'ky'; import { HTTPError } from 'ky';
import { api } from '/@/renderer/api'; import { api } from '/@/renderer/api';
import { FavoriteArgs, RawFavoriteResponse } from '/@/renderer/api/types'; import { FavoriteArgs, LibraryItem, RawFavoriteResponse } from '/@/renderer/api/types';
import { MutationOptions } from '/@/renderer/lib/react-query'; import { MutationOptions } from '/@/renderer/lib/react-query';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer, useSetAlbumListItemDataById } from '/@/renderer/store';
export const useCreateFavorite = (options?: MutationOptions) => { export const useCreateFavorite = (options?: MutationOptions) => {
const server = useCurrentServer(); const server = useCurrentServer();
const setAlbumListData = useSetAlbumListItemDataById();
return useMutation<RawFavoriteResponse, HTTPError, Omit<FavoriteArgs, 'server'>, null>({ return useMutation<RawFavoriteResponse, HTTPError, Omit<FavoriteArgs, 'server'>, null>({
mutationFn: (args) => api.controller.createFavorite({ ...args, server }), mutationFn: (args) => api.controller.createFavorite({ ...args, server }),
onSuccess: (_data, variables) => {
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 });
}
}
},
...options, ...options,
}); });
}; };

View file

@ -1,15 +1,24 @@
import { useMutation } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import { HTTPError } from 'ky'; import { HTTPError } from 'ky';
import { api } from '/@/renderer/api'; import { api } from '/@/renderer/api';
import { FavoriteArgs, RawFavoriteResponse } from '/@/renderer/api/types'; import { FavoriteArgs, LibraryItem, RawFavoriteResponse } from '/@/renderer/api/types';
import { MutationOptions } from '/@/renderer/lib/react-query'; import { MutationOptions } from '/@/renderer/lib/react-query';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer, useSetAlbumListItemDataById } from '/@/renderer/store';
export const useDeleteFavorite = (options?: MutationOptions) => { export const useDeleteFavorite = (options?: MutationOptions) => {
const server = useCurrentServer(); const server = useCurrentServer();
const setAlbumListData = useSetAlbumListItemDataById();
return useMutation<RawFavoriteResponse, HTTPError, Omit<FavoriteArgs, 'server'>, null>({ return useMutation<RawFavoriteResponse, HTTPError, Omit<FavoriteArgs, 'server'>, null>({
mutationFn: (args) => api.controller.deleteFavorite({ ...args, server }), mutationFn: (args) => api.controller.deleteFavorite({ ...args, server }),
onSuccess: (_data, variables) => {
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 });
}
}
},
...options, ...options,
}); });
}; };

View file

@ -9,6 +9,7 @@ export interface AlbumListDataState {
export interface AlbumListDataSlice extends AlbumListDataState { export interface AlbumListDataSlice extends AlbumListDataState {
actions: { actions: {
setItemData: (data: any[]) => void; setItemData: (data: any[]) => void;
setItemDataById: (id: string, data: any) => void;
}; };
} }
@ -21,6 +22,13 @@ export const useAlbumListDataStore = create<AlbumListDataSlice>()(
state.itemData = data; state.itemData = data;
}); });
}, },
setItemDataById: (id, data) => {
set((state) => {
const index = state.itemData.findIndex((item) => item?.id === id);
if (index === -1) return;
state.itemData[index] = { ...state.itemData[index], ...data };
});
},
}, },
itemData: [], itemData: [],
})), })),
@ -34,3 +42,6 @@ export const useAlbumListItemData = () =>
useAlbumListDataStore((state) => { useAlbumListDataStore((state) => {
return { itemData: state.itemData, setItemData: state.actions.setItemData }; return { itemData: state.itemData, setItemData: state.actions.setItemData };
}); });
export const useSetAlbumListItemDataById = () =>
useAlbumListDataStore((state) => state.actions.setItemDataById);

View file

@ -162,6 +162,7 @@ export type GridCardData = {
cardRows: CardRow<Album | AlbumArtist | Artist>[]; cardRows: CardRow<Album | AlbumArtist | Artist>[];
columnCount: number; columnCount: number;
display: ListDisplayType; display: ListDisplayType;
handleFavorite: (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => void;
handlePlayQueueAdd: (options: PlayQueueAddOptions) => void; handlePlayQueueAdd: (options: PlayQueueAddOptions) => void;
itemCount: number; itemCount: number;
itemData: any[]; itemData: any[];