mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
add double click handler / current song highlights
This commit is contained in:
@@ -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(' — ') ?? <> </>}
|
||||
</>
|
||||
);
|
||||
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(' — ') ?? <> </>}
|
||||
</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 ?? <> </>}
|
||||
</>
|
||||
);
|
||||
export const TitleColumn = ({ song }: ItemDetailListCellProps) => {
|
||||
const { isActive } = useIsCurrentSong(song);
|
||||
|
||||
return (
|
||||
<span className={clsx({ [styles.active]: isActive })}>
|
||||
<ExplicitIndicator explicitStatus={song.explicitStatus} />
|
||||
{song.name ?? <> </>}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
+15
-6
@@ -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(' — ') ?? <> </>}
|
||||
</>
|
||||
);
|
||||
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(' — ') ?? <> </>}
|
||||
</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 };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user