diff --git a/src/renderer/components/item-list/item-table-list/columns/image-column.tsx b/src/renderer/components/item-list/item-table-list/columns/image-column.tsx index b381c3c03..93a40bf5d 100644 --- a/src/renderer/components/item-list/item-table-list/columns/image-column.tsx +++ b/src/renderer/components/item-list/item-table-list/columns/image-column.tsx @@ -93,7 +93,10 @@ export const ImageColumn = (props: ItemTableListInnerColumn) => { [styles.compactPlayButtonOverlay]: props.size === 'compact', })} > - + handlePlay(playButtonBehavior, e)} diff --git a/src/renderer/components/item-list/item-table-list/columns/title-combined-column.module.css b/src/renderer/components/item-list/item-table-list/columns/title-combined-column.module.css index 57608c1c2..8f5067aaa 100644 --- a/src/renderer/components/item-list/item-table-list/columns/title-combined-column.module.css +++ b/src/renderer/components/item-list/item-table-list/columns/title-combined-column.module.css @@ -18,6 +18,9 @@ } .title { + display: inline-block; + width: fit-content; + max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -37,3 +40,33 @@ color: black; fill: rgb(255 215 100); } + +.image-container { + position: relative; + width: 100%; + height: 100%; +} + +.play-button-overlay { + position: absolute; + top: 50%; + left: 50%; + z-index: 10; + opacity: 0.6; + transform: translate(-50%, -50%); + transition: opacity 0.2s ease-in-out; + + &:hover { + opacity: 1; + } +} + +.play-button-overlay button { + width: 32px; + height: 32px; +} + +.compact-play-button-overlay button { + width: 24px; + height: 24px; +} diff --git a/src/renderer/components/item-list/item-table-list/columns/title-combined-column.tsx b/src/renderer/components/item-list/item-table-list/columns/title-combined-column.tsx index f94802044..3ef13be57 100644 --- a/src/renderer/components/item-list/item-table-list/columns/title-combined-column.tsx +++ b/src/renderer/components/item-list/item-table-list/columns/title-combined-column.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import { CSSProperties, useMemo } from 'react'; +import { CSSProperties, useMemo, useState } from 'react'; import { generatePath, Link } from 'react-router'; import styles from './title-combined-column.module.css'; @@ -11,14 +11,67 @@ import { ItemTableListInnerColumn, TableColumnContainer, } from '/@/renderer/components/item-list/item-table-list/item-table-list-column'; +import { PlayButton } from '/@/renderer/features/shared/components/play-button'; +import { + LONG_PRESS_PLAY_BEHAVIOR, + PlayTooltip, +} from '/@/renderer/features/shared/components/play-button-group'; import { AppRoute } from '/@/renderer/router/routes'; +import { usePlayButtonBehavior } from '/@/renderer/store'; import { Icon } from '/@/shared/components/icon/icon'; import { Image } from '/@/shared/components/image/image'; import { Text } from '/@/shared/components/text/text'; import { Folder, LibraryItem, QueueSong, RelatedAlbumArtist } from '/@/shared/types/domain-types'; +import { Play } from '/@/shared/types/types'; export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => { const row: object | undefined = (props.data as (any | undefined)[])[props.rowIndex]; + const item = props.data[props.rowIndex] as any; + const internalState = (props as any).internalState; + const playButtonBehavior = usePlayButtonBehavior(); + const [isHovered, setIsHovered] = useState(false); + + const handlePlay = (playType: Play, event: React.MouseEvent) => { + if (!item) { + return; + } + + // For SONG items, use double click behavior + if ( + (props.itemType === LibraryItem.SONG || + props.itemType === LibraryItem.PLAYLIST_SONG || + item._itemType === LibraryItem.SONG) && + props.controls?.onDoubleClick + ) { + // Calculate the index based on rowIndex, accounting for header if enabled + const isHeaderEnabled = !!props.enableHeader; + const index = isHeaderEnabled ? props.rowIndex - 1 : props.rowIndex; + + props.controls.onDoubleClick({ + event: null, + index, + internalState, + item, + itemType: props.itemType, + meta: { + playType, + }, + }); + return; + } + + // For other item types, use regular onPlay + if (!props.controls?.onPlay) { + return; + } + + props.controls.onPlay({ + event, + item, + itemType: props.itemType, + playType, + }); + }; const artists = useMemo(() => { if (row && 'artists' in row && Array.isArray(row.artists)) { @@ -52,8 +105,38 @@ export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => { containerStyle={{ '--row-height': `${rowHeight}px` } as CSSProperties} {...props} > - -
+
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + + {isHovered && ( +
+ + handlePlay(playButtonBehavior, e)} + onLongPress={(e) => + handlePlay(LONG_PRESS_PLAY_BEHAVIOR[playButtonBehavior], e) + } + /> + +
+ )} +
+
{row.name as string} @@ -91,10 +174,56 @@ export const QueueSongTitleCombinedColumn = (props: ItemTableListInnerColumn) => const row: object | undefined = (props.data as (any | undefined)[])[props.rowIndex]; const song = props.data[props.rowIndex] as QueueSong; + const item = props.data[props.rowIndex] as any; + const internalState = (props as any).internalState; + const playButtonBehavior = usePlayButtonBehavior(); + const [isHovered, setIsHovered] = useState(false); const isActive = !!props.activeRowId && (props.activeRowId === song?.id || props.activeRowId === song?._uniqueId); + const handlePlay = (playType: Play, event: React.MouseEvent) => { + if (!item) { + return; + } + + // For SONG items, use double click behavior + if ( + (props.itemType === LibraryItem.SONG || + props.itemType === LibraryItem.PLAYLIST_SONG || + item._itemType === LibraryItem.SONG) && + props.controls?.onDoubleClick + ) { + // Calculate the index based on rowIndex, accounting for header if enabled + const isHeaderEnabled = !!props.enableHeader; + const index = isHeaderEnabled ? props.rowIndex - 1 : props.rowIndex; + + props.controls.onDoubleClick({ + event: null, + index, + internalState, + item, + itemType: props.itemType, + meta: { + playType, + }, + }); + return; + } + + // For other item types, use regular onPlay + if (!props.controls?.onPlay) { + return; + } + + props.controls.onPlay({ + event, + item, + itemType: props.itemType, + playType, + }); + }; + const artists = useMemo(() => { if (row && 'artists' in row && Array.isArray(row.artists)) { return (row.artists as RelatedAlbumArtist[]).map((artist) => { @@ -129,7 +258,33 @@ export const QueueSongTitleCombinedColumn = (props: ItemTableListInnerColumn) => containerStyle={{ '--row-height': `${rowHeight}px` } as CSSProperties} {...props} > - +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + + {isHovered && ( +
+ + handlePlay(playButtonBehavior, e)} + onLongPress={(e) => + handlePlay(LONG_PRESS_PLAY_BEHAVIOR[playButtonBehavior], e) + } + /> + +
+ )} +
{ +export const PlayTooltip = ({ + children, + disabled, + type, +}: { + children: React.ReactNode; + disabled?: boolean; + type: Play; +}) => { return ( - }> + } + > {children} );