mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
fix unneccesary navigation on grid card control click
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { motion } from 'motion/react';
|
import { motion } from 'motion/react';
|
||||||
import { MouseEvent } from 'react';
|
import { memo, MouseEvent, useMemo } from 'react';
|
||||||
|
|
||||||
import styles from './item-card-controls.module.css';
|
import styles from './item-card-controls.module.css';
|
||||||
|
|
||||||
@@ -50,6 +50,134 @@ const containerProps = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createPlayHandler =
|
||||||
|
(
|
||||||
|
controls: ItemControls | undefined,
|
||||||
|
item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
|
||||||
|
internalState: ItemListStateActions | undefined,
|
||||||
|
itemType: LibraryItem,
|
||||||
|
playType: Play,
|
||||||
|
) =>
|
||||||
|
(e: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
controls?.onPlay?.({
|
||||||
|
event: e,
|
||||||
|
internalState,
|
||||||
|
item,
|
||||||
|
itemType,
|
||||||
|
playType,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createFavoriteHandler =
|
||||||
|
(
|
||||||
|
controls: ItemControls | undefined,
|
||||||
|
item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
|
||||||
|
internalState: ItemListStateActions | undefined,
|
||||||
|
itemType: LibraryItem,
|
||||||
|
) =>
|
||||||
|
(e: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newFavorite = !(item as { userFavorite: boolean }).userFavorite;
|
||||||
|
controls?.onFavorite?.({
|
||||||
|
event: e,
|
||||||
|
favorite: newFavorite,
|
||||||
|
internalState,
|
||||||
|
item,
|
||||||
|
itemType,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createRatingChangeHandler =
|
||||||
|
(
|
||||||
|
controls: ItemControls | undefined,
|
||||||
|
item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
|
||||||
|
internalState: ItemListStateActions | undefined,
|
||||||
|
itemType: LibraryItem,
|
||||||
|
) =>
|
||||||
|
(rating: number) => {
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newRating = rating;
|
||||||
|
|
||||||
|
if (rating === (item as { userRating: number }).userRating) {
|
||||||
|
newRating = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
controls?.onRating?.({
|
||||||
|
event: null,
|
||||||
|
internalState,
|
||||||
|
item,
|
||||||
|
itemType,
|
||||||
|
rating: newRating,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
const createMoreHandler =
|
||||||
|
(
|
||||||
|
controls: ItemControls | undefined,
|
||||||
|
item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
|
||||||
|
internalState: ItemListStateActions | undefined,
|
||||||
|
itemType: LibraryItem,
|
||||||
|
) =>
|
||||||
|
(e: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
controls?.onMore?.({
|
||||||
|
event: e,
|
||||||
|
internalState,
|
||||||
|
item,
|
||||||
|
itemType,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createExpandHandler =
|
||||||
|
(
|
||||||
|
controls: ItemControls | undefined,
|
||||||
|
item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
|
||||||
|
internalState: ItemListStateActions | undefined,
|
||||||
|
itemType: LibraryItem,
|
||||||
|
) =>
|
||||||
|
(e: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
controls?.onExpand?.({
|
||||||
|
event: e,
|
||||||
|
internalState,
|
||||||
|
item,
|
||||||
|
itemType,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const ItemCardControls = ({
|
export const ItemCardControls = ({
|
||||||
controls,
|
controls,
|
||||||
enableExpansion,
|
enableExpansion,
|
||||||
@@ -60,65 +188,65 @@ export const ItemCardControls = ({
|
|||||||
}: ItemCardControlsProps) => {
|
}: ItemCardControlsProps) => {
|
||||||
const isPlayerFetching = useIsPlayerFetching();
|
const isPlayerFetching = useIsPlayerFetching();
|
||||||
|
|
||||||
|
const playNowHandler = useMemo(
|
||||||
|
() => createPlayHandler(controls, item, internalState, itemType, Play.NOW),
|
||||||
|
[controls, item, internalState, itemType],
|
||||||
|
);
|
||||||
|
|
||||||
|
const playNextHandler = useMemo(
|
||||||
|
() => createPlayHandler(controls, item, internalState, itemType, Play.NEXT),
|
||||||
|
[controls, item, internalState, itemType],
|
||||||
|
);
|
||||||
|
|
||||||
|
const playLastHandler = useMemo(
|
||||||
|
() => createPlayHandler(controls, item, internalState, itemType, Play.LAST),
|
||||||
|
[controls, item, internalState, itemType],
|
||||||
|
);
|
||||||
|
|
||||||
|
const favoriteHandler = useMemo(
|
||||||
|
() => createFavoriteHandler(controls, item, internalState, itemType),
|
||||||
|
[controls, item, internalState, itemType],
|
||||||
|
);
|
||||||
|
|
||||||
|
const ratingChangeHandler = useMemo(
|
||||||
|
() => createRatingChangeHandler(controls, item, internalState, itemType),
|
||||||
|
[controls, item, internalState, itemType],
|
||||||
|
);
|
||||||
|
|
||||||
|
const moreHandler = useMemo(
|
||||||
|
() => createMoreHandler(controls, item, internalState, itemType),
|
||||||
|
[controls, item, internalState, itemType],
|
||||||
|
);
|
||||||
|
|
||||||
|
const expandHandler = useMemo(
|
||||||
|
() => createExpandHandler(controls, item, internalState, itemType),
|
||||||
|
[controls, item, internalState, itemType],
|
||||||
|
);
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<motion.div className={clsx(styles.container)} {...containerProps[type]}>
|
<motion.div className={clsx(styles.container)} {...containerProps[type]}>
|
||||||
{controls?.onPlay && (
|
{controls?.onPlay && (
|
||||||
<>
|
<>
|
||||||
<PlayButton
|
<PlayButton disabled={isPlayerFetching} onClick={playNowHandler} />
|
||||||
disabled={isPlayerFetching}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
controls?.onPlay?.({
|
|
||||||
event: e,
|
|
||||||
internalState,
|
|
||||||
item,
|
|
||||||
itemType,
|
|
||||||
playType: Play.NOW,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SecondaryPlayButton
|
<SecondaryPlayButton
|
||||||
className={styles.left}
|
className={styles.left}
|
||||||
icon="mediaPlayNext"
|
icon="mediaPlayNext"
|
||||||
onClick={(e) => {
|
onClick={playNextHandler}
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
controls?.onPlay?.({
|
|
||||||
event: e,
|
|
||||||
internalState,
|
|
||||||
item,
|
|
||||||
itemType,
|
|
||||||
playType: Play.NEXT,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<SecondaryPlayButton
|
<SecondaryPlayButton
|
||||||
className={styles.right}
|
className={styles.right}
|
||||||
icon="mediaPlayLast"
|
icon="mediaPlayLast"
|
||||||
onClick={(e) => {
|
onClick={playLastHandler}
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
controls?.onPlay?.({
|
|
||||||
event: e,
|
|
||||||
internalState,
|
|
||||||
item,
|
|
||||||
itemType,
|
|
||||||
playType: Play.LAST,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -126,57 +254,16 @@ export const ItemCardControls = ({
|
|||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
className={styles.favorite}
|
className={styles.favorite}
|
||||||
icon="favorite"
|
icon="favorite"
|
||||||
iconProps={{
|
iconProps={favoriteIconProps}
|
||||||
color: (item as { userFavorite: boolean }).userFavorite
|
onClick={favoriteHandler}
|
||||||
? 'primary'
|
|
||||||
: 'default',
|
|
||||||
fill: (item as { userFavorite: boolean }).userFavorite
|
|
||||||
? 'primary'
|
|
||||||
: undefined,
|
|
||||||
}}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newFavorite = !(item as { userFavorite: boolean }).userFavorite;
|
|
||||||
controls?.onFavorite?.({
|
|
||||||
event: e,
|
|
||||||
favorite: newFavorite,
|
|
||||||
internalState,
|
|
||||||
item,
|
|
||||||
itemType,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{controls?.onRating && (
|
{controls?.onRating && (
|
||||||
<Rating
|
<Rating
|
||||||
className={styles.rating}
|
className={styles.rating}
|
||||||
onChange={(rating) => {
|
onChange={ratingChangeHandler}
|
||||||
if (!item) {
|
onClick={ratingClickHandler}
|
||||||
return;
|
onMouseDown={ratingMouseDownHandler}
|
||||||
}
|
|
||||||
|
|
||||||
let newRating = rating;
|
|
||||||
|
|
||||||
if (rating === (item as { userRating: number }).userRating) {
|
|
||||||
newRating = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
controls?.onRating?.({
|
|
||||||
event: null,
|
|
||||||
internalState,
|
|
||||||
item,
|
|
||||||
itemType,
|
|
||||||
rating: newRating,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
size="xs"
|
size="xs"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -184,90 +271,92 @@ export const ItemCardControls = ({
|
|||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
className={styles.options}
|
className={styles.options}
|
||||||
icon="ellipsisHorizontal"
|
icon="ellipsisHorizontal"
|
||||||
onClick={(e) => {
|
onClick={moreHandler}
|
||||||
e.stopPropagation();
|
onDoubleClick={moreDoubleClickHandler}
|
||||||
e.preventDefault();
|
|
||||||
controls?.onMore?.({
|
|
||||||
event: e,
|
|
||||||
internalState,
|
|
||||||
item,
|
|
||||||
itemType,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onDoubleClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{controls?.onExpand && enableExpansion && (
|
{controls?.onExpand && enableExpansion && (
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
className={styles.expand}
|
className={styles.expand}
|
||||||
icon="arrowDownS"
|
icon="arrowDownS"
|
||||||
onClick={(e) => {
|
onClick={expandHandler}
|
||||||
e.stopPropagation();
|
|
||||||
controls?.onExpand?.({
|
|
||||||
event: e,
|
|
||||||
internalState,
|
|
||||||
item,
|
|
||||||
itemType,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PlayButton = ({
|
const PlayButton = memo(
|
||||||
disabled,
|
({
|
||||||
loading,
|
disabled,
|
||||||
onClick,
|
loading,
|
||||||
}: {
|
onClick,
|
||||||
disabled?: boolean;
|
}: {
|
||||||
loading?: boolean;
|
disabled?: boolean;
|
||||||
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
loading?: boolean;
|
||||||
}) => {
|
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||||
return (
|
}) => {
|
||||||
<button
|
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
className={clsx(styles.playButton, styles.primary, {
|
e.stopPropagation();
|
||||||
[styles.disabled]: disabled,
|
e.preventDefault();
|
||||||
})}
|
if (disabled || loading) {
|
||||||
disabled={disabled}
|
return;
|
||||||
onClick={(e) => {
|
}
|
||||||
e.stopPropagation();
|
onClick?.(e);
|
||||||
if (disabled || loading) {
|
};
|
||||||
return;
|
|
||||||
}
|
|
||||||
onClick?.(e);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon icon="mediaPlay" size="lg" />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const SecondaryPlayButton = ({
|
const handleMouseDown = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
className,
|
e.stopPropagation();
|
||||||
icon,
|
e.preventDefault();
|
||||||
onClick,
|
};
|
||||||
}: {
|
|
||||||
className?: string;
|
return (
|
||||||
icon: keyof typeof AppIcon;
|
<button
|
||||||
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
className={clsx(styles.playButton, styles.primary, {
|
||||||
}) => {
|
[styles.disabled]: disabled,
|
||||||
return (
|
})}
|
||||||
<button
|
disabled={disabled}
|
||||||
className={clsx(styles.playButton, styles.secondary, className)}
|
onClick={handleClick}
|
||||||
onClick={(e) => {
|
onMouseDown={handleMouseDown}
|
||||||
e.stopPropagation();
|
>
|
||||||
onClick?.(e);
|
<Icon icon="mediaPlay" size="lg" />
|
||||||
}}
|
</button>
|
||||||
>
|
);
|
||||||
<Icon icon={icon} size="lg" />
|
},
|
||||||
</button>
|
);
|
||||||
);
|
|
||||||
};
|
const SecondaryPlayButton = memo(
|
||||||
|
({
|
||||||
|
className,
|
||||||
|
icon,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
className?: string;
|
||||||
|
icon: keyof typeof AppIcon;
|
||||||
|
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
}) => {
|
||||||
|
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
onClick?.(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseDown = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={clsx(styles.playButton, styles.secondary, className)}
|
||||||
|
onClick={handleClick}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
>
|
||||||
|
<Icon icon={icon} size="lg" />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
interface SecondaryButtonProps {
|
interface SecondaryButtonProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -275,31 +364,43 @@ interface SecondaryButtonProps {
|
|||||||
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SecondaryButton = ({
|
const SecondaryButton = memo(
|
||||||
className,
|
({
|
||||||
icon,
|
className,
|
||||||
iconProps,
|
icon,
|
||||||
onClick,
|
iconProps,
|
||||||
onDoubleClick,
|
onClick,
|
||||||
}: SecondaryButtonProps & {
|
onDoubleClick,
|
||||||
iconProps?: Partial<IconProps>;
|
}: SecondaryButtonProps & {
|
||||||
onDoubleClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
iconProps?: Partial<IconProps>;
|
||||||
}) => {
|
onDoubleClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
||||||
return (
|
}) => {
|
||||||
<button
|
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
className={clsx(styles.secondaryButton, className)}
|
e.stopPropagation();
|
||||||
onClick={(e) => {
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
onClick?.(e);
|
||||||
e.preventDefault();
|
};
|
||||||
onClick?.(e);
|
|
||||||
}}
|
const handleDoubleClick = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
onDoubleClick={(e) => {
|
e.stopPropagation();
|
||||||
e.stopPropagation();
|
e.preventDefault();
|
||||||
e.preventDefault();
|
onDoubleClick?.(e);
|
||||||
onDoubleClick?.(e);
|
};
|
||||||
}}
|
|
||||||
>
|
const handleMouseDown = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
<Icon icon={icon} size="lg" {...iconProps} />
|
e.stopPropagation();
|
||||||
</button>
|
e.preventDefault();
|
||||||
);
|
};
|
||||||
};
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={clsx(styles.secondaryButton, className)}
|
||||||
|
onClick={handleClick}
|
||||||
|
onDoubleClick={handleDoubleClick}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
>
|
||||||
|
<Icon icon={icon} size="lg" {...iconProps} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user