From ff776293a6c998bc8a4bc61801945b13c6bde1f5 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Wed, 19 Nov 2025 22:28:03 -0800 Subject: [PATCH] add item card navigation --- .../components/item-card/item-card.module.css | 3 + .../components/item-card/item-card.tsx | 284 ++++++++++++------ 2 files changed, 202 insertions(+), 85 deletions(-) diff --git a/src/renderer/components/item-card/item-card.module.css b/src/renderer/components/item-card/item-card.module.css index 42949a30c..91a912098 100644 --- a/src/renderer/components/item-card/item-card.module.css +++ b/src/renderer/components/item-card/item-card.module.css @@ -21,9 +21,12 @@ .image-container { position: relative; + display: block; width: 100%; aspect-ratio: 1; overflow: hidden; + color: inherit; + text-decoration: none; &::before { position: absolute; diff --git a/src/renderer/components/item-card/item-card.tsx b/src/renderer/components/item-card/item-card.tsx index da130f8ca..936352463 100644 --- a/src/renderer/components/item-card/item-card.tsx +++ b/src/renderer/components/item-card/item-card.tsx @@ -8,6 +8,7 @@ import styles from './item-card.module.css'; import { ItemCardControls } from '/@/renderer/components/item-card/item-card-controls'; 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 { ItemControls } from '/@/renderer/components/item-list/types'; import { useDragDrop } from '/@/renderer/hooks/use-drag-drop'; @@ -170,6 +171,8 @@ const CompactItemCard = ({ }); if (data) { + const navigationPath = getItemNavigationPath(data, itemType); + const handleMouseEnter = () => { if (withControls) { setShowControls(true); @@ -182,7 +185,7 @@ const CompactItemCard = ({ } }; - const handleContextMenu = (e: React.MouseEvent) => { + const handleContextMenu = (e: React.MouseEvent) => { if (!data || !controls) { return; } @@ -197,50 +200,81 @@ const CompactItemCard = ({ }); }; + const handleImageClick = (e: React.MouseEvent) => { + // 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 = ( + <> + + + {withControls && showControls && ( + + )} + +
+ {rows + .filter( + (row): row is NonNullable => + row !== null && row !== undefined, + ) + .map((row, index) => ( + + ))} +
+ + ); + return (
-
- - - {withControls && showControls && ( - - )} - -
- {rows - .filter( - (row): row is NonNullable => - row !== null && row !== undefined, - ) - .map((row, index) => ( - - ))} + {navigationPath ? ( + + {imageContainerContent} + + ) : ( +
+ {imageContainerContent}
-
+ )}
); } @@ -325,6 +359,8 @@ const DefaultItemCard = ({ }); if (data) { + const navigationPath = getItemNavigationPath(data, itemType); + const handleMouseEnter = () => { if (withControls) { setShowControls(true); @@ -337,7 +373,7 @@ const DefaultItemCard = ({ } }; - const handleContextMenu = (e: React.MouseEvent) => { + const handleContextMenu = (e: React.MouseEvent) => { if (!data || !controls) { return; } @@ -352,34 +388,65 @@ const DefaultItemCard = ({ }); }; + const handleImageClick = (e: React.MouseEvent) => { + // 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 = ( + <> + + + {withControls && showControls && ( + + )} + + + ); + return (
-
- - - {withControls && showControls && ( - - )} - -
+ {navigationPath ? ( + + {imageContainerContent} + + ) : ( +
+ {imageContainerContent} +
+ )}
{rows .filter( @@ -525,6 +592,8 @@ const PosterItemCard = ({ }); if (data) { + const navigationPath = getItemNavigationPath(data, itemType); + const handleMouseEnter = () => { if (withControls) { setShowControls(true); @@ -537,7 +606,7 @@ const PosterItemCard = ({ } }; - const handleContextMenu = (e: React.MouseEvent) => { + const handleContextMenu = (e: React.MouseEvent) => { if (!data || !controls) { return; } @@ -552,6 +621,38 @@ const PosterItemCard = ({ }); }; + const handleImageClick = (e: React.MouseEvent) => { + // 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 = ( + <> + + + {withControls && showControls && data && ( + + )} + + + ); + return (
-
- - - {withControls && showControls && data && ( - - )} - -
+ {navigationPath ? ( + + {imageContainerContent} + + ) : ( +
+ {imageContainerContent} +
+ )} {data && (
{rows @@ -868,6 +968,20 @@ const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song | unde 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 = ({ data, index,