add drag state to item table

This commit is contained in:
jeffvli
2025-11-09 01:03:25 -08:00
parent ad409fecfa
commit d7e2ec0860
5 changed files with 152 additions and 29 deletions
+3 -3
View File
@@ -65,9 +65,9 @@
"@ag-grid-community/infinite-row-model": "^28.2.1", "@ag-grid-community/infinite-row-model": "^28.2.1",
"@ag-grid-community/react": "^28.2.1", "@ag-grid-community/react": "^28.2.1",
"@ag-grid-community/styles": "^28.2.1", "@ag-grid-community/styles": "^28.2.1",
"@atlaskit/pragmatic-drag-and-drop": "1.4.0", "@atlaskit/pragmatic-drag-and-drop": "1.7.7",
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.0", "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.2",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3", "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
"@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^4.0.0", "@electron-toolkit/utils": "^4.0.0",
"@mantine/colors-generator": "^8.2.8", "@mantine/colors-generator": "^8.2.8",
+13 -22
View File
@@ -24,13 +24,13 @@ importers:
specifier: ^28.2.1 specifier: ^28.2.1
version: 28.2.1 version: 28.2.1
'@atlaskit/pragmatic-drag-and-drop': '@atlaskit/pragmatic-drag-and-drop':
specifier: 1.4.0 specifier: 1.7.7
version: 1.4.0 version: 1.7.7
'@atlaskit/pragmatic-drag-and-drop-auto-scroll': '@atlaskit/pragmatic-drag-and-drop-auto-scroll':
specifier: ^2.1.0 specifier: ^2.1.2
version: 2.1.1 version: 2.1.2
'@atlaskit/pragmatic-drag-and-drop-hitbox': '@atlaskit/pragmatic-drag-and-drop-hitbox':
specifier: ^1.0.3 specifier: ^1.1.0
version: 1.1.0 version: 1.1.0
'@electron-toolkit/preload': '@electron-toolkit/preload':
specifier: ^3.0.1 specifier: ^3.0.1
@@ -395,17 +395,14 @@ packages:
peerDependencies: peerDependencies:
ajv: '>=8' ajv: '>=8'
'@atlaskit/pragmatic-drag-and-drop-auto-scroll@2.1.1': '@atlaskit/pragmatic-drag-and-drop-auto-scroll@2.1.2':
resolution: {integrity: sha512-VAQEb3NVLY9Q5ZgC5Eiws9Uf6xOINY9/pAZMdbOVlF90uRXEkmpYqdTL+zeyZ8U8deuqYCmXr7oWIEnxpNQVzA==} resolution: {integrity: sha512-6BgAUxSNbQFiG3uqNxf53cDQADn5mSeh/JsQzCHo46GPQnVWIJk77zWC8yZ++0Mfg1ECy02zNrbniF7SgHAhXQ==}
'@atlaskit/pragmatic-drag-and-drop-hitbox@1.1.0': '@atlaskit/pragmatic-drag-and-drop-hitbox@1.1.0':
resolution: {integrity: sha512-JWt6eVp6Br2FPHRM8s0dUIHQk/jFInGP1f3ti5CdtM1Ji5/pt8Akm44wDC063Gv2i5RGseixtbW0z/t6RYtbdg==} resolution: {integrity: sha512-JWt6eVp6Br2FPHRM8s0dUIHQk/jFInGP1f3ti5CdtM1Ji5/pt8Akm44wDC063Gv2i5RGseixtbW0z/t6RYtbdg==}
'@atlaskit/pragmatic-drag-and-drop@1.4.0': '@atlaskit/pragmatic-drag-and-drop@1.7.7':
resolution: {integrity: sha512-qRY3PTJIcxfl/QB8Gwswz+BRvlmgAC5pB+J2hL6dkIxgqAgVwOhAamMUKsrOcFU/axG2Q7RbNs1xfoLKDuhoPg==} resolution: {integrity: sha512-jX+68AoSTqO/fhCyJDTZ38Ey6/wyL2Iq+J/moanma0YyktpnoHxevjY1UNJHYp0NCburdQDZSL1ZFac1mO1osQ==}
'@atlaskit/pragmatic-drag-and-drop@1.7.4':
resolution: {integrity: sha512-lZHnO9BJdHPKnwB0uvVUCyDnIhL+WAHzXQ2EXX0qacogOsnvIUiCgY0BLKhBqTCWln3/f/Ox5jU54MKO6ayh9A==}
'@babel/code-frame@7.27.1': '@babel/code-frame@7.27.1':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
@@ -5857,23 +5854,17 @@ snapshots:
jsonpointer: 5.0.1 jsonpointer: 5.0.1
leven: 3.1.0 leven: 3.1.0
'@atlaskit/pragmatic-drag-and-drop-auto-scroll@2.1.1': '@atlaskit/pragmatic-drag-and-drop-auto-scroll@2.1.2':
dependencies: dependencies:
'@atlaskit/pragmatic-drag-and-drop': 1.7.4 '@atlaskit/pragmatic-drag-and-drop': 1.7.7
'@babel/runtime': 7.27.1 '@babel/runtime': 7.27.1
'@atlaskit/pragmatic-drag-and-drop-hitbox@1.1.0': '@atlaskit/pragmatic-drag-and-drop-hitbox@1.1.0':
dependencies: dependencies:
'@atlaskit/pragmatic-drag-and-drop': 1.7.4 '@atlaskit/pragmatic-drag-and-drop': 1.7.7
'@babel/runtime': 7.27.1 '@babel/runtime': 7.27.1
'@atlaskit/pragmatic-drag-and-drop@1.4.0': '@atlaskit/pragmatic-drag-and-drop@1.7.7':
dependencies:
'@babel/runtime': 7.27.1
bind-event-listener: 3.0.0
raf-schd: 4.0.3
'@atlaskit/pragmatic-drag-and-drop@1.7.4':
dependencies: dependencies:
'@babel/runtime': 7.27.1 '@babel/runtime': 7.27.1
bind-event-listener: 3.0.0 bind-event-listener: 3.0.0
@@ -115,6 +115,10 @@
} }
} }
.container.data-row.dragging {
opacity: 0.5;
}
.container.data-row > * { .container.data-row > * {
position: relative; position: relative;
z-index: 2; z-index: 2;
@@ -1,3 +1,4 @@
import { useMergedRef } from '@mantine/hooks';
import clsx from 'clsx'; import clsx from 'clsx';
import React, { CSSProperties, ReactNode, useEffect, useRef } from 'react'; import React, { CSSProperties, ReactNode, useEffect, useRef } from 'react';
import { CellComponentProps } from 'react-window-v2'; import { CellComponentProps } from 'react-window-v2';
@@ -5,6 +6,7 @@ import { CellComponentProps } from 'react-window-v2';
import styles from './item-table-list-column.module.css'; import styles from './item-table-list-column.module.css';
import i18n from '/@/i18n/i18n'; import i18n from '/@/i18n/i18n';
import { getDraggedItems } from '/@/renderer/components/item-list/helpers/get-dragged-items';
import { ActionsColumn } from '/@/renderer/components/item-list/item-table-list/columns/actions-column'; import { ActionsColumn } from '/@/renderer/components/item-list/item-table-list/columns/actions-column';
import { AlbumArtistsColumn } from '/@/renderer/components/item-list/item-table-list/columns/album-artists-column'; import { AlbumArtistsColumn } from '/@/renderer/components/item-list/item-table-list/columns/album-artists-column';
import { ArtistsColumn } from '/@/renderer/components/item-list/item-table-list/columns/artists-column'; import { ArtistsColumn } from '/@/renderer/components/item-list/item-table-list/columns/artists-column';
@@ -29,9 +31,11 @@ import { TitleColumn } from '/@/renderer/components/item-list/item-table-list/co
import { TitleCombinedColumn } from '/@/renderer/components/item-list/item-table-list/columns/title-combined-column'; import { TitleCombinedColumn } from '/@/renderer/components/item-list/item-table-list/columns/title-combined-column';
import { TableItemProps } from '/@/renderer/components/item-list/item-table-list/item-table-list'; import { TableItemProps } from '/@/renderer/components/item-list/item-table-list/item-table-list';
import { ItemControls, ItemListItem } from '/@/renderer/components/item-list/types'; import { ItemControls, ItemListItem } from '/@/renderer/components/item-list/types';
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
import { Icon } from '/@/shared/components/icon/icon'; import { Icon } from '/@/shared/components/icon/icon';
import { Skeleton } from '/@/shared/components/skeleton/skeleton'; import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { DragTarget, DragTargetMap } from '/@/shared/types/drag-and-drop';
import { TableColumn } from '/@/shared/types/types'; import { TableColumn } from '/@/shared/types/types';
export interface ItemTableListColumn extends CellComponentProps<TableItemProps> {} export interface ItemTableListColumn extends CellComponentProps<TableItemProps> {}
@@ -145,6 +149,58 @@ export const TableColumnTextContainer = (
? props.internalState.isSelected((item as any).id) ? props.internalState.isSelected((item as any).id)
: false; : false;
const shouldEnableDrag = !!props.enableDrag && isDataRow && !!item;
const { isDragging: isDraggingLocal, ref: dragRef } = useDragDrop<HTMLDivElement>({
drag: {
getId: () => {
if (!item || !isDataRow) {
return [];
}
const draggedItems = getDraggedItems(
item as any,
props.itemType,
props.internalState,
);
return draggedItems.map((draggedItem) => draggedItem.id);
},
getItem: () => {
if (!item || !isDataRow) {
return [];
}
return [item];
},
onDragStart: () => {
if (!item || !isDataRow || !props.internalState) {
return;
}
const draggedItems = getDraggedItems(
item as any,
props.itemType,
props.internalState,
);
props.internalState.setDragging(draggedItems);
},
onDrop: () => {
if (props.internalState) {
props.internalState.setDragging([]);
}
},
target: DragTargetMap[props.itemType] || DragTarget.GENERIC,
},
isEnabled: shouldEnableDrag,
});
const isDragging =
item && typeof item === 'object' && 'id' in item && props.internalState
? props.internalState.isDragging((item as any).id)
: isDraggingLocal;
const mergedRef = useMergedRef(containerRef, shouldEnableDrag ? dragRef : null);
useEffect(() => { useEffect(() => {
if (!isDataRow || !containerRef.current) return; if (!isDataRow || !containerRef.current) return;
@@ -203,6 +259,7 @@ export const TableColumnTextContainer = (
[styles.center]: props.columns[props.columnIndex].align === 'center', [styles.center]: props.columns[props.columnIndex].align === 'center',
[styles.compact]: props.size === 'compact', [styles.compact]: props.size === 'compact',
[styles.dataRow]: isDataRow, [styles.dataRow]: isDataRow,
[styles.dragging]: isDataRow && isDragging,
[styles.large]: props.size === 'large', [styles.large]: props.size === 'large',
[styles.left]: props.columns[props.columnIndex].align === 'start', [styles.left]: props.columns[props.columnIndex].align === 'start',
[styles.paddingLg]: props.cellPadding === 'lg', [styles.paddingLg]: props.cellPadding === 'lg',
@@ -219,7 +276,7 @@ export const TableColumnTextContainer = (
})} })}
data-row-index={isDataRow ? props.rowIndex : undefined} data-row-index={isDataRow ? props.rowIndex : undefined}
onClick={handleCellClick} onClick={handleCellClick}
ref={containerRef} ref={mergedRef}
style={props.style} style={props.style}
> >
<Text <Text
@@ -254,6 +311,58 @@ export const TableColumnContainer = (
? props.internalState.isSelected((item as any).id) ? props.internalState.isSelected((item as any).id)
: false; : false;
const shouldEnableDrag = !!props.enableDrag && isDataRow && !!item;
const { isDragging: isDraggingLocal, ref: dragRef } = useDragDrop<HTMLDivElement>({
drag: {
getId: () => {
if (!item || !isDataRow) {
return [];
}
const draggedItems = getDraggedItems(
item as any,
props.itemType,
props.internalState,
);
return draggedItems.map((draggedItem) => draggedItem.id);
},
getItem: () => {
if (!item || !isDataRow) {
return [];
}
return [item];
},
onDragStart: () => {
if (!item || !isDataRow || !props.internalState) {
return;
}
const draggedItems = getDraggedItems(
item as any,
props.itemType,
props.internalState,
);
props.internalState.setDragging(draggedItems);
},
onDrop: () => {
if (props.internalState) {
props.internalState.setDragging([]);
}
},
target: DragTargetMap[props.itemType] || DragTarget.GENERIC,
},
isEnabled: shouldEnableDrag,
});
const isDragging =
item && typeof item === 'object' && 'id' in item && props.internalState
? props.internalState.isDragging((item as any).id)
: isDraggingLocal;
const mergedRef = useMergedRef(containerRef, shouldEnableDrag ? dragRef : null);
useEffect(() => { useEffect(() => {
if (!isDataRow || !containerRef.current) return; if (!isDataRow || !containerRef.current) return;
@@ -312,6 +421,7 @@ export const TableColumnContainer = (
[styles.center]: props.columns[props.columnIndex].align === 'center', [styles.center]: props.columns[props.columnIndex].align === 'center',
[styles.compact]: props.size === 'compact', [styles.compact]: props.size === 'compact',
[styles.dataRow]: isDataRow, [styles.dataRow]: isDataRow,
[styles.dragging]: isDataRow && isDragging,
[styles.large]: props.size === 'large', [styles.large]: props.size === 'large',
[styles.left]: props.columns[props.columnIndex].align === 'start', [styles.left]: props.columns[props.columnIndex].align === 'start',
[styles.paddingLg]: props.cellPadding === 'lg', [styles.paddingLg]: props.cellPadding === 'lg',
@@ -328,7 +438,7 @@ export const TableColumnContainer = (
})} })}
data-row-index={isDataRow ? props.rowIndex : undefined} data-row-index={isDataRow ? props.rowIndex : undefined}
onClick={handleCellClick} onClick={handleCellClick}
ref={containerRef} ref={mergedRef}
style={{ ...props.containerStyle, ...props.style }} style={{ ...props.containerStyle, ...props.style }}
> >
{props.children} {props.children}
@@ -1,5 +1,6 @@
// Component adapted from https://github.com/bvaughn/react-window/issues/826 // Component adapted from https://github.com/bvaughn/react-window/issues/826
import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
import { useMergedRef } from '@mantine/hooks'; import { useMergedRef } from '@mantine/hooks';
import clsx from 'clsx'; import clsx from 'clsx';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
@@ -23,8 +24,8 @@ import { ExpandedListContainer } from '/@/renderer/components/item-list/expanded
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item'; import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls'; import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
import { import {
ItemListStateItem,
ItemListStateActions, ItemListStateActions,
ItemListStateItem,
useItemListState, useItemListState,
} from '/@/renderer/components/item-list/helpers/item-list-state'; } from '/@/renderer/components/item-list/helpers/item-list-state';
import { parseTableColumns } from '/@/renderer/components/item-list/helpers/parse-table-columns'; import { parseTableColumns } from '/@/renderer/components/item-list/helpers/parse-table-columns';
@@ -42,6 +43,7 @@ interface VirtualizedTableGridProps {
controls: ItemControls; controls: ItemControls;
data: unknown[]; data: unknown[];
enableAlternateRowColors: boolean; enableAlternateRowColors: boolean;
enableDrag?: boolean;
enableExpansion: boolean; enableExpansion: boolean;
enableHeader: boolean; enableHeader: boolean;
enableHorizontalBorders: boolean; enableHorizontalBorders: boolean;
@@ -77,6 +79,7 @@ const VirtualizedTableGrid = React.memo(
controls, controls,
data, data,
enableAlternateRowColors, enableAlternateRowColors,
enableDrag,
enableExpansion, enableExpansion,
enableHeader, enableHeader,
enableHorizontalBorders, enableHorizontalBorders,
@@ -115,6 +118,7 @@ const VirtualizedTableGrid = React.memo(
controls, controls,
data: enableHeader ? [null, ...data] : data, data: enableHeader ? [null, ...data] : data,
enableAlternateRowColors, enableAlternateRowColors,
enableDrag,
enableExpansion, enableExpansion,
enableHeader, enableHeader,
enableHorizontalBorders, enableHorizontalBorders,
@@ -134,6 +138,7 @@ const VirtualizedTableGrid = React.memo(
enableHeader, enableHeader,
data, data,
enableAlternateRowColors, enableAlternateRowColors,
enableDrag,
enableExpansion, enableExpansion,
enableHorizontalBorders, enableHorizontalBorders,
enableRowHoverHighlight, enableRowHoverHighlight,
@@ -414,6 +419,7 @@ export interface TableItemProps {
controls: ItemControls; controls: ItemControls;
data: ItemTableListProps['data']; data: ItemTableListProps['data'];
enableAlternateRowColors?: ItemTableListProps['enableAlternateRowColors']; enableAlternateRowColors?: ItemTableListProps['enableAlternateRowColors'];
enableDrag?: ItemTableListProps['enableDrag'];
enableExpansion?: ItemTableListProps['enableExpansion']; enableExpansion?: ItemTableListProps['enableExpansion'];
enableHeader?: ItemTableListProps['enableHeader']; enableHeader?: ItemTableListProps['enableHeader'];
enableHorizontalBorders?: ItemTableListProps['enableHorizontalBorders']; enableHorizontalBorders?: ItemTableListProps['enableHorizontalBorders'];
@@ -434,6 +440,7 @@ interface ItemTableListProps {
currentPage?: number; currentPage?: number;
data: unknown[]; data: unknown[];
enableAlternateRowColors?: boolean; enableAlternateRowColors?: boolean;
enableDrag?: boolean;
enableExpansion?: boolean; enableExpansion?: boolean;
enableHeader?: boolean; enableHeader?: boolean;
enableHorizontalBorders?: boolean; enableHorizontalBorders?: boolean;
@@ -461,6 +468,7 @@ export const ItemTableList = ({
currentPage, currentPage,
data, data,
enableAlternateRowColors = false, enableAlternateRowColors = false,
enableDrag = true,
enableExpansion = true, enableExpansion = true,
enableHeader = true, enableHeader = true,
enableHorizontalBorders = false, enableHorizontalBorders = false,
@@ -677,10 +685,19 @@ export const ItemTableList = ({
elements: { viewport: root.firstElementChild as HTMLElement }, elements: { viewport: root.firstElementChild as HTMLElement },
target: root, target: root,
}); });
if (enableDrag) {
autoScrollForElements({
canScroll: () => true,
element: root.firstElementChild as HTMLElement,
getAllowedAxis: () => 'vertical',
getConfiguration: () => ({ maxScrollSpeed: 'fast' }),
});
}
} }
return undefined; return undefined;
}, [initialize]); }, [enableDrag, initialize]);
useEffect(() => { useEffect(() => {
const header = pinnedRowRef.current?.childNodes[0] as HTMLDivElement; const header = pinnedRowRef.current?.childNodes[0] as HTMLDivElement;
@@ -1266,6 +1283,7 @@ export const ItemTableList = ({
controls={controls} controls={controls}
data={data} data={data}
enableAlternateRowColors={enableAlternateRowColors} enableAlternateRowColors={enableAlternateRowColors}
enableDrag={enableDrag}
enableExpansion={enableExpansion} enableExpansion={enableExpansion}
enableHeader={enableHeader} enableHeader={enableHeader}
enableHorizontalBorders={enableHorizontalBorders} enableHorizontalBorders={enableHorizontalBorders}