mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-11 14:53:47 +02:00
implement row playback controls for the detail list
This commit is contained in:
@@ -27,6 +27,7 @@ import { PlayCountColumn } from './play-count-column';
|
|||||||
import { RatingColumn } from './rating-column';
|
import { RatingColumn } from './rating-column';
|
||||||
import { ReleaseDateColumn } from './release-date-column';
|
import { ReleaseDateColumn } from './release-date-column';
|
||||||
import { RowIndexColumn } from './row-index-column';
|
import { RowIndexColumn } from './row-index-column';
|
||||||
|
import { ItemDetailRowPlayControlCell } from './row-play-control-cell';
|
||||||
import { SampleRateColumn } from './sample-rate-column';
|
import { SampleRateColumn } from './sample-rate-column';
|
||||||
import { SizeColumn } from './size-column';
|
import { SizeColumn } from './size-column';
|
||||||
import { TitleArtistColumn } from './title-artist-column';
|
import { TitleArtistColumn } from './title-artist-column';
|
||||||
@@ -111,6 +112,7 @@ export {
|
|||||||
GenreBadgeColumn,
|
GenreBadgeColumn,
|
||||||
GenreColumn,
|
GenreColumn,
|
||||||
ImageColumn,
|
ImageColumn,
|
||||||
|
ItemDetailRowPlayControlCell,
|
||||||
LastPlayedColumn,
|
LastPlayedColumn,
|
||||||
PathColumn,
|
PathColumn,
|
||||||
PlayCountColumn,
|
PlayCountColumn,
|
||||||
|
|||||||
@@ -1,23 +1,40 @@
|
|||||||
import styles from './row-index-column.module.css';
|
import styles from './row-index-column.module.css';
|
||||||
|
import { ItemDetailRowPlayControlCell } from './row-play-control-cell';
|
||||||
import { ItemDetailListCellProps } from './types';
|
import { ItemDetailListCellProps } from './types';
|
||||||
|
import { useDetailRowPlayControl } from './use-detail-row-play-control';
|
||||||
|
|
||||||
import { useIsCurrentSong } from '/@/renderer/features/player/hooks/use-is-current-song';
|
import { isRowPlayControlColumn } from '/@/renderer/components/item-list/helpers/get-row-play-control-column';
|
||||||
import { usePlayerStatus } from '/@/renderer/store';
|
|
||||||
import { Icon } from '/@/shared/components/icon/icon';
|
import { Icon } from '/@/shared/components/icon/icon';
|
||||||
import { PlayerStatus } from '/@/shared/types/types';
|
import { TableColumn } 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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const DefaultRowIndexColumn = ({ rowIndex }: ItemDetailListCellProps) => {
|
||||||
return <>{String((rowIndex ?? 0) + 1)}</>;
|
return <>{String((rowIndex ?? 0) + 1)}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const PlayableRowIndexColumn = (props: ItemDetailListCellProps) => {
|
||||||
|
const { handlePlay, isActive, isPlaying, showPlayControls } = useDetailRowPlayControl(props);
|
||||||
|
|
||||||
|
const indexContent = isActive ? (
|
||||||
|
<div className={styles.iconWrapper}>
|
||||||
|
<Icon fill="primary" icon={isPlaying ? 'mediaPlay' : 'mediaPause'} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
String((props.rowIndex ?? 0) + 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ItemDetailRowPlayControlCell
|
||||||
|
indexContent={indexContent}
|
||||||
|
onPlay={handlePlay}
|
||||||
|
showPlayControls={showPlayControls}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RowIndexColumn = (props: ItemDetailListCellProps) => {
|
||||||
|
if (!props.columns || !isRowPlayControlColumn(TableColumn.ROW_INDEX, props.columns)) {
|
||||||
|
return <DefaultRowIndexColumn {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <PlayableRowIndexColumn {...props} />;
|
||||||
|
};
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
.cell-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-target {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.index-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import styles from './row-play-control-cell.module.css';
|
||||||
|
|
||||||
|
import { ItemRowPlayControls } from '/@/renderer/features/shared/components/item-row-play-controls';
|
||||||
|
import { HoverCard } from '/@/shared/components/hover-card/hover-card';
|
||||||
|
import { Play } from '/@/shared/types/types';
|
||||||
|
|
||||||
|
export const ItemDetailRowPlayControlCell = ({
|
||||||
|
indexContent,
|
||||||
|
onPlay,
|
||||||
|
showPlayControls,
|
||||||
|
}: {
|
||||||
|
indexContent: ReactNode;
|
||||||
|
onPlay: (playType: Play) => void;
|
||||||
|
showPlayControls: boolean;
|
||||||
|
}) => {
|
||||||
|
if (!showPlayControls) {
|
||||||
|
return <>{indexContent}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.cellWrapper}>
|
||||||
|
<HoverCard openDelay={300} position="top" withArrow>
|
||||||
|
<HoverCard.Target>
|
||||||
|
<div className={styles.playTarget}>{indexContent}</div>
|
||||||
|
</HoverCard.Target>
|
||||||
|
<HoverCard.Dropdown onClick={(e) => e.stopPropagation()}>
|
||||||
|
<ItemRowPlayControls onPlay={onPlay} />
|
||||||
|
</HoverCard.Dropdown>
|
||||||
|
</HoverCard>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,7 +1,46 @@
|
|||||||
|
import { ItemDetailRowPlayControlCell } from './row-play-control-cell';
|
||||||
|
import styles from './row-play-control-cell.module.css';
|
||||||
import { ItemDetailListCellProps } from './types';
|
import { ItemDetailListCellProps } from './types';
|
||||||
|
import { useDetailRowPlayControl } from './use-detail-row-play-control';
|
||||||
|
|
||||||
export const TrackNumberColumn = ({ song }: ItemDetailListCellProps) => {
|
import { isRowPlayControlColumn } from '/@/renderer/components/item-list/helpers/get-row-play-control-column';
|
||||||
|
import { Icon } from '/@/shared/components/icon/icon';
|
||||||
|
import { TableColumn } from '/@/shared/types/types';
|
||||||
|
|
||||||
|
const formatTrackNumber = (song: ItemDetailListCellProps['song']) => {
|
||||||
const disc = song.discNumber ?? 1;
|
const disc = song.discNumber ?? 1;
|
||||||
const track = song.trackNumber.toString().padStart(2, '0');
|
const track = song.trackNumber.toString().padStart(2, '0');
|
||||||
return `${disc}-${track}`;
|
return `${disc}-${track}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DefaultTrackNumberColumn = ({ song }: ItemDetailListCellProps) => {
|
||||||
|
return <>{formatTrackNumber(song)}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PlayableTrackNumberColumn = (props: ItemDetailListCellProps) => {
|
||||||
|
const { handlePlay, isActive, isPlaying, showPlayControls } = useDetailRowPlayControl(props);
|
||||||
|
|
||||||
|
const indexContent = isActive ? (
|
||||||
|
<div className={styles.iconWrapper}>
|
||||||
|
<Icon fill="primary" icon={isPlaying ? 'mediaPlay' : 'mediaPause'} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
formatTrackNumber(props.song)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ItemDetailRowPlayControlCell
|
||||||
|
indexContent={indexContent}
|
||||||
|
onPlay={handlePlay}
|
||||||
|
showPlayControls={showPlayControls}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TrackNumberColumn = (props: ItemDetailListCellProps) => {
|
||||||
|
if (!props.columns || !isRowPlayControlColumn(TableColumn.TRACK_NUMBER, props.columns)) {
|
||||||
|
return <DefaultTrackNumberColumn {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <PlayableTrackNumberColumn {...props} />;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { ItemListStateActions } from '/@/renderer/components/item-list/helpers/item-list-state';
|
import { ItemListStateActions } from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { ItemControls } from '/@/renderer/components/item-list/types';
|
import { ItemControls, ItemTableListColumnConfig } from '/@/renderer/components/item-list/types';
|
||||||
import { Song } from '/@/shared/types/domain-types';
|
import { Song } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export interface ItemDetailListCellProps {
|
export interface ItemDetailListCellProps {
|
||||||
|
columns?: ItemTableListColumnConfig[];
|
||||||
controls?: ItemControls;
|
controls?: ItemControls;
|
||||||
internalState?: ItemListStateActions;
|
internalState?: ItemListStateActions;
|
||||||
isMutatingFavorite?: boolean;
|
isMutatingFavorite?: boolean;
|
||||||
|
|||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { ItemDetailListCellProps } from './types';
|
||||||
|
|
||||||
|
import { playSongFromItemListControl } from '/@/renderer/components/item-list/helpers/play-row-from-list';
|
||||||
|
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||||
|
import { useIsCurrentSong } from '/@/renderer/features/player/hooks/use-is-current-song';
|
||||||
|
import { usePlayerStatus } from '/@/renderer/store';
|
||||||
|
import { Song } from '/@/shared/types/domain-types';
|
||||||
|
import { Play, PlayerStatus } from '/@/shared/types/types';
|
||||||
|
|
||||||
|
export const useDetailRowPlayControl = ({
|
||||||
|
internalState,
|
||||||
|
rowIndex = 0,
|
||||||
|
song,
|
||||||
|
}: Pick<ItemDetailListCellProps, 'internalState' | 'rowIndex' | 'song'>) => {
|
||||||
|
const status = usePlayerStatus();
|
||||||
|
const player = usePlayer();
|
||||||
|
const { isActive } = useIsCurrentSong(song);
|
||||||
|
const isPlaying = isActive && status === PlayerStatus.PLAYING;
|
||||||
|
|
||||||
|
const showPlayControls = !!song?.id;
|
||||||
|
|
||||||
|
const handlePlay = useCallback(
|
||||||
|
(playType: Play) => {
|
||||||
|
if (!song) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
playSongFromItemListControl({
|
||||||
|
index: rowIndex,
|
||||||
|
internalState,
|
||||||
|
item: song as Song,
|
||||||
|
meta: { playType, singleSongOnly: true },
|
||||||
|
player,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[internalState, player, rowIndex, song],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handlePlay,
|
||||||
|
isActive,
|
||||||
|
isPlaying,
|
||||||
|
showPlayControls,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -447,6 +447,14 @@
|
|||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.row .track-cell-play-control {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
.track-row-dragging {
|
.track-row-dragging {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import styles from './item-detail-list.module.css';
|
|||||||
import { ItemCardControls } from '/@/renderer/components/item-card/item-card-controls';
|
import { ItemCardControls } from '/@/renderer/components/item-card/item-card-controls';
|
||||||
import { ItemImage } from '/@/renderer/components/item-image/item-image';
|
import { ItemImage } from '/@/renderer/components/item-image/item-image';
|
||||||
import { getDraggedItems } from '/@/renderer/components/item-list/helpers/get-dragged-items';
|
import { getDraggedItems } from '/@/renderer/components/item-list/helpers/get-dragged-items';
|
||||||
|
import { isRowPlayControlColumn } from '/@/renderer/components/item-list/helpers/get-row-play-control-column';
|
||||||
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
||||||
import {
|
import {
|
||||||
ItemListStateActions,
|
ItemListStateActions,
|
||||||
@@ -365,6 +366,7 @@ const TrackRow = memo(
|
|||||||
const isTitleColumn = col.id === TableColumn.TITLE;
|
const isTitleColumn = col.id === TableColumn.TITLE;
|
||||||
const isImageColumn = col.id === TableColumn.IMAGE;
|
const isImageColumn = col.id === TableColumn.IMAGE;
|
||||||
const isIconActionColumn = isNoHorizontalPaddingColumn(col.id);
|
const isIconActionColumn = isNoHorizontalPaddingColumn(col.id);
|
||||||
|
const isPlayControlColumn = isRowPlayControlColumn(col.id, columns);
|
||||||
const showHoverContent = shouldShowHoverOnlyColumnContent(
|
const showHoverContent = shouldShowHoverOnlyColumnContent(
|
||||||
col.id,
|
col.id,
|
||||||
isRowHovered,
|
isRowHovered,
|
||||||
@@ -374,6 +376,7 @@ const TrackRow = memo(
|
|||||||
const content = isSongsLoading ? null : showHoverContent ? (
|
const content = isSongsLoading ? null : showHoverContent ? (
|
||||||
<CellComponent
|
<CellComponent
|
||||||
columnId={col.id}
|
columnId={col.id}
|
||||||
|
columns={columns}
|
||||||
controls={controls}
|
controls={controls}
|
||||||
internalState={internalState}
|
internalState={internalState}
|
||||||
isMutatingFavorite={isMutatingFavorite}
|
isMutatingFavorite={isMutatingFavorite}
|
||||||
@@ -393,6 +396,7 @@ const TrackRow = memo(
|
|||||||
[styles.trackCellImage]: isImageColumn,
|
[styles.trackCellImage]: isImageColumn,
|
||||||
[styles.trackCellMuted]: !isTitleColumn,
|
[styles.trackCellMuted]: !isTitleColumn,
|
||||||
[styles.trackCellNoHPadding]: isIconActionColumn,
|
[styles.trackCellNoHPadding]: isIconActionColumn,
|
||||||
|
[styles.trackCellPlayControl]: isPlayControlColumn,
|
||||||
[styles.trackCellVerticalBorderVisible]:
|
[styles.trackCellVerticalBorderVisible]:
|
||||||
enableVerticalBorders && !isLastColumn,
|
enableVerticalBorders && !isLastColumn,
|
||||||
[styles.trackCellWithVerticalBorder]: !isLastColumn,
|
[styles.trackCellWithVerticalBorder]: !isLastColumn,
|
||||||
|
|||||||
Reference in New Issue
Block a user