diff --git a/src/renderer/components/item-list/item-detail-list/columns/row-index-column.module.css b/src/renderer/components/item-list/item-detail-list/columns/row-index-column.module.css new file mode 100644 index 000000000..3b0048ae7 --- /dev/null +++ b/src/renderer/components/item-list/item-detail-list/columns/row-index-column.module.css @@ -0,0 +1,5 @@ +.icon-wrapper { + display: flex; + align-items: center; + justify-content: center; +} diff --git a/src/renderer/components/item-list/item-detail-list/columns/row-index-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/row-index-column.tsx index b96e40116..817f0acec 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/row-index-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/row-index-column.tsx @@ -1,4 +1,23 @@ +import styles from './row-index-column.module.css'; import { ItemDetailListCellProps } from './types'; -export const RowIndexColumn = ({ rowIndex }: ItemDetailListCellProps) => - String((rowIndex ?? 0) + 1); +import { useIsCurrentSong } from '/@/renderer/features/player/hooks/use-is-current-song'; +import { usePlayerStatus } from '/@/renderer/store'; +import { Icon } from '/@/shared/components/icon/icon'; +import { PlayerStatus } from '/@/shared/types/types'; + +export const RowIndexColumn = ({ rowIndex, song }: ItemDetailListCellProps) => { + const status = usePlayerStatus(); + const { isActive } = useIsCurrentSong(song); + const isPlaying = isActive && status === PlayerStatus.PLAYING; + + if (isActive) { + return ( +
+ +
+ ); + } + + return <>{String((rowIndex ?? 0) + 1)}; +}; diff --git a/src/renderer/components/item-list/item-detail-list/columns/title-artist-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/title-artist-column.tsx index 0c16a5791..bfd60def1 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/title-artist-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/title-artist-column.tsx @@ -1,9 +1,18 @@ +import clsx from 'clsx'; + +import styles from './title-column.module.css'; + import { ItemDetailListCellProps } from '/@/renderer/components/item-list/item-detail-list/columns/types'; +import { useIsCurrentSong } from '/@/renderer/features/player/hooks/use-is-current-song'; import { ExplicitIndicator } from '/@/shared/components/explicit-indicator/explicit-indicator'; -export const TitleArtistColumn = ({ song }: ItemDetailListCellProps) => ( - <> - - {[song.name, song.artistName].filter(Boolean).join(' — ') ?? <> } - -); +export const TitleArtistColumn = ({ song }: ItemDetailListCellProps) => { + const { isActive } = useIsCurrentSong(song); + + return ( + + + {[song.name, song.artistName].filter(Boolean).join(' — ') ?? <> } + + ); +}; diff --git a/src/renderer/components/item-list/item-detail-list/columns/title-column.module.css b/src/renderer/components/item-list/item-detail-list/columns/title-column.module.css new file mode 100644 index 000000000..9b1d26b7a --- /dev/null +++ b/src/renderer/components/item-list/item-detail-list/columns/title-column.module.css @@ -0,0 +1,3 @@ +.active { + color: var(--theme-colors-primary); +} diff --git a/src/renderer/components/item-list/item-detail-list/columns/title-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/title-column.tsx index e4d588488..c46697fc8 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/title-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/title-column.tsx @@ -1,9 +1,18 @@ +import clsx from 'clsx'; + +import styles from './title-column.module.css'; + import { ItemDetailListCellProps } from '/@/renderer/components/item-list/item-detail-list/columns/types'; +import { useIsCurrentSong } from '/@/renderer/features/player/hooks/use-is-current-song'; import { ExplicitIndicator } from '/@/shared/components/explicit-indicator/explicit-indicator'; -export const TitleColumn = ({ song }: ItemDetailListCellProps) => ( - <> - - {song.name ?? <> } - -); +export const TitleColumn = ({ song }: ItemDetailListCellProps) => { + const { isActive } = useIsCurrentSong(song); + + return ( + + + {song.name ?? <> } + + ); +}; diff --git a/src/renderer/components/item-list/item-detail-list/columns/title-combined-column.tsx b/src/renderer/components/item-list/item-detail-list/columns/title-combined-column.tsx index 4d2701947..17e6de49c 100644 --- a/src/renderer/components/item-list/item-detail-list/columns/title-combined-column.tsx +++ b/src/renderer/components/item-list/item-detail-list/columns/title-combined-column.tsx @@ -1,9 +1,18 @@ +import clsx from 'clsx'; + +import styles from './title-column.module.css'; + import { ItemDetailListCellProps } from '/@/renderer/components/item-list/item-detail-list/columns/types'; +import { useIsCurrentSong } from '/@/renderer/features/player/hooks/use-is-current-song'; import { ExplicitIndicator } from '/@/shared/components/explicit-indicator/explicit-indicator'; -export const TitleCombinedColumn = ({ song }: ItemDetailListCellProps) => ( - <> - - {[song.name, song.artistName].filter(Boolean).join(' — ') ?? <> } - -); +export const TitleCombinedColumn = ({ song }: ItemDetailListCellProps) => { + const { isActive } = useIsCurrentSong(song); + + return ( + + + {[song.name, song.artistName].filter(Boolean).join(' — ') ?? <> } + + ); +}; diff --git a/src/renderer/components/item-list/item-detail-list/item-detail.tsx b/src/renderer/components/item-list/item-detail-list/item-detail.tsx index dcf86a9b7..3f2f34bae 100644 --- a/src/renderer/components/item-list/item-detail-list/item-detail.tsx +++ b/src/renderer/components/item-list/item-detail-list/item-detail.tsx @@ -56,8 +56,9 @@ import { formatDateAbsoluteUTC, formatDurationString } from '/@/renderer/utils'; import { ExplicitIndicator } from '/@/shared/components/explicit-indicator/explicit-indicator'; import { Skeleton } from '/@/shared/components/skeleton/skeleton'; import { Text } from '/@/shared/components/text/text'; +import { useDoubleClick } from '/@/shared/hooks/use-double-click'; import { Album, LibraryItem, Song, SongListSort, SortOrder } from '/@/shared/types/domain-types'; -import { ItemListKey, TableColumn } from '/@/shared/types/types'; +import { ItemListKey, Play, TableColumn } from '/@/shared/types/types'; const DEFAULT_ROW_HEIGHT = 300; @@ -93,6 +94,7 @@ interface RowData { } interface TrackRowProps { + albumSongs: Song[]; columns: ItemTableListColumnConfig[]; columnWidthPercents: number[]; controls?: ItemControls; @@ -113,6 +115,7 @@ const textAlignFromAlign = (align: ItemTableListColumnConfig['align']) => const TrackRow = memo( ({ + albumSongs, columns, columnWidthPercents, controls, @@ -139,6 +142,17 @@ const TrackRow = memo( const [isRowHovered, setIsRowHovered] = useState(false); const isSelected = useItemSelectionState(internalState, song.id); + const handleDoubleClick = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (isSongsLoading || albumSongs.length === 0) return; + internalState.setSelected([song]); + playerContext.addToQueueByData(albumSongs, Play.NOW, song.id); + }, + [albumSongs, internalState, isSongsLoading, playerContext, song], + ); + const handleRowClick = useCallback( (e: React.MouseEvent) => { e.preventDefault(); @@ -223,6 +237,11 @@ const TrackRow = memo( [internalState, song], ); + const handleClick = useDoubleClick({ + onDoubleClick: handleDoubleClick, + onSingleClick: handleRowClick, + }); + const handleContextMenu = useCallback( (event: React.MouseEvent) => { if (isSongsLoading || !controls?.onMore) return; @@ -254,7 +273,7 @@ const TrackRow = memo( [styles.trackRowSizeLarge]: size === 'large', [styles.trackRowWithHorizontalBorder]: rowIndex > 0, })} - onClick={handleRowClick} + onClick={handleClick} onContextMenu={handleContextMenu} onMouseEnter={() => setIsRowHovered(true)} onMouseLeave={() => setIsRowHovered(false)} @@ -653,6 +672,7 @@ const RowContent = memo(
{songs.map((song, rowIndex) => ( { +export const useIsCurrentSong = (song: QueueSong | Song) => { const currentSong = usePlayerSong(); const isActive = useMemo(() => { - return song._uniqueId === currentSong?._uniqueId; - }, [song._uniqueId, currentSong?._uniqueId]); + const queueSong = song as QueueSong; + + if (queueSong._uniqueId != null && queueSong._uniqueId !== '') { + return queueSong._uniqueId === currentSong?._uniqueId; + } + + return song.id === currentSong?.id; + }, [song, currentSong?.id, currentSong?._uniqueId]); return { isActive }; };