mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
isolate item card control renders
This commit is contained in:
@@ -169,6 +169,292 @@ export interface ItemCardDerivativeProps extends Omit<ItemCardProps, 'type'> {
|
|||||||
showRating: boolean;
|
showRating: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ItemCardData = NonNullable<ItemCardProps['data']>;
|
||||||
|
|
||||||
|
const ItemCardStandardImageArea = memo(function ItemCardStandardImageArea({
|
||||||
|
controls,
|
||||||
|
data,
|
||||||
|
enableExpansion,
|
||||||
|
enableImageViewport = true,
|
||||||
|
enableNavigation,
|
||||||
|
handleContextMenu,
|
||||||
|
handleImageClick,
|
||||||
|
handleLinkDragStart,
|
||||||
|
imageAsLink,
|
||||||
|
imageFetchPriority,
|
||||||
|
internalState,
|
||||||
|
isRound,
|
||||||
|
itemType,
|
||||||
|
navigationPath,
|
||||||
|
showRating,
|
||||||
|
variant,
|
||||||
|
withControls,
|
||||||
|
}: {
|
||||||
|
controls?: ItemControls;
|
||||||
|
data: ItemCardData;
|
||||||
|
enableExpansion?: boolean;
|
||||||
|
enableImageViewport?: boolean;
|
||||||
|
enableNavigation?: boolean;
|
||||||
|
handleContextMenu: (e: React.MouseEvent<HTMLElement>) => void;
|
||||||
|
handleImageClick: (e: React.MouseEvent<HTMLElement>) => void;
|
||||||
|
handleLinkDragStart: (e: React.DragEvent<HTMLAnchorElement>) => void;
|
||||||
|
imageAsLink?: boolean;
|
||||||
|
imageFetchPriority?: 'auto' | 'high' | 'low';
|
||||||
|
internalState?: ItemListStateActions;
|
||||||
|
isRound?: boolean;
|
||||||
|
itemType: LibraryItem;
|
||||||
|
navigationPath: null | string;
|
||||||
|
showRating: boolean;
|
||||||
|
variant: 'default' | 'poster';
|
||||||
|
withControls?: boolean;
|
||||||
|
}) {
|
||||||
|
const [showControls, setShowControls] = useState(false);
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
if (withControls) {
|
||||||
|
setShowControls(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
if (withControls) {
|
||||||
|
setShowControls(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const imageContainerClassName = clsx(styles.imageContainer, {
|
||||||
|
[styles.isRound]: isRound,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isFavorite = 'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
|
||||||
|
const userRating =
|
||||||
|
'userRating' in data &&
|
||||||
|
typeof (data as { userRating: null | number }).userRating === 'number'
|
||||||
|
? (data as { userRating: null | number }).userRating
|
||||||
|
: null;
|
||||||
|
const hasRating = showRating && userRating !== null && userRating > 0;
|
||||||
|
|
||||||
|
const imageContainerContent = (
|
||||||
|
<>
|
||||||
|
{itemType === LibraryItem.GENRE &&
|
||||||
|
data &&
|
||||||
|
'name' in data &&
|
||||||
|
typeof (data as Genre).name === 'string' ? (
|
||||||
|
<GenreImagePlaceholder
|
||||||
|
className={clsx(styles.image, styles.genrePlaceholder, {
|
||||||
|
[styles.isRound]: isRound,
|
||||||
|
})}
|
||||||
|
name={(data as Genre).name}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ItemImage
|
||||||
|
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
||||||
|
enableDebounce={false}
|
||||||
|
{...(variant === 'poster' ? { enableViewport: enableImageViewport } : {})}
|
||||||
|
explicitStatus={'explicitStatus' in data && data ? data.explicitStatus : null}
|
||||||
|
fetchPriority={imageFetchPriority}
|
||||||
|
id={(data as { imageId?: string })?.imageId}
|
||||||
|
itemType={itemType}
|
||||||
|
src={(data as { imageUrl?: string })?.imageUrl}
|
||||||
|
type="itemCard"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isFavorite && <div className={styles.favoriteBadge} />}
|
||||||
|
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||||
|
<AnimatePresence>
|
||||||
|
{withControls && showControls && (
|
||||||
|
<ItemCardControls
|
||||||
|
controls={controls}
|
||||||
|
enableExpansion={enableExpansion}
|
||||||
|
{...(variant === 'poster' ? { internalState } : {})}
|
||||||
|
item={data}
|
||||||
|
itemType={itemType}
|
||||||
|
showRating={showRating}
|
||||||
|
type={variant}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
|
||||||
|
<Link
|
||||||
|
className={imageContainerClassName}
|
||||||
|
draggable={false}
|
||||||
|
onClick={handleImageClick}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
|
onDragStart={handleLinkDragStart}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
state={{ item: data }}
|
||||||
|
to={navigationPath}
|
||||||
|
>
|
||||||
|
{imageContainerContent}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={imageContainerClassName}
|
||||||
|
onClick={handleImageClick}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
{imageContainerContent}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ItemCardStandardImageArea.displayName = 'ItemCardStandardImageArea';
|
||||||
|
|
||||||
|
const CompactItemCardImageArea = memo(function CompactItemCardImageArea({
|
||||||
|
controls,
|
||||||
|
data,
|
||||||
|
enableExpansion,
|
||||||
|
enableNavigation,
|
||||||
|
handleContextMenu,
|
||||||
|
handleImageClick,
|
||||||
|
handleLinkDragStart,
|
||||||
|
imageAsLink,
|
||||||
|
imageFetchPriority,
|
||||||
|
internalState,
|
||||||
|
isRound,
|
||||||
|
itemType,
|
||||||
|
navigationPath,
|
||||||
|
rows,
|
||||||
|
showRating,
|
||||||
|
withControls,
|
||||||
|
}: {
|
||||||
|
controls?: ItemControls;
|
||||||
|
data: ItemCardData;
|
||||||
|
enableExpansion?: boolean;
|
||||||
|
enableNavigation?: boolean;
|
||||||
|
handleContextMenu: (e: React.MouseEvent<HTMLElement>) => void;
|
||||||
|
handleImageClick: (e: React.MouseEvent<HTMLElement>) => void;
|
||||||
|
handleLinkDragStart: (e: React.DragEvent<HTMLAnchorElement>) => void;
|
||||||
|
imageAsLink?: boolean;
|
||||||
|
imageFetchPriority?: 'auto' | 'high' | 'low';
|
||||||
|
internalState?: ItemListStateActions;
|
||||||
|
isRound?: boolean;
|
||||||
|
itemType: LibraryItem;
|
||||||
|
navigationPath: null | string;
|
||||||
|
rows: DataRow[];
|
||||||
|
showRating: boolean;
|
||||||
|
withControls?: boolean;
|
||||||
|
}) {
|
||||||
|
const [showControls, setShowControls] = useState(false);
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
if (withControls) {
|
||||||
|
setShowControls(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
if (withControls) {
|
||||||
|
setShowControls(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const imageContainerClassName = clsx(styles.imageContainer, {
|
||||||
|
[styles.isRound]: isRound,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isFavorite = 'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
|
||||||
|
const userRating =
|
||||||
|
'userRating' in data &&
|
||||||
|
typeof (data as { userRating: null | number }).userRating === 'number'
|
||||||
|
? (data as { userRating: null | number }).userRating
|
||||||
|
: null;
|
||||||
|
const hasRating = showRating && userRating !== null && userRating > 0;
|
||||||
|
|
||||||
|
const imageContainerContent = (
|
||||||
|
<>
|
||||||
|
{itemType === LibraryItem.GENRE &&
|
||||||
|
data &&
|
||||||
|
'name' in data &&
|
||||||
|
typeof (data as Genre).name === 'string' ? (
|
||||||
|
<GenreImagePlaceholder
|
||||||
|
className={clsx(styles.image, styles.genrePlaceholder, {
|
||||||
|
[styles.isRound]: isRound,
|
||||||
|
})}
|
||||||
|
name={(data as Genre).name}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ItemImage
|
||||||
|
className={clsx(styles.image, {
|
||||||
|
[styles.isRound]: isRound,
|
||||||
|
})}
|
||||||
|
enableDebounce={false}
|
||||||
|
explicitStatus={'explicitStatus' in data && data ? data.explicitStatus : null}
|
||||||
|
fetchPriority={imageFetchPriority}
|
||||||
|
id={data?.imageId}
|
||||||
|
itemType={itemType}
|
||||||
|
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
|
||||||
|
type="itemCard"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isFavorite && <div className={styles.favoriteBadge} />}
|
||||||
|
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
||||||
|
<AnimatePresence>
|
||||||
|
{withControls && showControls && data && (
|
||||||
|
<ItemCardControls
|
||||||
|
controls={controls}
|
||||||
|
enableExpansion={enableExpansion}
|
||||||
|
internalState={internalState}
|
||||||
|
item={data}
|
||||||
|
itemType={itemType}
|
||||||
|
showRating={showRating}
|
||||||
|
type="compact"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
<div className={clsx(styles.detailContainer, styles.compact)}>
|
||||||
|
{rows
|
||||||
|
.filter(
|
||||||
|
(row): row is NonNullable<typeof row> => row !== null && row !== undefined,
|
||||||
|
)
|
||||||
|
.map((row, index) => (
|
||||||
|
<ItemCardRow
|
||||||
|
data={data!}
|
||||||
|
index={index}
|
||||||
|
key={row.id}
|
||||||
|
row={row}
|
||||||
|
type="compact"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
|
||||||
|
<Link
|
||||||
|
className={imageContainerClassName}
|
||||||
|
draggable={false}
|
||||||
|
onClick={handleImageClick}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
|
onDragStart={handleLinkDragStart}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
state={{ item: data }}
|
||||||
|
to={navigationPath}
|
||||||
|
>
|
||||||
|
{imageContainerContent}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className={imageContainerClassName}
|
||||||
|
onClick={handleImageClick}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
{imageContainerContent}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CompactItemCardImageArea.displayName = 'CompactItemCardImageArea';
|
||||||
|
|
||||||
const CompactItemCard = ({
|
const CompactItemCard = ({
|
||||||
controls,
|
controls,
|
||||||
data,
|
data,
|
||||||
@@ -185,7 +471,6 @@ const CompactItemCard = ({
|
|||||||
showRating,
|
showRating,
|
||||||
withControls,
|
withControls,
|
||||||
}: ItemCardDerivativeProps) => {
|
}: ItemCardDerivativeProps) => {
|
||||||
const [showControls, setShowControls] = useState(false);
|
|
||||||
const itemRowId =
|
const itemRowId =
|
||||||
data && internalState && typeof data === 'object' && 'id' in data
|
data && internalState && typeof data === 'object' && 'id' in data
|
||||||
? internalState.extractRowId(data)
|
? internalState.extractRowId(data)
|
||||||
@@ -297,18 +582,6 @@ const CompactItemCard = ({
|
|||||||
if (data) {
|
if (data) {
|
||||||
const navigationPath = getItemNavigationPath(data, itemType);
|
const navigationPath = getItemNavigationPath(data, itemType);
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
if (withControls) {
|
|
||||||
setShowControls(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
if (withControls) {
|
|
||||||
setShowControls(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
if (!data || !controls) {
|
if (!data || !controls) {
|
||||||
return;
|
return;
|
||||||
@@ -338,81 +611,6 @@ const CompactItemCard = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isFavorite =
|
|
||||||
'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
|
|
||||||
const userRating =
|
|
||||||
'userRating' in data &&
|
|
||||||
typeof (data as { userRating: null | number }).userRating === 'number'
|
|
||||||
? (data as { userRating: null | number }).userRating
|
|
||||||
: null;
|
|
||||||
const hasRating = showRating && userRating !== null && userRating > 0;
|
|
||||||
|
|
||||||
const imageContainerClassName = clsx(styles.imageContainer, {
|
|
||||||
[styles.isRound]: isRound,
|
|
||||||
});
|
|
||||||
|
|
||||||
const imageContainerContent = (
|
|
||||||
<>
|
|
||||||
{itemType === LibraryItem.GENRE &&
|
|
||||||
data &&
|
|
||||||
'name' in data &&
|
|
||||||
typeof (data as Genre).name === 'string' ? (
|
|
||||||
<GenreImagePlaceholder
|
|
||||||
className={clsx(styles.image, styles.genrePlaceholder, {
|
|
||||||
[styles.isRound]: isRound,
|
|
||||||
})}
|
|
||||||
name={(data as Genre).name}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ItemImage
|
|
||||||
className={clsx(styles.image, {
|
|
||||||
[styles.isRound]: isRound,
|
|
||||||
})}
|
|
||||||
enableDebounce={false}
|
|
||||||
explicitStatus={
|
|
||||||
'explicitStatus' in data && data ? data.explicitStatus : null
|
|
||||||
}
|
|
||||||
fetchPriority={imageFetchPriority}
|
|
||||||
id={data?.imageId}
|
|
||||||
itemType={itemType}
|
|
||||||
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
|
|
||||||
type="itemCard"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
|
||||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
|
||||||
<AnimatePresence>
|
|
||||||
{withControls && showControls && data && (
|
|
||||||
<ItemCardControls
|
|
||||||
controls={controls}
|
|
||||||
enableExpansion={enableExpansion}
|
|
||||||
internalState={internalState}
|
|
||||||
item={data}
|
|
||||||
itemType={itemType}
|
|
||||||
showRating={showRating}
|
|
||||||
type="compact"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
<div className={clsx(styles.detailContainer, styles.compact)}>
|
|
||||||
{rows
|
|
||||||
.filter(
|
|
||||||
(row): row is NonNullable<typeof row> =>
|
|
||||||
row !== null && row !== undefined,
|
|
||||||
)
|
|
||||||
.map((row, index) => (
|
|
||||||
<ItemCardRow
|
|
||||||
data={data!}
|
|
||||||
index={index}
|
|
||||||
key={row.id}
|
|
||||||
row={row}
|
|
||||||
type="compact"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.container, styles.compact, {
|
className={clsx(styles.container, styles.compact, {
|
||||||
@@ -421,31 +619,24 @@ const CompactItemCard = ({
|
|||||||
})}
|
})}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
|
<CompactItemCardImageArea
|
||||||
<Link
|
controls={controls}
|
||||||
className={imageContainerClassName}
|
data={data}
|
||||||
draggable={false}
|
enableExpansion={enableExpansion}
|
||||||
onClick={handleImageClick}
|
enableNavigation={enableNavigation}
|
||||||
onContextMenu={handleContextMenu}
|
handleContextMenu={handleContextMenu}
|
||||||
onDragStart={handleLinkDragStart}
|
handleImageClick={handleImageClick}
|
||||||
onMouseEnter={handleMouseEnter}
|
handleLinkDragStart={handleLinkDragStart}
|
||||||
onMouseLeave={handleMouseLeave}
|
imageAsLink={imageAsLink}
|
||||||
state={{ item: data }}
|
imageFetchPriority={imageFetchPriority}
|
||||||
to={navigationPath}
|
internalState={internalState}
|
||||||
>
|
isRound={isRound}
|
||||||
{imageContainerContent}
|
itemType={itemType}
|
||||||
</Link>
|
navigationPath={navigationPath}
|
||||||
) : (
|
rows={rows}
|
||||||
<div
|
showRating={showRating}
|
||||||
className={imageContainerClassName}
|
withControls={withControls}
|
||||||
onClick={handleImageClick}
|
/>
|
||||||
onContextMenu={handleContextMenu}
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
|
||||||
{imageContainerContent}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -491,7 +682,6 @@ const DefaultItemCard = ({
|
|||||||
showRating,
|
showRating,
|
||||||
withControls,
|
withControls,
|
||||||
}: ItemCardDerivativeProps) => {
|
}: ItemCardDerivativeProps) => {
|
||||||
const [showControls, setShowControls] = useState(false);
|
|
||||||
const itemRowId =
|
const itemRowId =
|
||||||
data && internalState && typeof data === 'object' && 'id' in data
|
data && internalState && typeof data === 'object' && 'id' in data
|
||||||
? internalState.extractRowId(data)
|
? internalState.extractRowId(data)
|
||||||
@@ -538,18 +728,6 @@ const DefaultItemCard = ({
|
|||||||
if (data) {
|
if (data) {
|
||||||
const navigationPath = getItemNavigationPath(data, itemType);
|
const navigationPath = getItemNavigationPath(data, itemType);
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
if (withControls) {
|
|
||||||
setShowControls(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
if (withControls) {
|
|
||||||
setShowControls(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
if (!data || !controls) {
|
if (!data || !controls) {
|
||||||
return;
|
return;
|
||||||
@@ -579,93 +757,30 @@ const DefaultItemCard = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
const imageContainerClassName = clsx(styles.imageContainer, {
|
|
||||||
[styles.isRound]: isRound,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isFavorite =
|
|
||||||
'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
|
|
||||||
const userRating =
|
|
||||||
'userRating' in data &&
|
|
||||||
typeof (data as { userRating: null | number }).userRating === 'number'
|
|
||||||
? (data as { userRating: null | number }).userRating
|
|
||||||
: null;
|
|
||||||
const hasRating = showRating && userRating !== null && userRating > 0;
|
|
||||||
|
|
||||||
const imageContainerContent = (
|
|
||||||
<>
|
|
||||||
{itemType === LibraryItem.GENRE &&
|
|
||||||
data &&
|
|
||||||
'name' in data &&
|
|
||||||
typeof (data as Genre).name === 'string' ? (
|
|
||||||
<GenreImagePlaceholder
|
|
||||||
className={clsx(styles.image, styles.genrePlaceholder, {
|
|
||||||
[styles.isRound]: isRound,
|
|
||||||
})}
|
|
||||||
name={(data as Genre).name}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ItemImage
|
|
||||||
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
|
||||||
enableDebounce={false}
|
|
||||||
explicitStatus={
|
|
||||||
'explicitStatus' in data && data ? data.explicitStatus : null
|
|
||||||
}
|
|
||||||
fetchPriority={imageFetchPriority}
|
|
||||||
id={data?.imageId}
|
|
||||||
itemType={itemType}
|
|
||||||
src={(data as Album | AlbumArtist | Playlist | Song)?.imageUrl}
|
|
||||||
type="itemCard"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
|
||||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
|
||||||
<AnimatePresence>
|
|
||||||
{withControls && showControls && (
|
|
||||||
<ItemCardControls
|
|
||||||
controls={controls}
|
|
||||||
enableExpansion={enableExpansion}
|
|
||||||
item={data}
|
|
||||||
itemType={itemType}
|
|
||||||
showRating={showRating}
|
|
||||||
type="default"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.container, {
|
className={clsx(styles.container, {
|
||||||
[styles.selected]: isSelected,
|
[styles.selected]: isSelected,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
|
<ItemCardStandardImageArea
|
||||||
<Link
|
controls={controls}
|
||||||
className={imageContainerClassName}
|
data={data}
|
||||||
draggable={false}
|
enableExpansion={enableExpansion}
|
||||||
onClick={handleImageClick}
|
enableNavigation={enableNavigation}
|
||||||
onContextMenu={handleContextMenu}
|
handleContextMenu={handleContextMenu}
|
||||||
onDragStart={handleLinkDragStart}
|
handleImageClick={handleImageClick}
|
||||||
onMouseEnter={handleMouseEnter}
|
handleLinkDragStart={handleLinkDragStart}
|
||||||
onMouseLeave={handleMouseLeave}
|
imageAsLink={imageAsLink}
|
||||||
state={{ item: data }}
|
imageFetchPriority={imageFetchPriority}
|
||||||
to={navigationPath}
|
internalState={internalState}
|
||||||
>
|
isRound={isRound}
|
||||||
{imageContainerContent}
|
itemType={itemType}
|
||||||
</Link>
|
navigationPath={navigationPath}
|
||||||
) : (
|
showRating={showRating}
|
||||||
<div
|
variant="default"
|
||||||
className={imageContainerClassName}
|
withControls={withControls}
|
||||||
onClick={handleImageClick}
|
/>
|
||||||
onContextMenu={handleContextMenu}
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
|
||||||
{imageContainerContent}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={styles.detailContainer}>
|
<div className={styles.detailContainer}>
|
||||||
{rows
|
{rows
|
||||||
.filter(
|
.filter(
|
||||||
@@ -728,7 +843,6 @@ const PosterItemCard = ({
|
|||||||
showRating,
|
showRating,
|
||||||
withControls,
|
withControls,
|
||||||
}: ItemCardDerivativeProps) => {
|
}: ItemCardDerivativeProps) => {
|
||||||
const [showControls, setShowControls] = useState(false);
|
|
||||||
const itemRowId =
|
const itemRowId =
|
||||||
data && internalState && typeof data === 'object' && 'id' in data
|
data && internalState && typeof data === 'object' && 'id' in data
|
||||||
? internalState.extractRowId(data)
|
? internalState.extractRowId(data)
|
||||||
@@ -840,18 +954,6 @@ const PosterItemCard = ({
|
|||||||
if (data) {
|
if (data) {
|
||||||
const navigationPath = getItemNavigationPath(data, itemType);
|
const navigationPath = getItemNavigationPath(data, itemType);
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
if (withControls) {
|
|
||||||
setShowControls(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
if (withControls) {
|
|
||||||
setShowControls(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
if (!data || !controls) {
|
if (!data || !controls) {
|
||||||
return;
|
return;
|
||||||
@@ -881,63 +983,6 @@ const PosterItemCard = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
const imageContainerClassName = clsx(styles.imageContainer, {
|
|
||||||
[styles.isRound]: isRound,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isFavorite =
|
|
||||||
'userFavorite' in data && (data as { userFavorite: boolean }).userFavorite;
|
|
||||||
const userRating =
|
|
||||||
'userRating' in data &&
|
|
||||||
typeof (data as { userRating: null | number }).userRating === 'number'
|
|
||||||
? (data as { userRating: null | number }).userRating
|
|
||||||
: null;
|
|
||||||
const hasRating = showRating && userRating !== null && userRating > 0;
|
|
||||||
|
|
||||||
const imageContainerContent = (
|
|
||||||
<>
|
|
||||||
{itemType === LibraryItem.GENRE &&
|
|
||||||
data &&
|
|
||||||
'name' in data &&
|
|
||||||
typeof (data as Genre).name === 'string' ? (
|
|
||||||
<GenreImagePlaceholder
|
|
||||||
className={clsx(styles.image, styles.genrePlaceholder, {
|
|
||||||
[styles.isRound]: isRound,
|
|
||||||
})}
|
|
||||||
name={(data as Genre).name}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<ItemImage
|
|
||||||
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
|
||||||
enableDebounce={false}
|
|
||||||
explicitStatus={
|
|
||||||
'explicitStatus' in data && data ? data.explicitStatus : null
|
|
||||||
}
|
|
||||||
fetchPriority={imageFetchPriority}
|
|
||||||
id={(data as { imageId: string })?.imageId}
|
|
||||||
itemType={itemType}
|
|
||||||
src={(data as { imageUrl: string })?.imageUrl}
|
|
||||||
type="itemCard"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isFavorite && <div className={styles.favoriteBadge} />}
|
|
||||||
{hasRating && <div className={styles.ratingBadge}>{userRating}</div>}
|
|
||||||
<AnimatePresence>
|
|
||||||
{withControls && showControls && data && (
|
|
||||||
<ItemCardControls
|
|
||||||
controls={controls}
|
|
||||||
enableExpansion={enableExpansion}
|
|
||||||
internalState={internalState}
|
|
||||||
item={data}
|
|
||||||
itemType={itemType}
|
|
||||||
showRating={showRating}
|
|
||||||
type="poster"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.container, styles.poster, {
|
className={clsx(styles.container, styles.poster, {
|
||||||
@@ -946,31 +991,24 @@ const PosterItemCard = ({
|
|||||||
})}
|
})}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{enableNavigation && navigationPath && (imageAsLink ?? !internalState) ? (
|
<ItemCardStandardImageArea
|
||||||
<Link
|
controls={controls}
|
||||||
className={imageContainerClassName}
|
data={data}
|
||||||
draggable={false}
|
enableExpansion={enableExpansion}
|
||||||
onClick={handleImageClick}
|
enableNavigation={enableNavigation}
|
||||||
onContextMenu={handleContextMenu}
|
handleContextMenu={handleContextMenu}
|
||||||
onDragStart={handleLinkDragStart}
|
handleImageClick={handleImageClick}
|
||||||
onMouseEnter={handleMouseEnter}
|
handleLinkDragStart={handleLinkDragStart}
|
||||||
onMouseLeave={handleMouseLeave}
|
imageAsLink={imageAsLink}
|
||||||
state={{ item: data }}
|
imageFetchPriority={imageFetchPriority}
|
||||||
to={navigationPath}
|
internalState={internalState}
|
||||||
>
|
isRound={isRound}
|
||||||
{imageContainerContent}
|
itemType={itemType}
|
||||||
</Link>
|
navigationPath={navigationPath}
|
||||||
) : (
|
showRating={showRating}
|
||||||
<div
|
variant="poster"
|
||||||
className={imageContainerClassName}
|
withControls={withControls}
|
||||||
onClick={handleImageClick}
|
/>
|
||||||
onContextMenu={handleContextMenu}
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
>
|
|
||||||
{imageContainerContent}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{data && (
|
{data && (
|
||||||
<div className={styles.detailContainer}>
|
<div className={styles.detailContainer}>
|
||||||
{rows
|
{rows
|
||||||
|
|||||||
Reference in New Issue
Block a user