add double click play to album detail

- add mediaPlayByIndex
- add index property to item list controls args
- add overrides to item list controls
This commit is contained in:
jeffvli
2025-11-22 22:27:45 -08:00
parent 0aee428aaf
commit 646eb4a3b0
15 changed files with 131 additions and 22 deletions
@@ -16,6 +16,7 @@ interface UseDefaultItemListControlsArgs {
edge: 'bottom' | 'left' | 'right' | 'top' | null, edge: 'bottom' | 'left' | 'right' | 'top' | null,
) => void; ) => void;
onColumnResized?: (columnId: TableColumn, width: number) => void; onColumnResized?: (columnId: TableColumn, width: number) => void;
overrides?: Partial<ItemControls>;
} }
const itemTypeMapping = { const itemTypeMapping = {
@@ -32,7 +33,7 @@ export const useDefaultItemListControls = (args?: UseDefaultItemListControlsArgs
const player = usePlayer(); const player = usePlayer();
const navigate = useNavigate(); const navigate = useNavigate();
const { onColumnReordered, onColumnResized } = args || {}; const { onColumnReordered, onColumnResized, overrides } = args || {};
const controls: ItemControls = useMemo(() => { const controls: ItemControls = useMemo(() => {
return { return {
@@ -326,8 +327,10 @@ export const useDefaultItemListControls = (args?: UseDefaultItemListControlsArgs
player.setRating(item._serverId, [item.id], apiItemType, newRating); player.setRating(item._serverId, [item.id], apiItemType, newRating);
}, },
...overrides,
}; };
}, [onColumnReordered, onColumnResized, navigate, player]); }, [onColumnReordered, onColumnResized, overrides, navigate, player]);
return controls; return controls;
}; };
@@ -438,7 +438,9 @@ export const useItemListState = (
); );
const getData = useCallback(() => { const getData = useCallback(() => {
return getDataFn ? getDataFn() : []; const data = getDataFn ? getDataFn() : [];
// Filter out null/undefined values (e.g., group header rows)
return data.filter((d) => d != null);
}, [getDataFn]); }, [getDataFn]);
const findItemIndex = useCallback( const findItemIndex = useCallback(
@@ -255,6 +255,7 @@ export interface ItemGridListProps {
onRangeChanged?: (range: { startIndex: number; stopIndex: number }) => void; onRangeChanged?: (range: { startIndex: number; stopIndex: number }) => void;
onScroll?: (offset: number, direction: 'down' | 'up') => void; onScroll?: (offset: number, direction: 'down' | 'up') => void;
onScrollEnd?: (offset: number, direction: 'down' | 'up') => void; onScrollEnd?: (offset: number, direction: 'down' | 'up') => void;
overrideControls?: Partial<ItemControls>;
ref?: Ref<ItemListHandle>; ref?: Ref<ItemListHandle>;
rows?: ItemCardProps['rows']; rows?: ItemCardProps['rows'];
} }
@@ -272,6 +273,7 @@ export const ItemGridList = ({
onRangeChanged, onRangeChanged,
onScroll, onScroll,
onScrollEnd, onScrollEnd,
overrideControls,
ref, ref,
rows, rows,
}: ItemGridListProps) => { }: ItemGridListProps) => {
@@ -366,7 +368,7 @@ export const ItemGridList = ({
throttledSetTableMeta(containerWidth, data.length, setTableMeta); throttledSetTableMeta(containerWidth, data.length, setTableMeta);
}, [containerWidth, data.length, throttledSetTableMeta]); }, [containerWidth, data.length, throttledSetTableMeta]);
const controls = useDefaultItemListControls(); const controls = useDefaultItemListControls({ overrides: overrideControls });
const scrollToIndex = useCallback( const scrollToIndex = useCallback(
( (
@@ -12,10 +12,14 @@ export const ActionsColumn = (props: ItemTableListInnerColumn) => {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
if (row !== undefined) { if (row !== undefined) {
const item = row as ItemListItem;
const rowId = props.internalState.extractRowId(item);
const index = rowId ? props.internalState.findItemIndex(rowId) : -1;
props.controls.onMore?.({ props.controls.onMore?.({
event, event,
index,
internalState: props.internalState, internalState: props.internalState,
item: row as ItemListItem, item,
itemType: props.itemType, itemType: props.itemType,
}); });
} }
@@ -24,11 +24,15 @@ export const FavoriteColumn = (props: ItemTableListInnerColumn) => {
onClick={(event) => { onClick={(event) => {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
const item = props.data[props.rowIndex] as ItemListItem;
const rowId = props.internalState.extractRowId(item);
const index = rowId ? props.internalState.findItemIndex(rowId) : -1;
props.controls.onFavorite?.({ props.controls.onFavorite?.({
event, event,
favorite: !row, favorite: !row,
index,
internalState: props.internalState, internalState: props.internalState,
item: props.data[props.rowIndex] as ItemListItem, item,
itemType: props.itemType, itemType: props.itemType,
}); });
}} }}
@@ -16,10 +16,14 @@ export const RatingColumn = (props: ItemTableListInnerColumn) => {
<Rating <Rating
className={row ? undefined : 'hover-only-flex'} className={row ? undefined : 'hover-only-flex'}
onChange={(rating) => { onChange={(rating) => {
const item = props.data[props.rowIndex] as ItemListItem;
const rowId = props.internalState.extractRowId(item);
const index = rowId ? props.internalState.findItemIndex(rowId) : -1;
props.controls.onRating?.({ props.controls.onRating?.({
event: null, event: null,
index,
internalState: props.internalState, internalState: props.internalState,
item: props.data[props.rowIndex] as ItemListItem, item,
itemType: props.itemType, itemType: props.itemType,
rating, rating,
}); });
@@ -20,7 +20,9 @@ export const RowIndexColumn = (props: ItemTableListInnerColumn) => {
const { itemType } = props; const { itemType } = props;
switch (itemType) { switch (itemType) {
case LibraryItem.PLAYLIST_SONG:
case LibraryItem.QUEUE_SONG: case LibraryItem.QUEUE_SONG:
case LibraryItem.SONG:
return <QueueSongRowIndexColumn {...props} />; return <QueueSongRowIndexColumn {...props} />;
default: default:
return <DefaultRowIndexColumn {...props} />; return <DefaultRowIndexColumn {...props} />;
@@ -54,14 +56,18 @@ const DefaultRowIndexColumn = (props: ItemTableListInnerColumn) => {
className={clsx(styles.expand, 'hover-only')} className={clsx(styles.expand, 'hover-only')}
icon="arrowDownS" icon="arrowDownS"
iconProps={{ color: 'muted', size: 'md' }} iconProps={{ color: 'muted', size: 'md' }}
onClick={(e) => onClick={(e) => {
const item = data[rowIndex] as ItemListItem;
const rowId = internalState.extractRowId(item);
const index = rowId ? internalState.findItemIndex(rowId) : -1;
controls.onExpand?.({ controls.onExpand?.({
event: e, event: e,
index,
internalState, internalState,
item: data[rowIndex] as ItemListItem, item,
itemType, itemType,
}) });
} }}
size="xs" size="xs"
variant="subtle" variant="subtle"
/> />
@@ -78,7 +84,7 @@ const DefaultRowIndexColumn = (props: ItemTableListInnerColumn) => {
const QueueSongRowIndexColumn = (props: ItemTableListInnerColumn) => { const QueueSongRowIndexColumn = (props: ItemTableListInnerColumn) => {
const status = usePlayerStatus(); const status = usePlayerStatus();
const song = props.data[props.rowIndex] as QueueSong; const song = props.data[props.rowIndex] as QueueSong;
const isActive = props.activeRowId === song?._uniqueId; const isActive = props.activeRowId === song?.id || props.activeRowId === song?._uniqueId;
let adjustedRowIndex = let adjustedRowIndex =
props.adjustedRowIndexMap?.get(props.rowIndex) ?? props.adjustedRowIndexMap?.get(props.rowIndex) ??
@@ -17,7 +17,9 @@ export const TitleColumn = (props: ItemTableListInnerColumn) => {
const { itemType } = props; const { itemType } = props;
switch (itemType) { switch (itemType) {
case LibraryItem.PLAYLIST_SONG:
case LibraryItem.QUEUE_SONG: case LibraryItem.QUEUE_SONG:
case LibraryItem.SONG:
return <QueueSongTitleColumn {...props} />; return <QueueSongTitleColumn {...props} />;
default: default:
return <DefaultTitleColumn {...props} />; return <DefaultTitleColumn {...props} />;
@@ -72,7 +74,7 @@ function QueueSongTitleColumn(props: ItemTableListInnerColumn) {
]; ];
const song = props.data[props.rowIndex] as QueueSong; const song = props.data[props.rowIndex] as QueueSong;
const isActive = props.activeRowId === song?._uniqueId; const isActive = props.activeRowId === song?.id || props.activeRowId === song?._uniqueId;
if (typeof row === 'string') { if (typeof row === 'string') {
const path = getTitlePath(props.itemType, (props.data[props.rowIndex] as any).id as string); const path = getTitlePath(props.itemType, (props.data[props.rowIndex] as any).id as string);
@@ -88,7 +88,7 @@ export const QueueSongTitleCombinedColumn = (props: ItemTableListInnerColumn) =>
const row: object | undefined = (props.data as (any | undefined)[])[props.rowIndex]; const row: object | undefined = (props.data as (any | undefined)[])[props.rowIndex];
const song = props.data[props.rowIndex] as QueueSong; const song = props.data[props.rowIndex] as QueueSong;
const isActive = props.activeRowId === song?._uniqueId; const isActive = props.activeRowId === song?.id || props.activeRowId === song?._uniqueId;
const artists = useMemo(() => { const artists = useMemo(() => {
if (row && 'artists' in row && Array.isArray(row.artists)) { if (row && 'artists' in row && Array.isArray(row.artists)) {
@@ -167,7 +167,9 @@ export const TitleCombinedColumn = (props: ItemTableListInnerColumn) => {
const { itemType } = props; const { itemType } = props;
switch (itemType) { switch (itemType) {
case LibraryItem.PLAYLIST_SONG:
case LibraryItem.QUEUE_SONG: case LibraryItem.QUEUE_SONG:
case LibraryItem.SONG:
return <QueueSongTitleCombinedColumn {...props} />; return <QueueSongTitleCombinedColumn {...props} />;
default: default:
return <DefaultTitleCombinedColumn {...props} />; return <DefaultTitleCombinedColumn {...props} />;
@@ -547,8 +547,11 @@ export const TableColumnTextContainer = (
const handleClick = useDoubleClick({ const handleClick = useDoubleClick({
onDoubleClick: (event: React.MouseEvent<HTMLDivElement>) => { onDoubleClick: (event: React.MouseEvent<HTMLDivElement>) => {
if (isDataRow && item) { if (isDataRow && item) {
const rowId = props.internalState.extractRowId(item);
const index = rowId ? props.internalState.findItemIndex(rowId) : -1;
props.controls.onDoubleClick?.({ props.controls.onDoubleClick?.({
event, event,
index,
internalState: props.internalState, internalState: props.internalState,
item: item as ItemListItem, item: item as ItemListItem,
itemType: props.itemType, itemType: props.itemType,
@@ -567,8 +570,11 @@ export const TableColumnTextContainer = (
} }
if (isDataRow && item && props.enableSelection) { if (isDataRow && item && props.enableSelection) {
const rowId = props.internalState.extractRowId(item);
const index = rowId ? props.internalState.findItemIndex(rowId) : -1;
props.controls.onClick?.({ props.controls.onClick?.({
event, event,
index,
internalState: props.internalState, internalState: props.internalState,
item: item as ItemListItem, item: item as ItemListItem,
itemType: props.itemType, itemType: props.itemType,
@@ -580,8 +586,11 @@ export const TableColumnTextContainer = (
const handleContextMenu = (event: React.MouseEvent<HTMLDivElement>) => { const handleContextMenu = (event: React.MouseEvent<HTMLDivElement>) => {
if (isDataRow && item) { if (isDataRow && item) {
event.preventDefault(); event.preventDefault();
const rowId = props.internalState.extractRowId(item);
const index = rowId ? props.internalState.findItemIndex(rowId) : -1;
props.controls.onMore?.({ props.controls.onMore?.({
event, event,
index,
internalState: props.internalState, internalState: props.internalState,
item: item as ItemListItem, item: item as ItemListItem,
itemType: props.itemType, itemType: props.itemType,
@@ -750,8 +759,11 @@ export const TableColumnContainer = (
const handleClick = useDoubleClick({ const handleClick = useDoubleClick({
onDoubleClick: (event: React.MouseEvent<HTMLDivElement>) => { onDoubleClick: (event: React.MouseEvent<HTMLDivElement>) => {
if (isDataRow && item) { if (isDataRow && item) {
const rowId = props.internalState.extractRowId(item);
const index = rowId ? props.internalState.findItemIndex(rowId) : -1;
props.controls.onDoubleClick?.({ props.controls.onDoubleClick?.({
event, event,
index,
internalState: props.internalState, internalState: props.internalState,
item: item as ItemListItem, item: item as ItemListItem,
itemType: props.itemType, itemType: props.itemType,
@@ -770,8 +782,11 @@ export const TableColumnContainer = (
} }
if (isDataRow && item && props.enableSelection) { if (isDataRow && item && props.enableSelection) {
const rowId = props.internalState.extractRowId(item);
const index = rowId ? props.internalState.findItemIndex(rowId) : -1;
props.controls.onClick?.({ props.controls.onClick?.({
event, event,
index,
internalState: props.internalState, internalState: props.internalState,
item: item as ItemListItem, item: item as ItemListItem,
itemType: props.itemType, itemType: props.itemType,
@@ -783,8 +798,11 @@ export const TableColumnContainer = (
const handleContextMenu = (event: React.MouseEvent<HTMLDivElement>) => { const handleContextMenu = (event: React.MouseEvent<HTMLDivElement>) => {
if (isDataRow && item) { if (isDataRow && item) {
event.preventDefault(); event.preventDefault();
const rowId = props.internalState.extractRowId(item);
const index = rowId ? props.internalState.findItemIndex(rowId) : -1;
props.controls.onMore?.({ props.controls.onMore?.({
event, event,
index,
internalState: props.internalState, internalState: props.internalState,
item: item as ItemListItem, item: item as ItemListItem,
itemType: props.itemType, itemType: props.itemType,
@@ -697,6 +697,7 @@ interface ItemTableListProps {
onColumnResized?: (columnId: TableColumn, width: number) => void; onColumnResized?: (columnId: TableColumn, width: number) => void;
onRangeChanged?: (range: { startIndex: number; stopIndex: number }) => void; onRangeChanged?: (range: { startIndex: number; stopIndex: number }) => void;
onScrollEnd?: (offset: number, internalState: ItemListStateActions) => void; onScrollEnd?: (offset: number, internalState: ItemListStateActions) => void;
overrideControls?: Partial<ItemControls>;
ref?: Ref<ItemListHandle>; ref?: Ref<ItemListHandle>;
rowHeight?: ((index: number, cellProps: TableItemProps) => number) | number; rowHeight?: ((index: number, cellProps: TableItemProps) => number) | number;
size?: 'compact' | 'default' | 'large'; size?: 'compact' | 'default' | 'large';
@@ -729,6 +730,7 @@ export const ItemTableList = ({
onColumnResized, onColumnResized,
onRangeChanged, onRangeChanged,
onScrollEnd, onScrollEnd,
overrideControls,
ref, ref,
rowHeight, rowHeight,
size = 'default', size = 'default',
@@ -1759,6 +1761,7 @@ export const ItemTableList = ({
const controls = useDefaultItemListControls({ const controls = useDefaultItemListControls({
onColumnReordered, onColumnReordered,
onColumnResized, onColumnResized,
overrides: overrideControls,
}); });
// Create itemProps for sticky header // Create itemProps for sticky header
+10 -6
View File
@@ -11,13 +11,14 @@ import { Play, TableColumn } from '/@/shared/types/types';
export interface DefaultItemControlProps { export interface DefaultItemControlProps {
event: null | React.MouseEvent<unknown>; event: null | React.MouseEvent<unknown>;
index?: number;
internalState?: ItemListStateActions; internalState?: ItemListStateActions;
item: ItemListItem | undefined; item: ItemListItem | undefined;
itemType: LibraryItem; itemType: LibraryItem;
} }
export interface ItemControls { export interface ItemControls {
onClick?: ({ internalState, item, itemType }: DefaultItemControlProps) => void; onClick?: ({ index, internalState, item, itemType }: DefaultItemControlProps) => void;
onColumnReordered?: ({ onColumnReordered?: ({
columnIdFrom, columnIdFrom,
columnIdTo, columnIdTo,
@@ -25,24 +26,27 @@ export interface ItemControls {
}: { }: {
columnIdFrom: TableColumn; columnIdFrom: TableColumn;
columnIdTo: TableColumn; columnIdTo: TableColumn;
edge: 'top' | 'bottom' | 'left' | 'right' | null; edge: 'bottom' | 'left' | 'right' | 'top' | null;
}) => void; }) => void;
onColumnResized?: ({ columnId, width }: { columnId: TableColumn; width: number }) => void; onColumnResized?: ({ columnId, width }: { columnId: TableColumn; width: number }) => void;
onDoubleClick?: ({ internalState, item, itemType }: DefaultItemControlProps) => void; onDoubleClick?: ({ index, internalState, item, itemType }: DefaultItemControlProps) => void;
onExpand?: ({ internalState, item, itemType }: DefaultItemControlProps) => void; onExpand?: ({ index, internalState, item, itemType }: DefaultItemControlProps) => void;
onFavorite?: ({ onFavorite?: ({
index,
internalState, internalState,
item, item,
itemType, itemType,
}: DefaultItemControlProps & { favorite: boolean }) => void; }: DefaultItemControlProps & { favorite: boolean }) => void;
onMore?: ({ internalState, item, itemType }: DefaultItemControlProps) => void; onMore?: ({ index, internalState, item, itemType }: DefaultItemControlProps) => void;
onPlay?: ({ onPlay?: ({
index,
internalState, internalState,
item, item,
itemType, itemType,
playType, playType,
}: DefaultItemControlProps & { playType: Play }) => void; }: DefaultItemControlProps & { playType: Play }) => void;
onRating?: ({ onRating?: ({
index,
internalState, internalState,
item, item,
itemType, itemType,
@@ -66,7 +70,7 @@ export interface ItemListHandle {
internalState: ItemListStateActions; internalState: ItemListStateActions;
scrollToIndex: ( scrollToIndex: (
index: number, index: number,
options?: { align?: 'top' | 'bottom' | 'center'; behavior?: 'auto' | 'smooth' }, options?: { align?: 'bottom' | 'center' | 'top'; behavior?: 'auto' | 'smooth' },
) => void; ) => void;
scrollToOffset: (offset: number, options?: { behavior?: 'auto' | 'smooth' }) => void; scrollToOffset: (offset: number, options?: { behavior?: 'auto' | 'smooth' }) => void;
} }
@@ -10,14 +10,16 @@ import { useItemListColumnResize } from '/@/renderer/components/item-list/helper
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns'; import { SONG_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
import { ItemTableList } from '/@/renderer/components/item-list/item-table-list/item-table-list'; import { ItemTableList } from '/@/renderer/components/item-list/item-table-list/item-table-list';
import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column'; import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
import { ItemControls } from '/@/renderer/components/item-list/types';
import { albumQueries } from '/@/renderer/features/albums/api/album-api'; import { albumQueries } from '/@/renderer/features/albums/api/album-api';
import { AlbumInfiniteCarousel } from '/@/renderer/features/albums/components/album-infinite-carousel'; import { AlbumInfiniteCarousel } from '/@/renderer/features/albums/components/album-infinite-carousel';
import { usePlayer } from '/@/renderer/features/player/context/player-context';
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu'; import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
import { searchLibraryItems } from '/@/renderer/features/shared/utils'; import { searchLibraryItems } from '/@/renderer/features/shared/utils';
import { useContainerQuery } from '/@/renderer/hooks'; import { useContainerQuery } from '/@/renderer/hooks';
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route'; import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer } from '/@/renderer/store'; import { useCurrentServer, usePlayerSong } from '/@/renderer/store';
import { useGeneralSettings, useSettingsStore } from '/@/renderer/store/settings.store'; import { useGeneralSettings, useSettingsStore } from '/@/renderer/store/settings.store';
import { import {
formatDateAbsoluteUTC, formatDateAbsoluteUTC,
@@ -46,7 +48,7 @@ import {
Song, Song,
SortOrder, SortOrder,
} from '/@/shared/types/domain-types'; } from '/@/shared/types/domain-types';
import { ItemListKey, ListDisplayType } from '/@/shared/types/types'; import { ItemListKey, ListDisplayType, Play } from '/@/shared/types/types';
interface AlbumMetadataTagsProps { interface AlbumMetadataTagsProps {
album: Album | undefined; album: Album | undefined;
@@ -423,6 +425,8 @@ const AlbumDetailSongsTable = ({ songs }: AlbumDetailSongsTableProps) => {
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const tableConfig = useSettingsStore((state) => state.lists[ItemListKey.ALBUM_DETAIL]?.table); const tableConfig = useSettingsStore((state) => state.lists[ItemListKey.ALBUM_DETAIL]?.table);
const currentSong = usePlayerSong();
const columns = useMemo(() => { const columns = useMemo(() => {
return tableConfig?.columns || []; return tableConfig?.columns || [];
}, [tableConfig?.columns]); }, [tableConfig?.columns]);
@@ -560,10 +564,31 @@ const AlbumDetailSongsTable = ({ songs }: AlbumDetailSongsTableProps) => {
})); }));
}, [discGroups, t, searchTerm]); }, [discGroups, t, searchTerm]);
const player = usePlayer();
const overrideControls: Partial<ItemControls> = useMemo(() => {
return {
onDoubleClick: ({ index, internalState, item }) => {
if (!item) {
return;
}
const items = internalState?.getData() as Song[];
if (index !== undefined) {
player.addToQueueByData(items, Play.NOW);
player.mediaPlayByIndex(index);
}
},
};
}, [player]);
if (!tableConfig || columns.length === 0) { if (!tableConfig || columns.length === 0) {
return null; return null;
} }
const currentSongId = currentSong?.id;
return ( return (
<Stack gap="md"> <Stack gap="md">
<Group gap="sm" w="100%"> <Group gap="sm" w="100%">
@@ -604,6 +629,7 @@ const AlbumDetailSongsTable = ({ songs }: AlbumDetailSongsTableProps) => {
/> />
</Group> </Group>
<ItemTableList <ItemTableList
activeRowId={currentSongId}
autoFitColumns={tableConfig.autoFitColumns} autoFitColumns={tableConfig.autoFitColumns}
CellComponent={ItemTableListColumn} CellComponent={ItemTableListColumn}
columns={columns} columns={columns}
@@ -622,6 +648,7 @@ const AlbumDetailSongsTable = ({ songs }: AlbumDetailSongsTableProps) => {
itemType={LibraryItem.SONG} itemType={LibraryItem.SONG}
onColumnReordered={handleColumnReordered} onColumnReordered={handleColumnReordered}
onColumnResized={handleColumnResized} onColumnResized={handleColumnResized}
overrideControls={overrideControls}
size={tableConfig.size} size={tableConfig.size}
/> />
</Stack> </Stack>
@@ -54,6 +54,7 @@ export interface PlayerContext {
mediaNext: () => void; mediaNext: () => void;
mediaPause: () => void; mediaPause: () => void;
mediaPlay: (id?: string) => void; mediaPlay: (id?: string) => void;
mediaPlayByIndex: (index: number) => void;
mediaPrevious: () => void; mediaPrevious: () => void;
mediaSeekToTimestamp: (timestamp: number) => void; mediaSeekToTimestamp: (timestamp: number) => void;
mediaSkipBackward: () => void; mediaSkipBackward: () => void;
@@ -94,6 +95,7 @@ export const PlayerContext = createContext<PlayerContext>({
mediaNext: () => {}, mediaNext: () => {},
mediaPause: () => {}, mediaPause: () => {},
mediaPlay: () => {}, mediaPlay: () => {},
mediaPlayByIndex: () => {},
mediaPrevious: () => {}, mediaPrevious: () => {},
mediaSeekToTimestamp: () => {}, mediaSeekToTimestamp: () => {},
mediaSkipBackward: () => {}, mediaSkipBackward: () => {},
@@ -488,6 +490,13 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
[storeActions], [storeActions],
); );
const mediaPlayByIndex = useCallback(
(index: number) => {
storeActions.mediaPlayByIndex(index);
},
[storeActions],
);
const mediaPrevious = useCallback(() => { const mediaPrevious = useCallback(() => {
storeActions.mediaPrevious(); storeActions.mediaPrevious();
}, [storeActions]); }, [storeActions]);
@@ -642,6 +651,7 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
mediaNext, mediaNext,
mediaPause, mediaPause,
mediaPlay, mediaPlay,
mediaPlayByIndex,
mediaPrevious, mediaPrevious,
mediaSeekToTimestamp, mediaSeekToTimestamp,
mediaSkipBackward, mediaSkipBackward,
@@ -677,6 +687,7 @@ export const PlayerProvider = ({ children }: { children: React.ReactNode }) => {
mediaNext, mediaNext,
mediaPause, mediaPause,
mediaPlay, mediaPlay,
mediaPlayByIndex,
mediaPrevious, mediaPrevious,
mediaSeekToTimestamp, mediaSeekToTimestamp,
mediaSkipBackward, mediaSkipBackward,
+17
View File
@@ -45,6 +45,7 @@ interface Actions {
mediaNext: () => void; mediaNext: () => void;
mediaPause: () => void; mediaPause: () => void;
mediaPlay: (id?: string) => void; mediaPlay: (id?: string) => void;
mediaPlayByIndex: (index: number) => void;
mediaPrevious: () => void; mediaPrevious: () => void;
mediaSeekToTimestamp: (timestamp: number) => void; mediaSeekToTimestamp: (timestamp: number) => void;
mediaSkipBackward: (offset?: number) => void; mediaSkipBackward: (offset?: number) => void;
@@ -682,6 +683,21 @@ export const usePlayerStoreBase = create<PlayerState>()(
state.player.status = PlayerStatus.PLAYING; state.player.status = PlayerStatus.PLAYING;
}); });
}, },
mediaPlayByIndex: (index: number) => {
set((state) => {
const queue = state.getQueue();
if (index === -1 || index >= queue.items.length) {
state.player.status = PlayerStatus.PAUSED;
return;
}
state.player.index = index;
setTimestampStore(0);
state.player.status = PlayerStatus.PLAYING;
});
},
mediaPrevious: () => { mediaPrevious: () => {
const currentIndex = get().player.index; const currentIndex = get().player.index;
@@ -1266,6 +1282,7 @@ export const usePlayerActions = () => {
mediaNext: state.mediaNext, mediaNext: state.mediaNext,
mediaPause: state.mediaPause, mediaPause: state.mediaPause,
mediaPlay: state.mediaPlay, mediaPlay: state.mediaPlay,
mediaPlayByIndex: state.mediaPlayByIndex,
mediaPrevious: state.mediaPrevious, mediaPrevious: state.mediaPrevious,
mediaSeekToTimestamp: state.mediaSeekToTimestamp, mediaSeekToTimestamp: state.mediaSeekToTimestamp,
mediaSkipBackward: state.mediaSkipBackward, mediaSkipBackward: state.mediaSkipBackward,