mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-13 20:10:07 +02:00
add optimistic update for favorite/ratings mutations
This commit is contained in:
@@ -7,6 +7,9 @@ import styles from './item-card-controls.module.css';
|
||||
import { ItemListStateActions } from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||
import { ItemControls } from '/@/renderer/components/item-list/types';
|
||||
import { useIsPlayerFetching } from '/@/renderer/features/player/context/player-context';
|
||||
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 { animationVariants } from '/@/shared/components/animations/animation-variants';
|
||||
import { AppIcon, Icon, IconProps } from '/@/shared/components/icon/icon';
|
||||
import { Rating } from '/@/shared/components/rating/rating';
|
||||
@@ -127,16 +130,6 @@ const createRatingChangeHandler =
|
||||
});
|
||||
};
|
||||
|
||||
const ratingClickHandler = (e: MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const ratingMouseDownHandler = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const moreDoubleClickHandler = (e: MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
@@ -225,14 +218,6 @@ export const ItemCardControls = ({
|
||||
|
||||
const isFavorite = (item as { userFavorite?: boolean })?.userFavorite ?? false;
|
||||
|
||||
const favoriteIconProps = useMemo<Partial<IconProps>>(
|
||||
() => ({
|
||||
color: isFavorite ? ('primary' as const) : ('default' as const),
|
||||
fill: isFavorite ? ('primary' as const) : undefined,
|
||||
}),
|
||||
[isFavorite],
|
||||
);
|
||||
|
||||
return (
|
||||
<motion.div className={clsx(styles.container)} {...containerProps[type]}>
|
||||
{controls?.onPlay && (
|
||||
@@ -251,20 +236,12 @@ export const ItemCardControls = ({
|
||||
</>
|
||||
)}
|
||||
{controls?.onFavorite && (
|
||||
<SecondaryButton
|
||||
className={styles.favorite}
|
||||
icon="favorite"
|
||||
iconProps={favoriteIconProps}
|
||||
onClick={favoriteHandler}
|
||||
/>
|
||||
<FavoriteButton isFavorite={isFavorite} onClick={favoriteHandler} />
|
||||
)}
|
||||
{controls?.onRating && (
|
||||
<Rating
|
||||
className={styles.rating}
|
||||
<RatingButton
|
||||
onChange={ratingChangeHandler}
|
||||
onClick={ratingClickHandler}
|
||||
onMouseDown={ratingMouseDownHandler}
|
||||
size="xs"
|
||||
rating={(item as { userRating: number }).userRating}
|
||||
/>
|
||||
)}
|
||||
{controls?.onMore && (
|
||||
@@ -286,6 +263,67 @@ export const ItemCardControls = ({
|
||||
);
|
||||
};
|
||||
|
||||
const FavoriteButton = memo(
|
||||
({
|
||||
isFavorite,
|
||||
onClick,
|
||||
}: {
|
||||
isFavorite: boolean;
|
||||
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||
}) => {
|
||||
const isMutatingCreate = useIsMutatingCreateFavorite();
|
||||
const isMutatingDelete = useIsMutatingDeleteFavorite();
|
||||
const isMutating = isMutatingCreate || isMutatingDelete;
|
||||
|
||||
const favoriteIconProps = useMemo<Partial<IconProps>>(
|
||||
() => ({
|
||||
color: isFavorite ? ('primary' as const) : ('default' as const),
|
||||
fill: isFavorite ? ('primary' as const) : undefined,
|
||||
}),
|
||||
[isFavorite],
|
||||
);
|
||||
|
||||
return (
|
||||
<SecondaryButton
|
||||
className={styles.favorite}
|
||||
disabled={isMutating}
|
||||
icon="favorite"
|
||||
iconProps={favoriteIconProps}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
},
|
||||
(prev, next) => prev.isFavorite === next.isFavorite,
|
||||
);
|
||||
|
||||
const RatingButton = memo(
|
||||
({ onChange, rating }: { onChange: (rating: number) => void; rating: number }) => {
|
||||
const ratingClickHandler = (e: MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const ratingMouseDownHandler = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
const isMutatingRating = useIsMutatingRating();
|
||||
return (
|
||||
<Rating
|
||||
className={styles.rating}
|
||||
onChange={onChange}
|
||||
onClick={ratingClickHandler}
|
||||
onMouseDown={ratingMouseDownHandler}
|
||||
readOnly={isMutatingRating}
|
||||
size="sm"
|
||||
value={rating}
|
||||
/>
|
||||
);
|
||||
},
|
||||
(prev, next) => prev.rating === next.rating,
|
||||
);
|
||||
|
||||
const PlayButton = memo(
|
||||
({
|
||||
disabled,
|
||||
@@ -360,6 +398,7 @@ const SecondaryPlayButton = memo(
|
||||
|
||||
interface SecondaryButtonProps {
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
icon: keyof typeof AppIcon;
|
||||
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||
}
|
||||
@@ -367,6 +406,7 @@ interface SecondaryButtonProps {
|
||||
const SecondaryButton = memo(
|
||||
({
|
||||
className,
|
||||
disabled,
|
||||
icon,
|
||||
iconProps,
|
||||
onClick,
|
||||
@@ -395,6 +435,7 @@ const SecondaryButton = memo(
|
||||
return (
|
||||
<button
|
||||
className={clsx(styles.secondaryButton, className)}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
onMouseDown={handleMouseDown}
|
||||
|
||||
@@ -116,7 +116,7 @@ export const useItemListInfiniteLoader = ({
|
||||
query: queryParams,
|
||||
});
|
||||
|
||||
return result.items;
|
||||
return result;
|
||||
},
|
||||
queryKey: queryKeys[getQueryKeyName(itemType)].list(serverId, queryParams),
|
||||
staleTime: 1000 * 15,
|
||||
@@ -130,7 +130,7 @@ export const useItemListInfiniteLoader = ({
|
||||
(oldData: { data: unknown[]; pagesLoaded: Record<string, boolean> }) => {
|
||||
const newData = [
|
||||
...oldData.data.slice(0, startIndex),
|
||||
...result,
|
||||
...result.items,
|
||||
...oldData.data.slice(endIndex),
|
||||
];
|
||||
const newPagesLoaded = {
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
TableColumnContainer,
|
||||
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||
import { ItemListItem } from '/@/renderer/components/item-list/types';
|
||||
import { useIsMutatingCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation';
|
||||
import { useIsMutatingDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
|
||||
export const FavoriteColumn = (props: ItemTableListInnerColumn) => {
|
||||
@@ -10,11 +12,16 @@ export const FavoriteColumn = (props: ItemTableListInnerColumn) => {
|
||||
props.columns[props.columnIndex].id
|
||||
];
|
||||
|
||||
const isMutatingCreateFavorite = useIsMutatingCreateFavorite();
|
||||
const isMutatingDeleteFavorite = useIsMutatingDeleteFavorite();
|
||||
const isMutatingFavorite = isMutatingCreateFavorite || isMutatingDeleteFavorite;
|
||||
|
||||
if (typeof row === 'boolean') {
|
||||
return (
|
||||
<TableColumnContainer {...props}>
|
||||
<ActionIcon
|
||||
className={row ? undefined : 'hover-only'}
|
||||
disabled={isMutatingFavorite}
|
||||
icon="favorite"
|
||||
iconProps={{
|
||||
color: row ? 'primary' : 'muted',
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
TableColumnContainer,
|
||||
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||
import { ItemListItem } from '/@/renderer/components/item-list/types';
|
||||
import { useIsMutatingRating } from '/@/renderer/features/shared/mutations/set-rating-mutation';
|
||||
import { Rating } from '/@/shared/components/rating/rating';
|
||||
|
||||
export const RatingColumn = (props: ItemTableListInnerColumn) => {
|
||||
@@ -10,6 +11,8 @@ export const RatingColumn = (props: ItemTableListInnerColumn) => {
|
||||
props.columns[props.columnIndex].id
|
||||
];
|
||||
|
||||
const isMutatingRating = useIsMutatingRating();
|
||||
|
||||
if (typeof row === 'number' || row === null) {
|
||||
return (
|
||||
<TableColumnContainer {...props}>
|
||||
@@ -28,6 +31,7 @@ export const RatingColumn = (props: ItemTableListInnerColumn) => {
|
||||
rating,
|
||||
});
|
||||
}}
|
||||
readOnly={isMutatingRating}
|
||||
size="xs"
|
||||
value={row || 0}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user