mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-16 16:34:24 +02:00
add table row playback controls
- supports song, album, artist, and album artist tables - hovering over the first row index or track number column will display a hovercard for the playback controls
This commit is contained in:
@@ -12,6 +12,7 @@ import styles from './album-detail-content.module.css';
|
||||
|
||||
import { useGridCarouselContainerQuery } from '/@/renderer/components/grid-carousel/grid-carousel-v2';
|
||||
import { useItemListStateSubscription } from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||
import { playSongFromItemListControl } from '/@/renderer/components/item-list/helpers/play-row-from-list';
|
||||
import { useItemListColumnReorder } from '/@/renderer/components/item-list/helpers/use-item-list-column-reorder';
|
||||
import { useItemListColumnResize } from '/@/renderer/components/item-list/helpers/use-item-list-column-resize';
|
||||
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||
@@ -60,7 +61,7 @@ import {
|
||||
SongListSort,
|
||||
SortOrder,
|
||||
} from '/@/shared/types/domain-types';
|
||||
import { ItemListKey, ListDisplayType, Play } from '/@/shared/types/types';
|
||||
import { ItemListKey, ListDisplayType } from '/@/shared/types/types';
|
||||
|
||||
const MetadataPillGroup = ({
|
||||
items,
|
||||
@@ -830,13 +831,13 @@ const AlbumDetailSongsTable = ({ songs }: AlbumDetailSongsTableProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const playType = (meta?.playType as Play) || Play.NOW;
|
||||
|
||||
const items = internalState?.getData() as Song[];
|
||||
|
||||
if (index !== undefined) {
|
||||
player.addToQueueByData(items, playType, item.id);
|
||||
}
|
||||
playSongFromItemListControl({
|
||||
index,
|
||||
internalState,
|
||||
item: item as Song,
|
||||
meta,
|
||||
player,
|
||||
});
|
||||
},
|
||||
};
|
||||
}, [player]);
|
||||
|
||||
@@ -14,6 +14,7 @@ import styles from './album-artist-detail-content.module.css';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { DataRow, MemoizedItemCard } from '/@/renderer/components/item-card/item-card';
|
||||
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
||||
import { playSongFromItemListControl } from '/@/renderer/components/item-list/helpers/play-row-from-list';
|
||||
import { useGridRows } from '/@/renderer/components/item-list/helpers/use-grid-rows';
|
||||
import { useItemListColumnReorder } from '/@/renderer/components/item-list/helpers/use-item-list-column-reorder';
|
||||
import { useItemListColumnResize } from '/@/renderer/components/item-list/helpers/use-item-list-column-resize';
|
||||
@@ -365,12 +366,13 @@ const AlbumArtistMetadataTopSongsContent = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const playType = (meta?.playType as Play) || Play.NOW;
|
||||
const items = internalState?.getData() as Song[];
|
||||
|
||||
if (index !== undefined) {
|
||||
player.addToQueueByData(items, playType, item.id);
|
||||
}
|
||||
playSongFromItemListControl({
|
||||
index,
|
||||
internalState,
|
||||
item: item as Song,
|
||||
meta,
|
||||
player,
|
||||
});
|
||||
},
|
||||
};
|
||||
}, [player]);
|
||||
@@ -657,12 +659,13 @@ const AlbumArtistMetadataFavoriteSongs = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const playType = (meta?.playType as Play) || Play.NOW;
|
||||
const items = internalState?.getData() as Song[];
|
||||
|
||||
if (index !== undefined) {
|
||||
player.addToQueueByData(items, playType, item.id);
|
||||
}
|
||||
playSongFromItemListControl({
|
||||
index,
|
||||
internalState,
|
||||
item: item as Song,
|
||||
meta,
|
||||
player,
|
||||
});
|
||||
},
|
||||
};
|
||||
}, [player]);
|
||||
|
||||
+9
-7
@@ -2,6 +2,7 @@ import { useSuspenseQueries } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import { playSongFromItemListControl } from '/@/renderer/components/item-list/helpers/play-row-from-list';
|
||||
import { useItemListColumnReorder } from '/@/renderer/components/item-list/helpers/use-item-list-column-reorder';
|
||||
import { useItemListColumnResize } from '/@/renderer/components/item-list/helpers/use-item-list-column-resize';
|
||||
import { ItemTableList } from '/@/renderer/components/item-list/item-table-list/item-table-list';
|
||||
@@ -24,7 +25,7 @@ import { useCurrentServer } from '/@/renderer/store/auth.store';
|
||||
import { useSettingsStore } from '/@/renderer/store/settings.store';
|
||||
import { sortSongList } from '/@/shared/api/utils';
|
||||
import { LibraryItem, Song } from '/@/shared/types/domain-types';
|
||||
import { ItemListKey, Play } from '/@/shared/types/types';
|
||||
import { ItemListKey } from '/@/shared/types/types';
|
||||
|
||||
const AlbumArtistDetailFavoriteSongsListRoute = () => {
|
||||
const { albumArtistId, artistId } = useParams() as {
|
||||
@@ -96,12 +97,13 @@ const AlbumArtistDetailFavoriteSongsListRoute = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const playType = (meta?.playType as Play) || Play.NOW;
|
||||
const items = internalState?.getData() as Song[];
|
||||
|
||||
if (index !== undefined) {
|
||||
player.addToQueueByData(items, playType, item.id);
|
||||
}
|
||||
playSongFromItemListControl({
|
||||
index,
|
||||
internalState,
|
||||
item: item as Song,
|
||||
meta,
|
||||
player,
|
||||
});
|
||||
},
|
||||
};
|
||||
}, [player]);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import { playSongFromItemListControl } from '/@/renderer/components/item-list/helpers/play-row-from-list';
|
||||
import { useItemListColumnReorder } from '/@/renderer/components/item-list/helpers/use-item-list-column-reorder';
|
||||
import { useItemListColumnResize } from '/@/renderer/components/item-list/helpers/use-item-list-column-resize';
|
||||
import { ItemTableList } from '/@/renderer/components/item-list/item-table-list/item-table-list';
|
||||
@@ -18,7 +19,7 @@ import { useCurrentServer } from '/@/renderer/store/auth.store';
|
||||
import { useSettingsStore } from '/@/renderer/store/settings.store';
|
||||
import { useLocalStorage } from '/@/shared/hooks/use-local-storage';
|
||||
import { LibraryItem, Song } from '/@/shared/types/domain-types';
|
||||
import { ItemListKey, Play } from '/@/shared/types/types';
|
||||
import { ItemListKey } from '/@/shared/types/types';
|
||||
|
||||
const AlbumArtistDetailTopSongsListRoute = () => {
|
||||
const { albumArtistId, artistId } = useParams() as {
|
||||
@@ -78,12 +79,13 @@ const AlbumArtistDetailTopSongsListRoute = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const playType = (meta?.playType as Play) || Play.NOW;
|
||||
const items = internalState?.getData() as Song[];
|
||||
|
||||
if (index !== undefined) {
|
||||
player.addToQueueByData(items, playType, item.id);
|
||||
}
|
||||
playSongFromItemListControl({
|
||||
index,
|
||||
internalState,
|
||||
item: item as Song,
|
||||
meta,
|
||||
player,
|
||||
});
|
||||
},
|
||||
};
|
||||
}, [player]);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { playSongFromItemListControl } from '/@/renderer/components/item-list/helpers/play-row-from-list';
|
||||
import { useItemListColumnReorder } from '/@/renderer/components/item-list/helpers/use-item-list-column-reorder';
|
||||
import { useItemListColumnResize } from '/@/renderer/components/item-list/helpers/use-item-list-column-resize';
|
||||
import { useItemListScrollPersist } from '/@/renderer/components/item-list/helpers/use-item-list-scroll-persist';
|
||||
@@ -21,7 +22,7 @@ import {
|
||||
PlaylistSongListResponse,
|
||||
Song,
|
||||
} from '/@/shared/types/domain-types';
|
||||
import { ItemListKey, Play, TableColumn } from '/@/shared/types/types';
|
||||
import { ItemListKey, TableColumn } from '/@/shared/types/types';
|
||||
|
||||
interface PlaylistDetailSongListTableProps extends Omit<
|
||||
ItemListTableComponentProps<PlaylistSongListQuery>,
|
||||
@@ -103,12 +104,13 @@ export const PlaylistDetailSongListTable = forwardRef<any, PlaylistDetailSongLis
|
||||
return;
|
||||
}
|
||||
|
||||
const playType = (meta?.playType as Play) || Play.NOW;
|
||||
const items = internalState?.getData() as Song[];
|
||||
|
||||
if (index !== undefined) {
|
||||
player.addToQueueByData(items, playType, item.id);
|
||||
}
|
||||
playSongFromItemListControl({
|
||||
index,
|
||||
internalState,
|
||||
item: item as Song,
|
||||
meta,
|
||||
player,
|
||||
});
|
||||
},
|
||||
};
|
||||
}, [player]);
|
||||
@@ -228,12 +230,13 @@ export const PlaylistDetailSongListEditTable = forwardRef<any, PlaylistDetailSon
|
||||
return;
|
||||
}
|
||||
|
||||
const playType = (meta?.playType as Play) || Play.NOW;
|
||||
const items = internalState?.getData() as Song[];
|
||||
|
||||
if (index !== undefined) {
|
||||
player.addToQueueByData(items, playType, item.id);
|
||||
}
|
||||
playSongFromItemListControl({
|
||||
index,
|
||||
internalState,
|
||||
item: item as Song,
|
||||
meta,
|
||||
player,
|
||||
});
|
||||
},
|
||||
};
|
||||
}, [player]);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
gap: 0;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import clsx from 'clsx';
|
||||
|
||||
import styles from './item-row-play-controls.module.css';
|
||||
|
||||
import {
|
||||
LONG_PRESS_PLAY_BEHAVIOR,
|
||||
PlayTooltip,
|
||||
} from '/@/renderer/features/shared/components/play-button-group';
|
||||
import { usePlayButtonClick } from '/@/renderer/features/shared/hooks/use-play-button-click';
|
||||
import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Play } from '/@/shared/types/types';
|
||||
|
||||
interface ItemRowPlayControlsProps {
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
onPlay: (playType: Play) => void;
|
||||
}
|
||||
|
||||
export const ItemRowPlayControls = ({ className, disabled, onPlay }: ItemRowPlayControlsProps) => {
|
||||
const handlePlayNext = usePlayButtonClick({
|
||||
onClick: () => {
|
||||
onPlay(Play.NEXT);
|
||||
},
|
||||
onLongPress: () => {
|
||||
onPlay(LONG_PRESS_PLAY_BEHAVIOR[Play.NEXT]);
|
||||
},
|
||||
});
|
||||
|
||||
const handlePlayNow = usePlayButtonClick({
|
||||
onClick: () => {
|
||||
onPlay(Play.NOW);
|
||||
},
|
||||
onLongPress: () => {
|
||||
onPlay(LONG_PRESS_PLAY_BEHAVIOR[Play.NOW]);
|
||||
},
|
||||
});
|
||||
|
||||
const handlePlayLast = usePlayButtonClick({
|
||||
onClick: () => {
|
||||
onPlay(Play.LAST);
|
||||
},
|
||||
onLongPress: () => {
|
||||
onPlay(LONG_PRESS_PLAY_BEHAVIOR[Play.LAST]);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ActionIconGroup className={clsx(styles.controls, className)}>
|
||||
<PlayTooltip disabled={disabled} type={Play.NOW}>
|
||||
<ActionIcon
|
||||
icon="mediaPlay"
|
||||
iconProps={{
|
||||
size: 'md',
|
||||
}}
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
{...handlePlayNow.handlers}
|
||||
{...handlePlayNow.props}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
<PlayTooltip disabled={disabled} type={Play.NEXT}>
|
||||
<ActionIcon
|
||||
icon="mediaPlayNext"
|
||||
iconProps={{
|
||||
size: 'md',
|
||||
}}
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
{...handlePlayNext.handlers}
|
||||
{...handlePlayNext.props}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
<PlayTooltip disabled={disabled} type={Play.LAST}>
|
||||
<ActionIcon
|
||||
icon="mediaPlayLast"
|
||||
iconProps={{
|
||||
size: 'md',
|
||||
}}
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
{...handlePlayLast.handlers}
|
||||
{...handlePlayLast.props}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
</ActionIconGroup>
|
||||
);
|
||||
};
|
||||
@@ -14,11 +14,7 @@ import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
|
||||
import { openCreatePlaylistModal } from '/@/renderer/features/playlists/components/create-playlist-form';
|
||||
import { useIsMutatingSidebarPlaylistFolderMove } from '/@/renderer/features/playlists/mutations/sidebar-playlist-folder-move-mutation';
|
||||
import {
|
||||
LONG_PRESS_PLAY_BEHAVIOR,
|
||||
PlayTooltip,
|
||||
} from '/@/renderer/features/shared/components/play-button-group';
|
||||
import { usePlayButtonClick } from '/@/renderer/features/shared/hooks/use-play-button-click';
|
||||
import { ItemRowPlayControls } from '/@/renderer/features/shared/components/item-row-play-controls';
|
||||
import {
|
||||
collectFolderPaths,
|
||||
PlaylistFolderDragExpandProvider,
|
||||
@@ -41,7 +37,7 @@ import {
|
||||
} from '/@/renderer/store';
|
||||
import { formatDurationString } from '/@/renderer/utils';
|
||||
import { Accordion } from '/@/shared/components/accordion/accordion';
|
||||
import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { animationProps } from '/@/shared/components/animations/animation-props';
|
||||
import { animationVariants } from '/@/shared/components/animations/animation-variants';
|
||||
import { ButtonProps } from '/@/shared/components/button/button';
|
||||
@@ -299,7 +295,12 @@ export const PlaylistRowButton = memo(
|
||||
<Text className={styles.compactName} fw={500} size="md">
|
||||
{name}
|
||||
</Text>
|
||||
{isHovered && <RowControls id={to} onPlay={handlePlay} variant="compact" />}
|
||||
{isHovered && (
|
||||
<ItemRowPlayControls
|
||||
className={clsx(styles.controls, styles.controlsCompact)}
|
||||
onPlay={(playType) => handlePlay(to, playType)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -347,7 +348,12 @@ export const PlaylistRowButton = memo(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isHovered && <RowControls id={to} onPlay={handlePlay} />}
|
||||
{isHovered && (
|
||||
<ItemRowPlayControls
|
||||
className={styles.controls}
|
||||
onPlay={(playType) => handlePlay(to, playType)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</MotionLink>
|
||||
@@ -355,88 +361,6 @@ export const PlaylistRowButton = memo(
|
||||
},
|
||||
);
|
||||
|
||||
const RowControls = ({
|
||||
id,
|
||||
onPlay,
|
||||
variant = 'expanded',
|
||||
}: {
|
||||
id: string;
|
||||
onPlay: (id: string, playType: Play) => void;
|
||||
variant?: 'compact' | 'expanded';
|
||||
}) => {
|
||||
const handlePlayNext = usePlayButtonClick({
|
||||
onClick: () => {
|
||||
onPlay(id, Play.NEXT);
|
||||
},
|
||||
onLongPress: () => {
|
||||
onPlay(id, LONG_PRESS_PLAY_BEHAVIOR[Play.NEXT]);
|
||||
},
|
||||
});
|
||||
|
||||
const handlePlayNow = usePlayButtonClick({
|
||||
onClick: () => {
|
||||
onPlay(id, Play.NOW);
|
||||
},
|
||||
onLongPress: () => {
|
||||
onPlay(id, LONG_PRESS_PLAY_BEHAVIOR[Play.NOW]);
|
||||
},
|
||||
});
|
||||
|
||||
const handlePlayLast = usePlayButtonClick({
|
||||
onClick: () => {
|
||||
onPlay(id, Play.LAST);
|
||||
},
|
||||
onLongPress: () => {
|
||||
onPlay(id, LONG_PRESS_PLAY_BEHAVIOR[Play.LAST]);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ActionIconGroup
|
||||
className={clsx(styles.controls, {
|
||||
[styles.controlsCompact]: variant === 'compact',
|
||||
})}
|
||||
>
|
||||
<PlayTooltip type={Play.NOW}>
|
||||
<ActionIcon
|
||||
icon="mediaPlay"
|
||||
iconProps={{
|
||||
size: 'md',
|
||||
}}
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
{...handlePlayNow.handlers}
|
||||
{...handlePlayNow.props}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
<PlayTooltip type={Play.NEXT}>
|
||||
<ActionIcon
|
||||
icon="mediaPlayNext"
|
||||
iconProps={{
|
||||
size: 'md',
|
||||
}}
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
{...handlePlayNext.handlers}
|
||||
{...handlePlayNext.props}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
<PlayTooltip type={Play.LAST}>
|
||||
<ActionIcon
|
||||
icon="mediaPlayLast"
|
||||
iconProps={{
|
||||
size: 'md',
|
||||
}}
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
{...handlePlayLast.handlers}
|
||||
{...handlePlayLast.props}
|
||||
/>
|
||||
</PlayTooltip>
|
||||
</ActionIconGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const SidebarPlaylistList = () => {
|
||||
const player = usePlayer();
|
||||
const { t } = useTranslation();
|
||||
|
||||
Reference in New Issue
Block a user