add double click handler / current song highlights

This commit is contained in:
jeffvli
2026-02-09 15:24:05 -08:00
parent 78d6e5b1d1
commit c9223c402a
8 changed files with 106 additions and 26 deletions
@@ -0,0 +1,5 @@
.icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
@@ -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 (
<div className={styles.iconWrapper}>
<Icon fill="primary" icon={isPlaying ? 'mediaPlay' : 'mediaPause'} />
</div>
);
}
return <>{String((rowIndex ?? 0) + 1)}</>;
};
@@ -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) => (
<>
<ExplicitIndicator explicitStatus={song.explicitStatus} />
{[song.name, song.artistName].filter(Boolean).join(' — ') ?? <>&nbsp;</>}
</>
);
export const TitleArtistColumn = ({ song }: ItemDetailListCellProps) => {
const { isActive } = useIsCurrentSong(song);
return (
<span className={clsx({ [styles.active]: isActive })}>
<ExplicitIndicator explicitStatus={song.explicitStatus} />
{[song.name, song.artistName].filter(Boolean).join(' — ') ?? <>&nbsp;</>}
</span>
);
};
@@ -0,0 +1,3 @@
.active {
color: var(--theme-colors-primary);
}
@@ -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) => (
<>
<ExplicitIndicator explicitStatus={song.explicitStatus} />
{song.name ?? <>&nbsp;</>}
</>
);
export const TitleColumn = ({ song }: ItemDetailListCellProps) => {
const { isActive } = useIsCurrentSong(song);
return (
<span className={clsx({ [styles.active]: isActive })}>
<ExplicitIndicator explicitStatus={song.explicitStatus} />
{song.name ?? <>&nbsp;</>}
</span>
);
};
@@ -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) => (
<>
<ExplicitIndicator explicitStatus={song.explicitStatus} />
{[song.name, song.artistName].filter(Boolean).join(' — ') ?? <>&nbsp;</>}
</>
);
export const TitleCombinedColumn = ({ song }: ItemDetailListCellProps) => {
const { isActive } = useIsCurrentSong(song);
return (
<span className={clsx({ [styles.active]: isActive })}>
<ExplicitIndicator explicitStatus={song.explicitStatus} />
{[song.name, song.artistName].filter(Boolean).join(' — ') ?? <>&nbsp;</>}
</span>
);
};
@@ -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<HTMLDivElement>) => {
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(
<div className={styles.tracksTable} role="table">
{songs.map((song, rowIndex) => (
<TrackRow
albumSongs={songItems ? (songItems as Song[]) : []}
columns={trackColumns}
columnWidthPercents={columnWidthPercents}
controls={controls}
@@ -1,14 +1,20 @@
import { useMemo } from 'react';
import { usePlayerSong } from '/@/renderer/store';
import { QueueSong } from '/@/shared/types/domain-types';
import { QueueSong, Song } from '/@/shared/types/domain-types';
export const useIsCurrentSong = (song: QueueSong) => {
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 };
};