mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
add long press to card controls for shuffle
This commit is contained in:
@@ -13,6 +13,7 @@ import { useIsMutatingRating } from '/@/renderer/features/shared/mutations/set-r
|
|||||||
import { animationVariants } from '/@/shared/components/animations/animation-variants';
|
import { animationVariants } from '/@/shared/components/animations/animation-variants';
|
||||||
import { AppIcon, Icon, IconProps } from '/@/shared/components/icon/icon';
|
import { AppIcon, Icon, IconProps } from '/@/shared/components/icon/icon';
|
||||||
import { Rating } from '/@/shared/components/rating/rating';
|
import { Rating } from '/@/shared/components/rating/rating';
|
||||||
|
import { useLongPress } from '/@/shared/hooks/use-long-press';
|
||||||
import {
|
import {
|
||||||
Album,
|
Album,
|
||||||
AlbumArtist,
|
AlbumArtist,
|
||||||
@@ -179,8 +180,6 @@ export const ItemCardControls = ({
|
|||||||
itemType,
|
itemType,
|
||||||
type = 'default',
|
type = 'default',
|
||||||
}: ItemCardControlsProps) => {
|
}: ItemCardControlsProps) => {
|
||||||
const isPlayerFetching = useIsPlayerFetching();
|
|
||||||
|
|
||||||
const playNowHandler = useMemo(
|
const playNowHandler = useMemo(
|
||||||
() => createPlayHandler(controls, item, internalState, itemType, Play.NOW),
|
() => createPlayHandler(controls, item, internalState, itemType, Play.NOW),
|
||||||
[controls, item, internalState, itemType],
|
[controls, item, internalState, itemType],
|
||||||
@@ -196,6 +195,21 @@ export const ItemCardControls = ({
|
|||||||
[controls, item, internalState, itemType],
|
[controls, item, internalState, itemType],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const playShuffleHandler = useMemo(
|
||||||
|
() => createPlayHandler(controls, item, internalState, itemType, Play.SHUFFLE),
|
||||||
|
[controls, item, internalState, itemType],
|
||||||
|
);
|
||||||
|
|
||||||
|
const playNextShuffleHandler = useMemo(
|
||||||
|
() => createPlayHandler(controls, item, internalState, itemType, Play.NEXT_SHUFFLE),
|
||||||
|
[controls, item, internalState, itemType],
|
||||||
|
);
|
||||||
|
|
||||||
|
const playLastShuffleHandler = useMemo(
|
||||||
|
() => createPlayHandler(controls, item, internalState, itemType, Play.LAST_SHUFFLE),
|
||||||
|
[controls, item, internalState, itemType],
|
||||||
|
);
|
||||||
|
|
||||||
const favoriteHandler = useMemo(
|
const favoriteHandler = useMemo(
|
||||||
() => createFavoriteHandler(controls, item, internalState, itemType),
|
() => createFavoriteHandler(controls, item, internalState, itemType),
|
||||||
[controls, item, internalState, itemType],
|
[controls, item, internalState, itemType],
|
||||||
@@ -222,16 +236,18 @@ export const ItemCardControls = ({
|
|||||||
<motion.div className={clsx(styles.container)} {...containerProps[type]}>
|
<motion.div className={clsx(styles.container)} {...containerProps[type]}>
|
||||||
{controls?.onPlay && (
|
{controls?.onPlay && (
|
||||||
<>
|
<>
|
||||||
<PlayButton disabled={isPlayerFetching} onClick={playNowHandler} />
|
<PlayButton onClick={playNowHandler} onLongPress={playShuffleHandler} />
|
||||||
<SecondaryPlayButton
|
<SecondaryPlayButton
|
||||||
className={styles.left}
|
className={styles.left}
|
||||||
icon="mediaPlayNext"
|
icon="mediaPlayNext"
|
||||||
onClick={playNextHandler}
|
onClick={playNextHandler}
|
||||||
|
onLongPress={playNextShuffleHandler}
|
||||||
/>
|
/>
|
||||||
<SecondaryPlayButton
|
<SecondaryPlayButton
|
||||||
className={styles.right}
|
className={styles.right}
|
||||||
icon="mediaPlayLast"
|
icon="mediaPlayLast"
|
||||||
onClick={playLastHandler}
|
onClick={playLastHandler}
|
||||||
|
onLongPress={playLastShuffleHandler}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -326,26 +342,40 @@ const RatingButton = memo(
|
|||||||
|
|
||||||
const PlayButton = memo(
|
const PlayButton = memo(
|
||||||
({
|
({
|
||||||
disabled,
|
|
||||||
loading,
|
loading,
|
||||||
onClick,
|
onClick,
|
||||||
|
onLongPress,
|
||||||
}: {
|
}: {
|
||||||
disabled?: boolean;
|
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
onLongPress?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
|
const isPlayerFetching = useIsPlayerFetching();
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
const disabled = isPlayerFetching || loading;
|
||||||
if (disabled || loading) {
|
|
||||||
return;
|
const longPressHandlers = useLongPress<HTMLButtonElement>({
|
||||||
}
|
onClick: (e) => {
|
||||||
onClick?.(e);
|
if (disabled || loading) {
|
||||||
};
|
return;
|
||||||
|
}
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
onClick?.(e as React.MouseEvent<HTMLButtonElement>);
|
||||||
|
},
|
||||||
|
onLongPress: (e) => {
|
||||||
|
if (disabled || loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
onLongPress?.(e as React.MouseEvent<HTMLButtonElement>);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const handleMouseDown = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleMouseDown = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
longPressHandlers.onMouseDown?.(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -354,8 +384,12 @@ const PlayButton = memo(
|
|||||||
[styles.disabled]: disabled,
|
[styles.disabled]: disabled,
|
||||||
})}
|
})}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={handleClick}
|
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
|
onMouseLeave={longPressHandlers.onMouseLeave}
|
||||||
|
onMouseUp={longPressHandlers.onMouseUp}
|
||||||
|
onTouchCancel={longPressHandlers.onTouchCancel}
|
||||||
|
onTouchEnd={longPressHandlers.onTouchEnd}
|
||||||
|
onTouchStart={longPressHandlers.onTouchStart}
|
||||||
>
|
>
|
||||||
<Icon icon="mediaPlay" size="lg" />
|
<Icon icon="mediaPlay" size="lg" />
|
||||||
</button>
|
</button>
|
||||||
@@ -368,27 +402,47 @@ const SecondaryPlayButton = memo(
|
|||||||
className,
|
className,
|
||||||
icon,
|
icon,
|
||||||
onClick,
|
onClick,
|
||||||
|
onLongPress,
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
icon: keyof typeof AppIcon;
|
icon: keyof typeof AppIcon;
|
||||||
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
|
onLongPress?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
|
const isPlayerFetching = useIsPlayerFetching();
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
const disabled = isPlayerFetching;
|
||||||
onClick?.(e);
|
|
||||||
};
|
const longPressHandlers = useLongPress<HTMLButtonElement>({
|
||||||
|
onClick: (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
onClick?.(e as React.MouseEvent<HTMLButtonElement>);
|
||||||
|
},
|
||||||
|
onLongPress: (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
onLongPress?.(e as React.MouseEvent<HTMLButtonElement>);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const handleMouseDown = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleMouseDown = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
longPressHandlers.onMouseDown?.(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={clsx(styles.playButton, styles.secondary, className)}
|
className={clsx(styles.playButton, styles.secondary, className, {
|
||||||
onClick={handleClick}
|
[styles.disabled]: disabled,
|
||||||
|
})}
|
||||||
|
disabled={disabled}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
|
onMouseLeave={longPressHandlers.onMouseLeave}
|
||||||
|
onMouseUp={longPressHandlers.onMouseUp}
|
||||||
|
onTouchCancel={longPressHandlers.onTouchCancel}
|
||||||
|
onTouchEnd={longPressHandlers.onTouchEnd}
|
||||||
|
onTouchStart={longPressHandlers.onTouchStart}
|
||||||
>
|
>
|
||||||
<Icon icon={icon} size="lg" />
|
<Icon icon={icon} size="lg" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,3 +1,104 @@
|
|||||||
import { useLongPress as useMantineLongPress } from '@mantine/hooks';
|
import { useCallback, useRef } from 'react';
|
||||||
|
|
||||||
export const useLongPress = useMantineLongPress;
|
interface UseLongPressOptions<T extends HTMLElement = HTMLElement> {
|
||||||
|
delay?: number;
|
||||||
|
onClick?: (event: React.MouseEvent<T> | React.TouchEvent<T>) => void;
|
||||||
|
onLongPress?: (event: React.MouseEvent<T> | React.TouchEvent<T>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseLongPressReturn {
|
||||||
|
onMouseDown: (event: React.MouseEvent) => void;
|
||||||
|
onMouseLeave: (event: React.MouseEvent) => void;
|
||||||
|
onMouseUp: (event: React.MouseEvent) => void;
|
||||||
|
onTouchCancel: (event: React.TouchEvent) => void;
|
||||||
|
onTouchEnd: (event: React.TouchEvent) => void;
|
||||||
|
onTouchStart: (event: React.TouchEvent) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useLongPress = <T extends HTMLElement = HTMLElement>({
|
||||||
|
delay = 500,
|
||||||
|
onClick,
|
||||||
|
onLongPress,
|
||||||
|
}: UseLongPressOptions<T>): UseLongPressReturn => {
|
||||||
|
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const targetRef = useRef<EventTarget | null>(null);
|
||||||
|
const longPressTriggeredRef = useRef(false);
|
||||||
|
const eventRef = useRef<null | React.MouseEvent<T> | React.TouchEvent<T>>(null);
|
||||||
|
|
||||||
|
const start = useCallback(
|
||||||
|
(event: React.MouseEvent<T> | React.TouchEvent<T>) => {
|
||||||
|
longPressTriggeredRef.current = false;
|
||||||
|
targetRef.current = event.target;
|
||||||
|
eventRef.current = event;
|
||||||
|
|
||||||
|
timeoutRef.current = setTimeout(() => {
|
||||||
|
longPressTriggeredRef.current = true;
|
||||||
|
if (eventRef.current) {
|
||||||
|
onLongPress?.(eventRef.current);
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
|
},
|
||||||
|
[onLongPress, delay],
|
||||||
|
);
|
||||||
|
|
||||||
|
const clear = useCallback(() => {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
timeoutRef.current = null;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleMouseDown = useCallback(
|
||||||
|
(event: React.MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
start(event as React.MouseEvent<T>);
|
||||||
|
},
|
||||||
|
[start],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleMouseUp = useCallback(() => {
|
||||||
|
clear();
|
||||||
|
if (!longPressTriggeredRef.current && onClick && eventRef.current) {
|
||||||
|
onClick(eventRef.current);
|
||||||
|
}
|
||||||
|
longPressTriggeredRef.current = false;
|
||||||
|
eventRef.current = null;
|
||||||
|
}, [clear, onClick]);
|
||||||
|
|
||||||
|
const handleMouseLeave = useCallback(() => {
|
||||||
|
clear();
|
||||||
|
longPressTriggeredRef.current = false;
|
||||||
|
eventRef.current = null;
|
||||||
|
}, [clear]);
|
||||||
|
|
||||||
|
const handleTouchStart = useCallback(
|
||||||
|
(event: React.TouchEvent) => {
|
||||||
|
start(event as React.TouchEvent<T>);
|
||||||
|
},
|
||||||
|
[start],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTouchEnd = useCallback(() => {
|
||||||
|
clear();
|
||||||
|
if (!longPressTriggeredRef.current && onClick && eventRef.current) {
|
||||||
|
onClick(eventRef.current);
|
||||||
|
}
|
||||||
|
longPressTriggeredRef.current = false;
|
||||||
|
eventRef.current = null;
|
||||||
|
}, [clear, onClick]);
|
||||||
|
|
||||||
|
const handleTouchCancel = useCallback(() => {
|
||||||
|
clear();
|
||||||
|
longPressTriggeredRef.current = false;
|
||||||
|
eventRef.current = null;
|
||||||
|
}, [clear]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
onMouseDown: handleMouseDown,
|
||||||
|
onMouseLeave: handleMouseLeave,
|
||||||
|
onMouseUp: handleMouseUp,
|
||||||
|
onTouchCancel: handleTouchCancel,
|
||||||
|
onTouchEnd: handleTouchEnd,
|
||||||
|
onTouchStart: handleTouchStart,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user