Add favorite handler to grid cards
This commit is contained in:
parent
7a3bdb531d
commit
d17f30f5e6
12 changed files with 114 additions and 7 deletions
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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[];
|
||||||
|
|
Reference in a new issue