mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
add item card navigation
This commit is contained in:
@@ -21,9 +21,12 @@
|
|||||||
|
|
||||||
.image-container {
|
.image-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import styles from './item-card.module.css';
|
|||||||
|
|
||||||
import { ItemCardControls } from '/@/renderer/components/item-card/item-card-controls';
|
import { ItemCardControls } from '/@/renderer/components/item-card/item-card-controls';
|
||||||
import { getDraggedItems } from '/@/renderer/components/item-list/helpers/get-dragged-items';
|
import { getDraggedItems } from '/@/renderer/components/item-list/helpers/get-dragged-items';
|
||||||
|
import { getTitlePath } from '/@/renderer/components/item-list/helpers/get-title-path';
|
||||||
import { ItemListStateActions } from '/@/renderer/components/item-list/helpers/item-list-state';
|
import { ItemListStateActions } from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { ItemControls } from '/@/renderer/components/item-list/types';
|
import { ItemControls } from '/@/renderer/components/item-list/types';
|
||||||
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
||||||
@@ -170,6 +171,8 @@ const CompactItemCard = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
|
const navigationPath = getItemNavigationPath(data, itemType);
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
if (withControls) {
|
if (withControls) {
|
||||||
setShowControls(true);
|
setShowControls(true);
|
||||||
@@ -182,7 +185,7 @@ const CompactItemCard = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleContextMenu = (e: React.MouseEvent<HTMLDivElement>) => {
|
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
if (!data || !controls) {
|
if (!data || !controls) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -197,50 +200,81 @@ const CompactItemCard = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleImageClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
|
// Prevent navigation on double-click, let the double-click handler work
|
||||||
|
if (e.detail === 2 && navigationPath) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
handleClick(e as any);
|
||||||
|
};
|
||||||
|
|
||||||
|
const imageContainerClassName = clsx(styles.imageContainer, {
|
||||||
|
[styles.isRound]: isRound,
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageContainerContent = (
|
||||||
|
<>
|
||||||
|
<Image
|
||||||
|
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
||||||
|
src={imageUrl}
|
||||||
|
/>
|
||||||
|
<AnimatePresence>
|
||||||
|
{withControls && showControls && (
|
||||||
|
<ItemCardControls
|
||||||
|
controls={controls}
|
||||||
|
item={data}
|
||||||
|
itemType={itemType}
|
||||||
|
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, {
|
||||||
[styles.selected]: isSelected,
|
[styles.selected]: isSelected,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div
|
{navigationPath ? (
|
||||||
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}
|
<Link
|
||||||
onClick={handleClick}
|
className={imageContainerClassName}
|
||||||
onContextMenu={handleContextMenu}
|
onClick={handleImageClick}
|
||||||
onMouseEnter={handleMouseEnter}
|
onContextMenu={handleContextMenu}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseEnter={handleMouseEnter}
|
||||||
>
|
onMouseLeave={handleMouseLeave}
|
||||||
<Image
|
to={navigationPath}
|
||||||
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
>
|
||||||
src={imageUrl}
|
{imageContainerContent}
|
||||||
/>
|
</Link>
|
||||||
<AnimatePresence>
|
) : (
|
||||||
{withControls && showControls && (
|
<div
|
||||||
<ItemCardControls
|
className={imageContainerClassName}
|
||||||
controls={controls}
|
onClick={handleImageClick}
|
||||||
item={data}
|
onContextMenu={handleContextMenu}
|
||||||
itemType={itemType}
|
onMouseEnter={handleMouseEnter}
|
||||||
type="compact"
|
onMouseLeave={handleMouseLeave}
|
||||||
/>
|
>
|
||||||
)}
|
{imageContainerContent}
|
||||||
</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>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -325,6 +359,8 @@ const DefaultItemCard = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
|
const navigationPath = getItemNavigationPath(data, itemType);
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
if (withControls) {
|
if (withControls) {
|
||||||
setShowControls(true);
|
setShowControls(true);
|
||||||
@@ -337,7 +373,7 @@ const DefaultItemCard = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleContextMenu = (e: React.MouseEvent<HTMLDivElement>) => {
|
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
if (!data || !controls) {
|
if (!data || !controls) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -352,34 +388,65 @@ const DefaultItemCard = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleImageClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
|
// Prevent navigation on double-click, let the double-click handler work
|
||||||
|
if (e.detail === 2 && navigationPath) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
handleClick(e as any);
|
||||||
|
};
|
||||||
|
|
||||||
|
const imageContainerClassName = clsx(styles.imageContainer, {
|
||||||
|
[styles.isRound]: isRound,
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageContainerContent = (
|
||||||
|
<>
|
||||||
|
<Image
|
||||||
|
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
||||||
|
src={imageUrl}
|
||||||
|
/>
|
||||||
|
<AnimatePresence>
|
||||||
|
{withControls && showControls && (
|
||||||
|
<ItemCardControls
|
||||||
|
controls={controls}
|
||||||
|
item={data}
|
||||||
|
itemType={itemType}
|
||||||
|
type="default"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.container, {
|
className={clsx(styles.container, {
|
||||||
[styles.selected]: isSelected,
|
[styles.selected]: isSelected,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div
|
{navigationPath ? (
|
||||||
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}
|
<Link
|
||||||
onClick={handleClick}
|
className={imageContainerClassName}
|
||||||
onContextMenu={handleContextMenu}
|
onClick={handleImageClick}
|
||||||
onMouseEnter={handleMouseEnter}
|
onContextMenu={handleContextMenu}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseEnter={handleMouseEnter}
|
||||||
>
|
onMouseLeave={handleMouseLeave}
|
||||||
<Image
|
to={navigationPath}
|
||||||
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
>
|
||||||
src={imageUrl}
|
{imageContainerContent}
|
||||||
/>
|
</Link>
|
||||||
<AnimatePresence>
|
) : (
|
||||||
{withControls && showControls && (
|
<div
|
||||||
<ItemCardControls
|
className={imageContainerClassName}
|
||||||
controls={controls}
|
onClick={handleImageClick}
|
||||||
item={data}
|
onContextMenu={handleContextMenu}
|
||||||
itemType={itemType}
|
onMouseEnter={handleMouseEnter}
|
||||||
type="default"
|
onMouseLeave={handleMouseLeave}
|
||||||
/>
|
>
|
||||||
)}
|
{imageContainerContent}
|
||||||
</AnimatePresence>
|
</div>
|
||||||
</div>
|
)}
|
||||||
<div className={styles.detailContainer}>
|
<div className={styles.detailContainer}>
|
||||||
{rows
|
{rows
|
||||||
.filter(
|
.filter(
|
||||||
@@ -525,6 +592,8 @@ const PosterItemCard = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
|
const navigationPath = getItemNavigationPath(data, itemType);
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = () => {
|
||||||
if (withControls) {
|
if (withControls) {
|
||||||
setShowControls(true);
|
setShowControls(true);
|
||||||
@@ -537,7 +606,7 @@ const PosterItemCard = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleContextMenu = (e: React.MouseEvent<HTMLDivElement>) => {
|
const handleContextMenu = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
if (!data || !controls) {
|
if (!data || !controls) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -552,6 +621,38 @@ const PosterItemCard = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleImageClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||||
|
// Prevent navigation on double-click, let the double-click handler work
|
||||||
|
if (e.detail === 2 && navigationPath) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
handleClick(e as any);
|
||||||
|
};
|
||||||
|
|
||||||
|
const imageContainerClassName = clsx(styles.imageContainer, {
|
||||||
|
[styles.isRound]: isRound,
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageContainerContent = (
|
||||||
|
<>
|
||||||
|
<Image
|
||||||
|
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
||||||
|
src={imageUrl}
|
||||||
|
/>
|
||||||
|
<AnimatePresence>
|
||||||
|
{withControls && showControls && data && (
|
||||||
|
<ItemCardControls
|
||||||
|
controls={controls}
|
||||||
|
internalState={internalState}
|
||||||
|
item={data}
|
||||||
|
itemType={itemType}
|
||||||
|
type="poster"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.container, styles.poster, {
|
className={clsx(styles.container, styles.poster, {
|
||||||
@@ -560,29 +661,28 @@ const PosterItemCard = ({
|
|||||||
})}
|
})}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<div
|
{navigationPath ? (
|
||||||
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}
|
<Link
|
||||||
onClick={handleClick}
|
className={imageContainerClassName}
|
||||||
onContextMenu={handleContextMenu}
|
onClick={handleImageClick}
|
||||||
onMouseEnter={handleMouseEnter}
|
onContextMenu={handleContextMenu}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseEnter={handleMouseEnter}
|
||||||
>
|
onMouseLeave={handleMouseLeave}
|
||||||
<Image
|
to={navigationPath}
|
||||||
className={clsx(styles.image, { [styles.isRound]: isRound })}
|
>
|
||||||
src={imageUrl}
|
{imageContainerContent}
|
||||||
/>
|
</Link>
|
||||||
<AnimatePresence>
|
) : (
|
||||||
{withControls && showControls && data && (
|
<div
|
||||||
<ItemCardControls
|
className={imageContainerClassName}
|
||||||
controls={controls}
|
onClick={handleImageClick}
|
||||||
internalState={internalState}
|
onContextMenu={handleContextMenu}
|
||||||
item={data}
|
onMouseEnter={handleMouseEnter}
|
||||||
itemType={itemType}
|
onMouseLeave={handleMouseLeave}
|
||||||
type="poster"
|
>
|
||||||
/>
|
{imageContainerContent}
|
||||||
)}
|
</div>
|
||||||
</AnimatePresence>
|
)}
|
||||||
</div>
|
|
||||||
{data && (
|
{data && (
|
||||||
<div className={styles.detailContainer}>
|
<div className={styles.detailContainer}>
|
||||||
{rows
|
{rows
|
||||||
@@ -868,6 +968,20 @@ const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song | unde
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getItemNavigationPath = (
|
||||||
|
data: Album | AlbumArtist | Artist | Playlist | Song | undefined,
|
||||||
|
itemType: LibraryItem,
|
||||||
|
): null | string => {
|
||||||
|
if (!data || !('id' in data) || !data.id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if data has _itemType (like in title row logic)
|
||||||
|
const effectiveItemType = '_itemType' in data && data._itemType ? data._itemType : itemType;
|
||||||
|
|
||||||
|
return getTitlePath(effectiveItemType, data.id);
|
||||||
|
};
|
||||||
|
|
||||||
const ItemCardRow = ({
|
const ItemCardRow = ({
|
||||||
data,
|
data,
|
||||||
index,
|
index,
|
||||||
|
|||||||
Reference in New Issue
Block a user