mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-19 09:54:18 +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';
|
import { ItemDetailListCellProps } from './types';
|
||||||
|
|
||||||
export const RowIndexColumn = ({ rowIndex }: ItemDetailListCellProps) =>
|
import { useIsCurrentSong } from '/@/renderer/features/player/hooks/use-is-current-song';
|
||||||
String((rowIndex ?? 0) + 1);
|
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 { 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';
|
import { ExplicitIndicator } from '/@/shared/components/explicit-indicator/explicit-indicator';
|
||||||
|
|
||||||
export const TitleArtistColumn = ({ song }: ItemDetailListCellProps) => (
|
export const TitleArtistColumn = ({ song }: ItemDetailListCellProps) => {
|
||||||
<>
|
const { isActive } = useIsCurrentSong(song);
|
||||||
<ExplicitIndicator explicitStatus={song.explicitStatus} />
|
|
||||||
{[song.name, song.artistName].filter(Boolean).join(' — ') ?? <> </>}
|
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 { 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';
|
import { ExplicitIndicator } from '/@/shared/components/explicit-indicator/explicit-indicator';
|
||||||
|
|
||||||
export const TitleColumn = ({ song }: ItemDetailListCellProps) => (
|
export const TitleColumn = ({ song }: ItemDetailListCellProps) => {
|
||||||
<>
|
const { isActive } = useIsCurrentSong(song);
|
||||||
<ExplicitIndicator explicitStatus={song.explicitStatus} />
|
|
||||||
{song.name ?? <> </>}
|
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 { 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';
|
import { ExplicitIndicator } from '/@/shared/components/explicit-indicator/explicit-indicator';
|
||||||
|
|
||||||
export const TitleCombinedColumn = ({ song }: ItemDetailListCellProps) => (
|
export const TitleCombinedColumn = ({ song }: ItemDetailListCellProps) => {
|
||||||
<>
|
const { isActive } = useIsCurrentSong(song);
|
||||||
<ExplicitIndicator explicitStatus={song.explicitStatus} />
|
|
||||||
{[song.name, song.artistName].filter(Boolean).join(' — ') ?? <> </>}
|
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 { ExplicitIndicator } from '/@/shared/components/explicit-indicator/explicit-indicator';
|
||||||
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
|
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
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 { 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;
|
const DEFAULT_ROW_HEIGHT = 300;
|
||||||
|
|
||||||
@@ -93,6 +94,7 @@ interface RowData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface TrackRowProps {
|
interface TrackRowProps {
|
||||||
|
albumSongs: Song[];
|
||||||
columns: ItemTableListColumnConfig[];
|
columns: ItemTableListColumnConfig[];
|
||||||
columnWidthPercents: number[];
|
columnWidthPercents: number[];
|
||||||
controls?: ItemControls;
|
controls?: ItemControls;
|
||||||
@@ -113,6 +115,7 @@ const textAlignFromAlign = (align: ItemTableListColumnConfig['align']) =>
|
|||||||
|
|
||||||
const TrackRow = memo(
|
const TrackRow = memo(
|
||||||
({
|
({
|
||||||
|
albumSongs,
|
||||||
columns,
|
columns,
|
||||||
columnWidthPercents,
|
columnWidthPercents,
|
||||||
controls,
|
controls,
|
||||||
@@ -139,6 +142,17 @@ const TrackRow = memo(
|
|||||||
const [isRowHovered, setIsRowHovered] = useState(false);
|
const [isRowHovered, setIsRowHovered] = useState(false);
|
||||||
const isSelected = useItemSelectionState(internalState, song.id);
|
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(
|
const handleRowClick = useCallback(
|
||||||
(e: React.MouseEvent) => {
|
(e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -223,6 +237,11 @@ const TrackRow = memo(
|
|||||||
[internalState, song],
|
[internalState, song],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleClick = useDoubleClick({
|
||||||
|
onDoubleClick: handleDoubleClick,
|
||||||
|
onSingleClick: handleRowClick,
|
||||||
|
});
|
||||||
|
|
||||||
const handleContextMenu = useCallback(
|
const handleContextMenu = useCallback(
|
||||||
(event: React.MouseEvent<HTMLDivElement>) => {
|
(event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
if (isSongsLoading || !controls?.onMore) return;
|
if (isSongsLoading || !controls?.onMore) return;
|
||||||
@@ -254,7 +273,7 @@ const TrackRow = memo(
|
|||||||
[styles.trackRowSizeLarge]: size === 'large',
|
[styles.trackRowSizeLarge]: size === 'large',
|
||||||
[styles.trackRowWithHorizontalBorder]: rowIndex > 0,
|
[styles.trackRowWithHorizontalBorder]: rowIndex > 0,
|
||||||
})}
|
})}
|
||||||
onClick={handleRowClick}
|
onClick={handleClick}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
onMouseEnter={() => setIsRowHovered(true)}
|
onMouseEnter={() => setIsRowHovered(true)}
|
||||||
onMouseLeave={() => setIsRowHovered(false)}
|
onMouseLeave={() => setIsRowHovered(false)}
|
||||||
@@ -653,6 +672,7 @@ const RowContent = memo(
|
|||||||
<div className={styles.tracksTable} role="table">
|
<div className={styles.tracksTable} role="table">
|
||||||
{songs.map((song, rowIndex) => (
|
{songs.map((song, rowIndex) => (
|
||||||
<TrackRow
|
<TrackRow
|
||||||
|
albumSongs={songItems ? (songItems as Song[]) : []}
|
||||||
columns={trackColumns}
|
columns={trackColumns}
|
||||||
columnWidthPercents={columnWidthPercents}
|
columnWidthPercents={columnWidthPercents}
|
||||||
controls={controls}
|
controls={controls}
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { usePlayerSong } from '/@/renderer/store';
|
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 currentSong = usePlayerSong();
|
||||||
|
|
||||||
const isActive = useMemo(() => {
|
const isActive = useMemo(() => {
|
||||||
return song._uniqueId === currentSong?._uniqueId;
|
const queueSong = song as QueueSong;
|
||||||
}, [song._uniqueId, currentSong?._uniqueId]);
|
|
||||||
|
if (queueSong._uniqueId != null && queueSong._uniqueId !== '') {
|
||||||
|
return queueSong._uniqueId === currentSong?._uniqueId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return song.id === currentSong?.id;
|
||||||
|
}, [song, currentSong?.id, currentSong?._uniqueId]);
|
||||||
|
|
||||||
return { isActive };
|
return { isActive };
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user