mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-13 07:42:31 +02:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user