add item card navigation

This commit is contained in:
jeffvli
2025-11-19 22:28:03 -08:00
parent b4b31bc6b4
commit 96d15929ba
2 changed files with 202 additions and 85 deletions
@@ -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;
+158 -44
View File
@@ -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,19 +200,20 @@ const CompactItemCard = ({
}); });
}; };
return ( const handleImageClick = (e: React.MouseEvent<HTMLElement>) => {
<div // Prevent navigation on double-click, let the double-click handler work
className={clsx(styles.container, styles.compact, { if (e.detail === 2 && navigationPath) {
[styles.selected]: isSelected, e.preventDefault();
})} }
> handleClick(e as any);
<div };
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}
onClick={handleClick} const imageContainerClassName = clsx(styles.imageContainer, {
onContextMenu={handleContextMenu} [styles.isRound]: isRound,
onMouseEnter={handleMouseEnter} });
onMouseLeave={handleMouseLeave}
> const imageContainerContent = (
<>
<Image <Image
className={clsx(styles.image, { [styles.isRound]: isRound })} className={clsx(styles.image, { [styles.isRound]: isRound })}
src={imageUrl} src={imageUrl}
@@ -240,7 +244,37 @@ const CompactItemCard = ({
/> />
))} ))}
</div> </div>
</>
);
return (
<div
className={clsx(styles.container, styles.compact, {
[styles.selected]: isSelected,
})}
>
{navigationPath ? (
<Link
className={imageContainerClassName}
onClick={handleImageClick}
onContextMenu={handleContextMenu}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
to={navigationPath}
>
{imageContainerContent}
</Link>
) : (
<div
className={imageContainerClassName}
onClick={handleImageClick}
onContextMenu={handleContextMenu}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{imageContainerContent}
</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,19 +388,20 @@ const DefaultItemCard = ({
}); });
}; };
return ( const handleImageClick = (e: React.MouseEvent<HTMLElement>) => {
<div // Prevent navigation on double-click, let the double-click handler work
className={clsx(styles.container, { if (e.detail === 2 && navigationPath) {
[styles.selected]: isSelected, e.preventDefault();
})} }
> handleClick(e as any);
<div };
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}
onClick={handleClick} const imageContainerClassName = clsx(styles.imageContainer, {
onContextMenu={handleContextMenu} [styles.isRound]: isRound,
onMouseEnter={handleMouseEnter} });
onMouseLeave={handleMouseLeave}
> const imageContainerContent = (
<>
<Image <Image
className={clsx(styles.image, { [styles.isRound]: isRound })} className={clsx(styles.image, { [styles.isRound]: isRound })}
src={imageUrl} src={imageUrl}
@@ -379,7 +416,37 @@ const DefaultItemCard = ({
/> />
)} )}
</AnimatePresence> </AnimatePresence>
</>
);
return (
<div
className={clsx(styles.container, {
[styles.selected]: isSelected,
})}
>
{navigationPath ? (
<Link
className={imageContainerClassName}
onClick={handleImageClick}
onContextMenu={handleContextMenu}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
to={navigationPath}
>
{imageContainerContent}
</Link>
) : (
<div
className={imageContainerClassName}
onClick={handleImageClick}
onContextMenu={handleContextMenu}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{imageContainerContent}
</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,21 +621,20 @@ const PosterItemCard = ({
}); });
}; };
return ( const handleImageClick = (e: React.MouseEvent<HTMLElement>) => {
<div // Prevent navigation on double-click, let the double-click handler work
className={clsx(styles.container, styles.poster, { if (e.detail === 2 && navigationPath) {
[styles.dragging]: isDragging, e.preventDefault();
[styles.selected]: isSelected, }
})} handleClick(e as any);
ref={ref} };
>
<div const imageContainerClassName = clsx(styles.imageContainer, {
className={clsx(styles.imageContainer, { [styles.isRound]: isRound })} [styles.isRound]: isRound,
onClick={handleClick} });
onContextMenu={handleContextMenu}
onMouseEnter={handleMouseEnter} const imageContainerContent = (
onMouseLeave={handleMouseLeave} <>
>
<Image <Image
className={clsx(styles.image, { [styles.isRound]: isRound })} className={clsx(styles.image, { [styles.isRound]: isRound })}
src={imageUrl} src={imageUrl}
@@ -582,7 +650,39 @@ const PosterItemCard = ({
/> />
)} )}
</AnimatePresence> </AnimatePresence>
</>
);
return (
<div
className={clsx(styles.container, styles.poster, {
[styles.dragging]: isDragging,
[styles.selected]: isSelected,
})}
ref={ref}
>
{navigationPath ? (
<Link
className={imageContainerClassName}
onClick={handleImageClick}
onContextMenu={handleContextMenu}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
to={navigationPath}
>
{imageContainerContent}
</Link>
) : (
<div
className={imageContainerClassName}
onClick={handleImageClick}
onContextMenu={handleContextMenu}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{imageContainerContent}
</div> </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,