mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
304 lines
9.3 KiB
TypeScript
304 lines
9.3 KiB
TypeScript
import clsx from 'clsx';
|
|
import { motion } from 'motion/react';
|
|
import { MouseEvent } from 'react';
|
|
|
|
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 { animationVariants } from '/@/shared/components/animations/animation-variants';
|
|
import { AppIcon, Icon, IconProps } from '/@/shared/components/icon/icon';
|
|
import { Rating } from '/@/shared/components/rating/rating';
|
|
import {
|
|
Album,
|
|
AlbumArtist,
|
|
Artist,
|
|
LibraryItem,
|
|
Playlist,
|
|
Song,
|
|
} from '/@/shared/types/domain-types';
|
|
import { Play } from '/@/shared/types/types';
|
|
|
|
interface ItemCardControlsProps {
|
|
controls?: ItemControls;
|
|
internalState?: ItemListStateActions;
|
|
item: Album | AlbumArtist | Artist | Playlist | Song | undefined;
|
|
itemType: LibraryItem;
|
|
type?: 'compact' | 'default' | 'poster';
|
|
}
|
|
|
|
const containerProps = {
|
|
compact: {
|
|
animate: 'show',
|
|
exit: 'hidden',
|
|
initial: 'hidden',
|
|
variants: animationVariants.combine(animationVariants.zoomIn, animationVariants.fadeIn),
|
|
},
|
|
default: {
|
|
animate: 'show',
|
|
exit: 'hidden',
|
|
initial: 'hidden',
|
|
variants: animationVariants.combine(animationVariants.zoomIn, animationVariants.fadeIn),
|
|
},
|
|
poster: {
|
|
animate: 'show',
|
|
exit: 'hidden',
|
|
initial: 'hidden',
|
|
variants: animationVariants.combine(animationVariants.slideInUp, animationVariants.fadeIn),
|
|
},
|
|
};
|
|
|
|
export const ItemCardControls = ({
|
|
controls,
|
|
internalState,
|
|
item,
|
|
itemType,
|
|
type = 'default',
|
|
}: ItemCardControlsProps) => {
|
|
const isPlayerFetching = useIsPlayerFetching();
|
|
|
|
return (
|
|
<motion.div className={clsx(styles.container)} {...containerProps[type]}>
|
|
{controls?.onPlay && (
|
|
<>
|
|
<PlayButton
|
|
disabled={isPlayerFetching}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
controls?.onPlay?.({
|
|
event: e,
|
|
internalState,
|
|
item,
|
|
itemType,
|
|
playType: Play.NOW,
|
|
});
|
|
}}
|
|
/>
|
|
<SecondaryPlayButton
|
|
className={styles.left}
|
|
icon="mediaPlayNext"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
controls?.onPlay?.({
|
|
event: e,
|
|
internalState,
|
|
item,
|
|
itemType,
|
|
playType: Play.NEXT,
|
|
});
|
|
}}
|
|
/>
|
|
<SecondaryPlayButton
|
|
className={styles.right}
|
|
icon="mediaPlayLast"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
controls?.onPlay?.({
|
|
event: e,
|
|
internalState,
|
|
item,
|
|
itemType,
|
|
playType: Play.LAST,
|
|
});
|
|
}}
|
|
/>
|
|
</>
|
|
)}
|
|
{controls?.onFavorite && (
|
|
<SecondaryButton
|
|
className={styles.favorite}
|
|
icon="favorite"
|
|
iconProps={{
|
|
color: (item as { userFavorite: boolean }).userFavorite
|
|
? '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 && (
|
|
<Rating
|
|
className={styles.rating}
|
|
onChange={(rating) => {
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
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"
|
|
/>
|
|
)}
|
|
{controls?.onMore && (
|
|
<SecondaryButton
|
|
className={styles.options}
|
|
icon="ellipsisHorizontal"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
controls?.onMore?.({
|
|
event: e,
|
|
internalState,
|
|
item,
|
|
itemType,
|
|
});
|
|
}}
|
|
onDoubleClick={(e) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
}}
|
|
/>
|
|
)}
|
|
{controls?.onExpand && (
|
|
<SecondaryButton
|
|
className={styles.expand}
|
|
icon="arrowDownS"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
controls?.onExpand?.({
|
|
event: e,
|
|
internalState,
|
|
item,
|
|
itemType,
|
|
});
|
|
}}
|
|
/>
|
|
)}
|
|
</motion.div>
|
|
);
|
|
};
|
|
|
|
const PlayButton = ({
|
|
disabled,
|
|
loading,
|
|
onClick,
|
|
}: {
|
|
disabled?: boolean;
|
|
loading?: boolean;
|
|
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
|
}) => {
|
|
return (
|
|
<button
|
|
className={clsx(styles.playButton, styles.primary, {
|
|
[styles.disabled]: disabled,
|
|
})}
|
|
disabled={disabled}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
if (disabled || loading) {
|
|
return;
|
|
}
|
|
onClick?.(e);
|
|
}}
|
|
>
|
|
<Icon icon="mediaPlay" size="lg" />
|
|
</button>
|
|
);
|
|
};
|
|
|
|
const SecondaryPlayButton = ({
|
|
className,
|
|
icon,
|
|
onClick,
|
|
}: {
|
|
className?: string;
|
|
icon: keyof typeof AppIcon;
|
|
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
|
}) => {
|
|
return (
|
|
<button
|
|
className={clsx(styles.playButton, styles.secondary, className)}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onClick?.(e);
|
|
}}
|
|
>
|
|
<Icon icon={icon} size="lg" />
|
|
</button>
|
|
);
|
|
};
|
|
|
|
interface SecondaryButtonProps {
|
|
className?: string;
|
|
icon: keyof typeof AppIcon;
|
|
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
|
}
|
|
|
|
const SecondaryButton = ({
|
|
className,
|
|
icon,
|
|
iconProps,
|
|
onClick,
|
|
onDoubleClick,
|
|
}: SecondaryButtonProps & {
|
|
iconProps?: Partial<IconProps>;
|
|
onDoubleClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
|
}) => {
|
|
return (
|
|
<button
|
|
className={clsx(styles.secondaryButton, className)}
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
onClick?.(e);
|
|
}}
|
|
onDoubleClick={(e) => {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
onDoubleClick?.(e);
|
|
}}
|
|
>
|
|
<Icon icon={icon} size="lg" {...iconProps} />
|
|
</button>
|
|
);
|
|
};
|