mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
add column resize / reorder dragging
This commit is contained in:
@@ -192,9 +192,10 @@ export const useDefaultItemListControls = (args?: UseDefaultItemListControlsArgs
|
|||||||
onColumnReordered?.(columnIdFrom, columnIdTo, edge);
|
onColumnReordered?.(columnIdFrom, columnIdTo, edge);
|
||||||
},
|
},
|
||||||
|
|
||||||
onColumnResized: ({ columnId, width }: { columnId: TableColumn; width: number }) => {
|
onColumnResized: onColumnResized
|
||||||
onColumnResized?.(columnId, width);
|
? ({ columnId, width }: { columnId: TableColumn; width: number }) =>
|
||||||
},
|
onColumnResized(columnId, width)
|
||||||
|
: undefined,
|
||||||
|
|
||||||
onDoubleClick: ({ internalState, item, itemType, meta }: DefaultItemControlProps) => {
|
onDoubleClick: ({ internalState, item, itemType, meta }: DefaultItemControlProps) => {
|
||||||
if (!item || !internalState) {
|
if (!item || !internalState) {
|
||||||
|
|||||||
@@ -78,14 +78,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.track-header-cell {
|
.track-header-cell {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding-right: var(--theme-spacing-sm);
|
padding-right: var(--theme-spacing-sm);
|
||||||
padding-left: var(--theme-spacing-sm);
|
padding-left: var(--theme-spacing-sm);
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-header-cell-no-h-padding {
|
.track-header-cell-no-h-padding {
|
||||||
@@ -97,6 +96,89 @@
|
|||||||
border-right: 1px solid var(--theme-colors-border);
|
border-right: 1px solid var(--theme-colors-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.track-header-cell-dragging {
|
||||||
|
cursor: grabbing;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-header-cell-dragged-over-left::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
width: 3px;
|
||||||
|
content: '';
|
||||||
|
background-color: var(--theme-colors-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-header-cell-dragged-over-right::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
|
width: 3px;
|
||||||
|
content: '';
|
||||||
|
background-color: var(--theme-colors-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-header-cell:hover .resize-handle {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-header-cell:hover .resize-handle::before {
|
||||||
|
background-color: var(--theme-colors-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
|
width: 2px;
|
||||||
|
margin-right: -4px;
|
||||||
|
cursor: col-resize;
|
||||||
|
background: var(--theme-colors-border);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .resize-handle::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 2px;
|
||||||
|
content: '';
|
||||||
|
background-color: transparent;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
} */
|
||||||
|
|
||||||
|
.resize-handle-left {
|
||||||
|
left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle-left::before {
|
||||||
|
right: auto;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle-right {
|
||||||
|
right: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle-dragging {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 240px 1fr;
|
grid-template-columns: 240px 1fr;
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
import {
|
||||||
|
attachClosestEdge,
|
||||||
|
type Edge,
|
||||||
|
extractClosestEdge,
|
||||||
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
||||||
|
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
||||||
|
import {
|
||||||
|
draggable,
|
||||||
|
dropTargetForElements,
|
||||||
|
} from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
||||||
|
import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
@@ -58,6 +69,7 @@ import { ExplicitIndicator } from '/@/shared/components/explicit-indicator/expli
|
|||||||
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
|
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
|
||||||
import { useDoubleClick } from '/@/shared/hooks/use-double-click';
|
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 { dndUtils, DragData, DragOperation, DragTarget } from '/@/shared/types/drag-and-drop';
|
||||||
import { ItemListKey, Play, TableColumn } from '/@/shared/types/types';
|
import { ItemListKey, Play, TableColumn } from '/@/shared/types/types';
|
||||||
|
|
||||||
const DEFAULT_ROW_HEIGHT = 300;
|
const DEFAULT_ROW_HEIGHT = 300;
|
||||||
@@ -72,10 +84,17 @@ interface ItemDetailListProps {
|
|||||||
internalState?: ItemListStateActions;
|
internalState?: ItemListStateActions;
|
||||||
itemCount?: number;
|
itemCount?: number;
|
||||||
items?: unknown[];
|
items?: unknown[];
|
||||||
|
onColumnReordered?: (
|
||||||
|
columnIdFrom: TableColumn,
|
||||||
|
columnIdTo: TableColumn,
|
||||||
|
edge: 'bottom' | 'left' | 'right' | 'top' | null,
|
||||||
|
) => void;
|
||||||
|
onColumnResized?: (columnId: TableColumn, width: number) => void;
|
||||||
onRangeChanged?: (range: { startIndex: number; stopIndex: number }) => Promise<void> | void;
|
onRangeChanged?: (range: { startIndex: number; stopIndex: number }) => Promise<void> | void;
|
||||||
onScrollEnd?: (rowIndex: number) => void;
|
onScrollEnd?: (rowIndex: number) => void;
|
||||||
rowHeight?: number;
|
rowHeight?: number;
|
||||||
scrollOffset?: number;
|
scrollOffset?: number;
|
||||||
|
tableId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RowData {
|
interface RowData {
|
||||||
@@ -731,10 +750,259 @@ const RowComponent = memo((props: RowComponentProps<RowData>): ReactElement => {
|
|||||||
|
|
||||||
RowComponent.displayName = 'ItemDetailRow';
|
RowComponent.displayName = 'ItemDetailRow';
|
||||||
|
|
||||||
|
interface DetailListHeaderCellProps {
|
||||||
|
columnId: TableColumn;
|
||||||
|
columnWidthPercents: number[];
|
||||||
|
enableColumnResize?: boolean;
|
||||||
|
enableVerticalBorders: boolean;
|
||||||
|
isLastColumn: boolean;
|
||||||
|
onColumnReordered?: (args: {
|
||||||
|
columnIdFrom: TableColumn;
|
||||||
|
columnIdTo: TableColumn;
|
||||||
|
edge: Edge | null;
|
||||||
|
}) => void;
|
||||||
|
onColumnResized?: (columnId: TableColumn, width: number) => void;
|
||||||
|
tableId: string;
|
||||||
|
trackColumns: ItemTableListColumnConfig[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const DetailListHeaderCell = memo(
|
||||||
|
({
|
||||||
|
columnId,
|
||||||
|
columnWidthPercents,
|
||||||
|
enableColumnResize,
|
||||||
|
enableVerticalBorders,
|
||||||
|
isLastColumn,
|
||||||
|
onColumnReordered,
|
||||||
|
onColumnResized,
|
||||||
|
tableId,
|
||||||
|
trackColumns,
|
||||||
|
}: DetailListHeaderCellProps) => {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
const [isDraggedOver, setIsDraggedOver] = useState<Edge | null>(null);
|
||||||
|
const colIndex = trackColumns.findIndex((c) => c.id === columnId);
|
||||||
|
const col = colIndex >= 0 ? trackColumns[colIndex] : null;
|
||||||
|
const percent = col ? (columnWidthPercents[colIndex] ?? 0) : 0;
|
||||||
|
const { fixedWidth, isFixedColumn } = getTrackColumnFixed(columnId);
|
||||||
|
const currentWidth = col?.width ?? (fixedWidth || 100);
|
||||||
|
const showResizeHandle =
|
||||||
|
enableColumnResize && !isFixedColumn && !col?.autoSize && onColumnResized;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current || !onColumnReordered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReorder = (
|
||||||
|
columnIdFrom: TableColumn,
|
||||||
|
columnIdTo: TableColumn,
|
||||||
|
edge: Edge | null,
|
||||||
|
) => {
|
||||||
|
onColumnReordered({ columnIdFrom, columnIdTo, edge });
|
||||||
|
};
|
||||||
|
|
||||||
|
return combine(
|
||||||
|
draggable({
|
||||||
|
element: containerRef.current,
|
||||||
|
getInitialData: () => {
|
||||||
|
const data = dndUtils.generateDragData(
|
||||||
|
{
|
||||||
|
id: [columnId],
|
||||||
|
operation: [DragOperation.REORDER],
|
||||||
|
type: DragTarget.TABLE_COLUMN,
|
||||||
|
},
|
||||||
|
{ tableId },
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onDragStart: () => setIsDragging(true),
|
||||||
|
onDrop: () => setIsDragging(false),
|
||||||
|
onGenerateDragPreview: (data) => {
|
||||||
|
disableNativeDragPreview({ nativeSetDragImage: data.nativeSetDragImage });
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
dropTargetForElements({
|
||||||
|
canDrop: (args) => {
|
||||||
|
const data = args.source.data as unknown as DragData;
|
||||||
|
const sourceTableId = (data.metadata as { tableId?: string })?.tableId;
|
||||||
|
const isSelf = (args.source.data.id as string[])[0] === columnId;
|
||||||
|
const isSameTable = sourceTableId === tableId;
|
||||||
|
return (
|
||||||
|
dndUtils.isDropTarget(data.type, [DragTarget.TABLE_COLUMN]) &&
|
||||||
|
!isSelf &&
|
||||||
|
isSameTable
|
||||||
|
);
|
||||||
|
},
|
||||||
|
element: containerRef.current,
|
||||||
|
getData: ({ element, input }) => {
|
||||||
|
const data = dndUtils.generateDragData(
|
||||||
|
{
|
||||||
|
id: [columnId],
|
||||||
|
operation: [DragOperation.REORDER],
|
||||||
|
type: DragTarget.TABLE_COLUMN,
|
||||||
|
},
|
||||||
|
{ tableId },
|
||||||
|
);
|
||||||
|
return attachClosestEdge(data, {
|
||||||
|
allowedEdges: ['left', 'right'],
|
||||||
|
element,
|
||||||
|
input,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onDrag: (args) => {
|
||||||
|
const closestEdgeOfTarget = extractClosestEdge(args.self.data);
|
||||||
|
setIsDraggedOver(closestEdgeOfTarget);
|
||||||
|
},
|
||||||
|
onDragLeave: () => setIsDraggedOver(null),
|
||||||
|
onDrop: (args) => {
|
||||||
|
const closestEdgeOfTarget = extractClosestEdge(args.self.data);
|
||||||
|
const from = args.source.data.id as string[];
|
||||||
|
const to = args.self.data.id as string[];
|
||||||
|
|
||||||
|
handleReorder(
|
||||||
|
from[0] as TableColumn,
|
||||||
|
to[0] as TableColumn,
|
||||||
|
closestEdgeOfTarget,
|
||||||
|
);
|
||||||
|
setIsDraggedOver(null);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}, [columnId, onColumnReordered, tableId]);
|
||||||
|
|
||||||
|
const style: React.CSSProperties = {
|
||||||
|
flex: isFixedColumn ? `0 0 ${fixedWidth}px` : `${percent} 1 0`,
|
||||||
|
justifyContent: colTypeToJustifyContentMap[col?.align ?? 'start'],
|
||||||
|
minWidth: isFixedColumn ? fixedWidth : 0,
|
||||||
|
textAlign: colTypeToAlignMap[col?.align ?? 'start'] as 'center' | 'left' | 'right',
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = useCallback(
|
||||||
|
(id: TableColumn, width: number) => {
|
||||||
|
onColumnResized?.(id, width);
|
||||||
|
},
|
||||||
|
[onColumnResized],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(styles.trackHeaderCell, {
|
||||||
|
[styles.trackHeaderCellDraggedOverLeft]: isDraggedOver === 'left',
|
||||||
|
[styles.trackHeaderCellDraggedOverRight]: isDraggedOver === 'right',
|
||||||
|
[styles.trackHeaderCellDragging]: isDragging,
|
||||||
|
[styles.trackHeaderCellNoHPadding]: isNoHorizontalPaddingColumn(columnId),
|
||||||
|
[styles.trackHeaderCellWithVerticalBorder]:
|
||||||
|
enableVerticalBorders && !isLastColumn,
|
||||||
|
})}
|
||||||
|
ref={containerRef}
|
||||||
|
role="columnheader"
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{columnLabelMap[columnId] ?? ''}
|
||||||
|
{showResizeHandle && (
|
||||||
|
<DetailListColumnResizeHandle
|
||||||
|
columnId={columnId}
|
||||||
|
initialWidth={currentWidth}
|
||||||
|
onResize={handleResize}
|
||||||
|
side="right"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
DetailListHeaderCell.displayName = 'DetailListHeaderCell';
|
||||||
|
|
||||||
|
interface DetailListColumnResizeHandleProps {
|
||||||
|
columnId: TableColumn;
|
||||||
|
initialWidth: number;
|
||||||
|
onResize: (columnId: TableColumn, width: number) => void;
|
||||||
|
side: 'left' | 'right';
|
||||||
|
}
|
||||||
|
|
||||||
|
const DetailListColumnResizeHandle = ({
|
||||||
|
columnId,
|
||||||
|
initialWidth,
|
||||||
|
onResize,
|
||||||
|
side,
|
||||||
|
}: DetailListColumnResizeHandleProps) => {
|
||||||
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
const handleRef = useRef<HTMLDivElement>(null);
|
||||||
|
const startWidthRef = useRef<number>(initialWidth);
|
||||||
|
const startXRef = useRef<number>(0);
|
||||||
|
const finalWidthRef = useRef<number>(initialWidth);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isDragging) {
|
||||||
|
startWidthRef.current = initialWidth;
|
||||||
|
}
|
||||||
|
}, [initialWidth, isDragging]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isDragging) return;
|
||||||
|
|
||||||
|
const handleMouseMove = (event: MouseEvent) => {
|
||||||
|
const deltaX = event.clientX - startXRef.current;
|
||||||
|
const newWidth = Math.min(Math.max(10, startWidthRef.current + deltaX), 1000);
|
||||||
|
finalWidthRef.current = newWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
setIsDragging(false);
|
||||||
|
document.body.style.cursor = '';
|
||||||
|
document.body.style.userSelect = '';
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp);
|
||||||
|
onResize(columnId, finalWidthRef.current);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
|
document.addEventListener('mouseup', handleMouseUp);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp);
|
||||||
|
};
|
||||||
|
}, [isDragging, columnId, onResize]);
|
||||||
|
|
||||||
|
const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
setIsDragging(true);
|
||||||
|
startWidthRef.current = initialWidth;
|
||||||
|
startXRef.current = event.clientX;
|
||||||
|
document.body.style.cursor = 'col-resize';
|
||||||
|
document.body.style.userSelect = 'none';
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(styles.resizeHandle, {
|
||||||
|
[styles.resizeHandleDragging]: isDragging,
|
||||||
|
[styles.resizeHandleLeft]: side === 'left',
|
||||||
|
[styles.resizeHandleRight]: side === 'right',
|
||||||
|
})}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
ref={handleRef}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
interface DetailListHeaderProps {
|
interface DetailListHeaderProps {
|
||||||
columnWidthPercents: number[];
|
columnWidthPercents: number[];
|
||||||
|
enableColumnReorder?: boolean;
|
||||||
|
enableColumnResize?: boolean;
|
||||||
enableVerticalBorders: boolean;
|
enableVerticalBorders: boolean;
|
||||||
headerLeftRef: React.RefObject<HTMLSpanElement | null>;
|
headerLeftRef: React.RefObject<HTMLSpanElement | null>;
|
||||||
|
onColumnReordered?: (args: {
|
||||||
|
columnIdFrom: TableColumn;
|
||||||
|
columnIdTo: TableColumn;
|
||||||
|
edge: Edge | null;
|
||||||
|
}) => void;
|
||||||
|
onColumnResized?: (columnId: TableColumn, width: number) => void;
|
||||||
|
tableId: string;
|
||||||
trackColumns: ItemTableListColumnConfig[];
|
trackColumns: ItemTableListColumnConfig[];
|
||||||
trackTableSize: 'compact' | 'default' | 'large';
|
trackTableSize: 'compact' | 'default' | 'large';
|
||||||
}
|
}
|
||||||
@@ -754,8 +1022,13 @@ const colTypeToJustifyContentMap = {
|
|||||||
const DetailListHeader = memo(
|
const DetailListHeader = memo(
|
||||||
({
|
({
|
||||||
columnWidthPercents,
|
columnWidthPercents,
|
||||||
|
enableColumnReorder,
|
||||||
|
enableColumnResize,
|
||||||
enableVerticalBorders,
|
enableVerticalBorders,
|
||||||
headerLeftRef,
|
headerLeftRef,
|
||||||
|
onColumnReordered,
|
||||||
|
onColumnResized,
|
||||||
|
tableId,
|
||||||
trackColumns,
|
trackColumns,
|
||||||
trackTableSize,
|
trackTableSize,
|
||||||
}: DetailListHeaderProps) => {
|
}: DetailListHeaderProps) => {
|
||||||
@@ -778,10 +1051,30 @@ const DetailListHeader = memo(
|
|||||||
role="row"
|
role="row"
|
||||||
>
|
>
|
||||||
{trackColumns.map((col, colIndex) => {
|
{trackColumns.map((col, colIndex) => {
|
||||||
const percent = columnWidthPercents[colIndex] ?? 0;
|
|
||||||
const { fixedWidth, isFixedColumn } = getTrackColumnFixed(col.id);
|
|
||||||
const isLastColumn = colIndex === trackColumns.length - 1;
|
const isLastColumn = colIndex === trackColumns.length - 1;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(enableColumnResize && onColumnResized) ||
|
||||||
|
(enableColumnReorder && onColumnReordered)
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<DetailListHeaderCell
|
||||||
|
columnId={col.id}
|
||||||
|
columnWidthPercents={columnWidthPercents}
|
||||||
|
enableColumnResize={enableColumnResize}
|
||||||
|
enableVerticalBorders={enableVerticalBorders}
|
||||||
|
isLastColumn={isLastColumn}
|
||||||
|
key={col.id}
|
||||||
|
onColumnReordered={onColumnReordered}
|
||||||
|
onColumnResized={onColumnResized}
|
||||||
|
tableId={tableId}
|
||||||
|
trackColumns={trackColumns}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const percent = columnWidthPercents[colIndex] ?? 0;
|
||||||
|
const { fixedWidth, isFixedColumn } = getTrackColumnFixed(col.id);
|
||||||
const style: React.CSSProperties = {
|
const style: React.CSSProperties = {
|
||||||
flex: isFixedColumn ? `0 0 ${fixedWidth}px` : `${percent} 1 0`,
|
flex: isFixedColumn ? `0 0 ${fixedWidth}px` : `${percent} 1 0`,
|
||||||
justifyContent: colTypeToJustifyContentMap[col.align],
|
justifyContent: colTypeToJustifyContentMap[col.align],
|
||||||
@@ -804,7 +1097,9 @@ const DetailListHeader = memo(
|
|||||||
role="columnheader"
|
role="columnheader"
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
{columnLabelMap[col.id] ?? ''}
|
<span className={styles.trackHeaderCellContent}>
|
||||||
|
{columnLabelMap[col.id] ?? ''}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -819,6 +1114,8 @@ DetailListHeader.displayName = 'DetailListHeader';
|
|||||||
|
|
||||||
const SCROLL_END_DEBOUNCE_MS = 150;
|
const SCROLL_END_DEBOUNCE_MS = 150;
|
||||||
|
|
||||||
|
const DEFAULT_DETAIL_TABLE_ID = 'album-detail';
|
||||||
|
|
||||||
export const ItemDetailList = ({
|
export const ItemDetailList = ({
|
||||||
currentPage,
|
currentPage,
|
||||||
data,
|
data,
|
||||||
@@ -826,15 +1123,21 @@ export const ItemDetailList = ({
|
|||||||
getItem,
|
getItem,
|
||||||
itemCount: externalItemCount,
|
itemCount: externalItemCount,
|
||||||
items,
|
items,
|
||||||
|
onColumnReordered,
|
||||||
|
onColumnResized,
|
||||||
onRangeChanged,
|
onRangeChanged,
|
||||||
onScrollEnd,
|
onScrollEnd,
|
||||||
|
tableId = DEFAULT_DETAIL_TABLE_ID,
|
||||||
}: ItemDetailListProps) => {
|
}: ItemDetailListProps) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const listRef = useListRef(null);
|
const listRef = useListRef(null);
|
||||||
const lastVisibleStartIndexRef = useRef(0);
|
const lastVisibleStartIndexRef = useRef(0);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const controls = useDefaultItemListControls();
|
const controls = useDefaultItemListControls({
|
||||||
|
onColumnReordered,
|
||||||
|
onColumnResized,
|
||||||
|
});
|
||||||
const isMutatingCreateFavorite = useIsMutatingCreateFavorite();
|
const isMutatingCreateFavorite = useIsMutatingCreateFavorite();
|
||||||
const isMutatingDeleteFavorite = useIsMutatingDeleteFavorite();
|
const isMutatingDeleteFavorite = useIsMutatingDeleteFavorite();
|
||||||
const isMutatingFavorite = isMutatingCreateFavorite || isMutatingDeleteFavorite;
|
const isMutatingFavorite = isMutatingCreateFavorite || isMutatingDeleteFavorite;
|
||||||
@@ -1053,8 +1356,17 @@ export const ItemDetailList = ({
|
|||||||
{enableHeader && (
|
{enableHeader && (
|
||||||
<DetailListHeader
|
<DetailListHeader
|
||||||
columnWidthPercents={columnWidthPercents}
|
columnWidthPercents={columnWidthPercents}
|
||||||
|
enableColumnReorder={!!onColumnReordered}
|
||||||
|
enableColumnResize={!!controls.onColumnResized}
|
||||||
enableVerticalBorders={enableVerticalBorders}
|
enableVerticalBorders={enableVerticalBorders}
|
||||||
headerLeftRef={headerLeftRef}
|
headerLeftRef={headerLeftRef}
|
||||||
|
onColumnReordered={controls.onColumnReordered}
|
||||||
|
onColumnResized={
|
||||||
|
controls.onColumnResized
|
||||||
|
? (columnId, width) => controls.onColumnResized?.({ columnId, width })
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
tableId={tableId}
|
||||||
trackColumns={trackColumns}
|
trackColumns={trackColumns}
|
||||||
trackTableSize={trackTableSize}
|
trackTableSize={trackTableSize}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { UseSuspenseQueryOptions } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { useItemListInfiniteLoader } from '/@/renderer/components/item-list/helpers/item-list-infinite-loader';
|
import { useItemListInfiniteLoader } from '/@/renderer/components/item-list/helpers/item-list-infinite-loader';
|
||||||
|
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 { ItemDetailList } from '/@/renderer/components/item-list/item-detail-list/item-detail';
|
import { ItemDetailList } from '/@/renderer/components/item-list/item-detail-list/item-detail';
|
||||||
import { ItemListComponentProps } from '/@/renderer/components/item-list/types';
|
import { ItemListComponentProps } from '/@/renderer/components/item-list/types';
|
||||||
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
|
||||||
@@ -33,6 +35,18 @@ export const AlbumListInfiniteDetail = ({
|
|||||||
|
|
||||||
const listQueryFn = api.controller.getAlbumList;
|
const listQueryFn = api.controller.getAlbumList;
|
||||||
|
|
||||||
|
const { handleColumnReordered } = useItemListColumnReorder({
|
||||||
|
itemListKey: ItemListKey.ALBUM,
|
||||||
|
tableKey: 'detail',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { handleColumnResized } = useItemListColumnResize({
|
||||||
|
itemListKey: ItemListKey.ALBUM,
|
||||||
|
tableKey: 'detail',
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('handleColumnResized', handleColumnResized);
|
||||||
|
|
||||||
const { getItem, itemCount, loadedItems, onRangeChanged } = useItemListInfiniteLoader({
|
const { getItem, itemCount, loadedItems, onRangeChanged } = useItemListInfiniteLoader({
|
||||||
eventKey: ItemListKey.ALBUM,
|
eventKey: ItemListKey.ALBUM,
|
||||||
itemsPerPage,
|
itemsPerPage,
|
||||||
@@ -49,6 +63,8 @@ export const AlbumListInfiniteDetail = ({
|
|||||||
enableHeader={enableHeader}
|
enableHeader={enableHeader}
|
||||||
getItem={getItem}
|
getItem={getItem}
|
||||||
itemCount={itemCount}
|
itemCount={itemCount}
|
||||||
|
onColumnReordered={handleColumnReordered}
|
||||||
|
onColumnResized={handleColumnResized}
|
||||||
onRangeChanged={onRangeChanged}
|
onRangeChanged={onRangeChanged}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { UseSuspenseQueryOptions } from '@tanstack/react-query';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader';
|
import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader';
|
||||||
|
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 { ItemDetailList } from '/@/renderer/components/item-list/item-detail-list/item-detail';
|
import { ItemDetailList } from '/@/renderer/components/item-list/item-detail-list/item-detail';
|
||||||
import { ItemListWithPagination } from '/@/renderer/components/item-list/item-list-pagination/item-list-pagination';
|
import { ItemListWithPagination } from '/@/renderer/components/item-list/item-list-pagination/item-list-pagination';
|
||||||
import { useItemListPagination } from '/@/renderer/components/item-list/item-list-pagination/use-item-list-pagination';
|
import { useItemListPagination } from '/@/renderer/components/item-list/item-list-pagination/use-item-list-pagination';
|
||||||
@@ -35,6 +37,16 @@ export const AlbumListPaginatedDetail = ({
|
|||||||
|
|
||||||
const listQueryFn = api.controller.getAlbumList;
|
const listQueryFn = api.controller.getAlbumList;
|
||||||
|
|
||||||
|
const { handleColumnReordered } = useItemListColumnReorder({
|
||||||
|
itemListKey: ItemListKey.ALBUM,
|
||||||
|
tableKey: 'detail',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { handleColumnResized } = useItemListColumnResize({
|
||||||
|
itemListKey: ItemListKey.ALBUM,
|
||||||
|
tableKey: 'detail',
|
||||||
|
});
|
||||||
|
|
||||||
const { currentPage, onChange } = useItemListPagination();
|
const { currentPage, onChange } = useItemListPagination();
|
||||||
|
|
||||||
const { data, pageCount, totalItemCount } = useItemListPaginatedLoader({
|
const { data, pageCount, totalItemCount } = useItemListPaginatedLoader({
|
||||||
@@ -60,6 +72,8 @@ export const AlbumListPaginatedDetail = ({
|
|||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
enableHeader={enableHeader}
|
enableHeader={enableHeader}
|
||||||
items={data || []}
|
items={data || []}
|
||||||
|
onColumnReordered={handleColumnReordered}
|
||||||
|
onColumnResized={handleColumnResized}
|
||||||
/>
|
/>
|
||||||
</ItemListWithPagination>
|
</ItemListWithPagination>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user