fix(core): some dnd perf issues (#9661)
1. page list item are bound two draggables. adding `draggable` prop to WorkbenchLink to mitigate the issue. 2. DndService may not resolve datatransfer when dragging.
This commit is contained in:
@@ -87,10 +87,16 @@ export const useDraggable = <D extends DNDData = DNDData>(
|
|||||||
}, [...deps, context.toExternalData]);
|
}, [...deps, context.toExternalData]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dragRef.current) {
|
if (
|
||||||
|
!dragRef.current ||
|
||||||
|
(typeof options.canDrag === 'boolean' && !options.canDrag)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const element = dragRef.current;
|
||||||
|
const dragHandle = dragHandleRef.current;
|
||||||
|
|
||||||
const windowEvent = {
|
const windowEvent = {
|
||||||
dragleave: () => {
|
dragleave: () => {
|
||||||
setDraggingPosition(state =>
|
setDraggingPosition(state =>
|
||||||
@@ -104,11 +110,11 @@ export const useDraggable = <D extends DNDData = DNDData>(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
dragRef.current.dataset.affineDraggable = 'true';
|
element.dataset.affineDraggable = 'true';
|
||||||
|
|
||||||
const cleanupDraggable = draggable({
|
const cleanupDraggable = draggable({
|
||||||
element: dragRef.current,
|
element,
|
||||||
dragHandle: dragHandleRef.current ?? undefined,
|
dragHandle: dragHandle ?? undefined,
|
||||||
canDrag: draggableGet(options.canDrag),
|
canDrag: draggableGet(options.canDrag),
|
||||||
getInitialData: draggableGet(options.data),
|
getInitialData: draggableGet(options.data),
|
||||||
getInitialDataForExternal: draggableGet(options.toExternalData),
|
getInitialDataForExternal: draggableGet(options.toExternalData),
|
||||||
@@ -130,8 +136,8 @@ export const useDraggable = <D extends DNDData = DNDData>(
|
|||||||
if (enableDropTarget.current) {
|
if (enableDropTarget.current) {
|
||||||
setDropTarget([]);
|
setDropTarget([]);
|
||||||
}
|
}
|
||||||
if (dragRef.current) {
|
if (element) {
|
||||||
dragRef.current.dataset['dragging'] = 'true';
|
element.dataset['dragging'] = 'true';
|
||||||
}
|
}
|
||||||
options.onDragStart?.(args);
|
options.onDragStart?.(args);
|
||||||
},
|
},
|
||||||
@@ -153,8 +159,8 @@ export const useDraggable = <D extends DNDData = DNDData>(
|
|||||||
if (enableDropTarget.current) {
|
if (enableDropTarget.current) {
|
||||||
setDropTarget([]);
|
setDropTarget([]);
|
||||||
}
|
}
|
||||||
if (dragRef.current) {
|
if (element) {
|
||||||
delete dragRef.current.dataset['dragging'];
|
delete element.dataset['dragging'];
|
||||||
}
|
}
|
||||||
options.onDrop?.(args);
|
options.onDrop?.(args);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
||||||
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
||||||
import { dropTargetForExternal } from '@atlaskit/pragmatic-drag-and-drop/external/adapter';
|
import { dropTargetForExternal } from '@atlaskit/pragmatic-drag-and-drop/external/adapter';
|
||||||
import type {
|
import type {
|
||||||
@@ -17,7 +18,14 @@ import {
|
|||||||
type Instruction,
|
type Instruction,
|
||||||
type ItemMode,
|
type ItemMode,
|
||||||
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
||||||
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
import {
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import { shallowUpdater } from '../../utils';
|
import { shallowUpdater } from '../../utils';
|
||||||
import { getAdaptedEventArgs, isExternalDrag } from './common';
|
import { getAdaptedEventArgs, isExternalDrag } from './common';
|
||||||
@@ -201,9 +209,16 @@ export const useDropTarget = <D extends DNDData = DNDData>(
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [...deps, dropTargetContext.fromExternalData]);
|
}, [...deps, dropTargetContext.fromExternalData]);
|
||||||
|
|
||||||
const dropTargetOptions = useMemo(() => {
|
const getDropTargetOptions = useCallback(() => {
|
||||||
const wrappedCanDrop = dropTargetGet(options.canDrop, options);
|
const wrappedCanDrop = dropTargetGet(options.canDrop, options);
|
||||||
let _element: HTMLElement | null = null;
|
let element: HTMLElement | null = dropTargetRef.current;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!element ||
|
||||||
|
(typeof options.canDrop === 'boolean' && !options.canDrop)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const updateDragOver = (
|
const updateDragOver = (
|
||||||
args: DropTargetDragEvent<D>,
|
args: DropTargetDragEvent<D>,
|
||||||
@@ -254,12 +269,7 @@ export const useDropTarget = <D extends DNDData = DNDData>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get element() {
|
element,
|
||||||
if (!_element) {
|
|
||||||
_element = dropTargetRef.current;
|
|
||||||
}
|
|
||||||
return _element;
|
|
||||||
},
|
|
||||||
canDrop: wrappedCanDrop
|
canDrop: wrappedCanDrop
|
||||||
? (args: DropTargetGetFeedback<D>) => {
|
? (args: DropTargetGetFeedback<D>) => {
|
||||||
// check if args has data. if not, it's an external drag
|
// check if args has data. if not, it's an external drag
|
||||||
@@ -290,8 +300,8 @@ export const useDropTarget = <D extends DNDData = DNDData>(
|
|||||||
}
|
}
|
||||||
if (options.treeInstruction) {
|
if (options.treeInstruction) {
|
||||||
setTreeInstruction(null);
|
setTreeInstruction(null);
|
||||||
if (dropTargetRef.current) {
|
if (element) {
|
||||||
delete dropTargetRef.current.dataset['treeInstruction'];
|
delete element.dataset['treeInstruction'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (options.closestEdge) {
|
if (options.closestEdge) {
|
||||||
@@ -300,8 +310,8 @@ export const useDropTarget = <D extends DNDData = DNDData>(
|
|||||||
if (enableDropEffect.current) {
|
if (enableDropEffect.current) {
|
||||||
setDropEffect(null);
|
setDropEffect(null);
|
||||||
}
|
}
|
||||||
if (dropTargetRef.current) {
|
if (element) {
|
||||||
delete dropTargetRef.current.dataset['draggedOver'];
|
delete element.dataset['draggedOver'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// external data is only available in drop event thus
|
// external data is only available in drop event thus
|
||||||
@@ -318,10 +328,7 @@ export const useDropTarget = <D extends DNDData = DNDData>(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (args.location.current.dropTargets[0]?.element === element) {
|
||||||
args.location.current.dropTargets[0]?.element ===
|
|
||||||
dropTargetRef.current
|
|
||||||
) {
|
|
||||||
options.onDrop?.({
|
options.onDrop?.({
|
||||||
...args,
|
...args,
|
||||||
treeInstruction: extractInstruction(args.self.data),
|
treeInstruction: extractInstruction(args.self.data),
|
||||||
@@ -376,19 +383,15 @@ export const useDropTarget = <D extends DNDData = DNDData>(
|
|||||||
},
|
},
|
||||||
onDropTargetChange: (args: DropTargetDropEvent<D>) => {
|
onDropTargetChange: (args: DropTargetDropEvent<D>) => {
|
||||||
args = getAdaptedEventArgs(args, options.fromExternalData);
|
args = getAdaptedEventArgs(args, options.fromExternalData);
|
||||||
if (
|
if (args.location.current.dropTargets[0]?.element === element) {
|
||||||
args.location.current.dropTargets[0]?.element ===
|
|
||||||
dropTargetRef.current
|
|
||||||
) {
|
|
||||||
if (enableDraggedOver.current) {
|
if (enableDraggedOver.current) {
|
||||||
setDraggedOver(true);
|
setDraggedOver(true);
|
||||||
}
|
}
|
||||||
if (options.treeInstruction) {
|
if (options.treeInstruction) {
|
||||||
const instruction = extractInstruction(args.self.data);
|
const instruction = extractInstruction(args.self.data);
|
||||||
setTreeInstruction(instruction);
|
setTreeInstruction(instruction);
|
||||||
if (dropTargetRef.current) {
|
if (element) {
|
||||||
dropTargetRef.current.dataset['treeInstruction'] =
|
element.dataset['treeInstruction'] = instruction?.type;
|
||||||
instruction?.type;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (options.closestEdge) {
|
if (options.closestEdge) {
|
||||||
@@ -410,8 +413,8 @@ export const useDropTarget = <D extends DNDData = DNDData>(
|
|||||||
clientY: args.location.current.input.clientY,
|
clientY: args.location.current.input.clientY,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (dropTargetRef.current) {
|
if (element) {
|
||||||
dropTargetRef.current.dataset['draggedOver'] = 'true';
|
element.dataset['draggedOver'] = 'true';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (enableDraggedOver.current) {
|
if (enableDraggedOver.current) {
|
||||||
@@ -422,8 +425,8 @@ export const useDropTarget = <D extends DNDData = DNDData>(
|
|||||||
}
|
}
|
||||||
if (options.treeInstruction) {
|
if (options.treeInstruction) {
|
||||||
setTreeInstruction(null);
|
setTreeInstruction(null);
|
||||||
if (dropTargetRef.current) {
|
if (element) {
|
||||||
delete dropTargetRef.current.dataset['treeInstruction'];
|
delete element.dataset['treeInstruction'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (enableDropEffect.current) {
|
if (enableDropEffect.current) {
|
||||||
@@ -440,8 +443,8 @@ export const useDropTarget = <D extends DNDData = DNDData>(
|
|||||||
if (options.closestEdge) {
|
if (options.closestEdge) {
|
||||||
setClosestEdge(null);
|
setClosestEdge(null);
|
||||||
}
|
}
|
||||||
if (dropTargetRef.current) {
|
if (element) {
|
||||||
delete dropTargetRef.current.dataset['draggedOver'];
|
delete element.dataset['draggedOver'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -449,18 +452,26 @@ export const useDropTarget = <D extends DNDData = DNDData>(
|
|||||||
}, [options]);
|
}, [options]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!dropTargetRef.current) {
|
const dropTargetOptions = getDropTargetOptions();
|
||||||
|
if (!dropTargetOptions) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return dropTargetForElements(dropTargetOptions as any);
|
|
||||||
}, [dropTargetOptions]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
// @ts-expect-error: fix type error
|
||||||
if (!dropTargetRef.current || !options.fromExternalData) {
|
const cleanup = [dropTargetForElements(dropTargetOptions)];
|
||||||
return;
|
|
||||||
|
if (options.allowExternal && options.fromExternalData) {
|
||||||
|
// @ts-expect-error: fix type error
|
||||||
|
cleanup.push(dropTargetForExternal(dropTargetOptions));
|
||||||
}
|
}
|
||||||
return dropTargetForExternal(dropTargetOptions as any);
|
|
||||||
}, [dropTargetOptions, options.fromExternalData]);
|
return combine(...cleanup);
|
||||||
|
}, [
|
||||||
|
getDropTargetOptions,
|
||||||
|
options.canDrop,
|
||||||
|
options.allowExternal,
|
||||||
|
options.fromExternalData,
|
||||||
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dropTargetRef,
|
dropTargetRef,
|
||||||
|
|||||||
@@ -382,7 +382,7 @@ const PageListItemWrapper = forwardRef(
|
|||||||
|
|
||||||
if (to) {
|
if (to) {
|
||||||
return (
|
return (
|
||||||
<WorkbenchLink ref={ref} {...commonProps} to={to}>
|
<WorkbenchLink ref={ref} draggable={false} {...commonProps} to={to}>
|
||||||
{children}
|
{children}
|
||||||
</WorkbenchLink>
|
</WorkbenchLink>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export class DndService extends Service {
|
|||||||
isDropEvent?: boolean
|
isDropEvent?: boolean
|
||||||
) => {
|
) => {
|
||||||
if (!isDropEvent) {
|
if (!isDropEvent) {
|
||||||
return this.resolveBlocksuiteExternalData(args.source) || {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
let resolved: AffineDNDData['draggable'] | null = null;
|
let resolved: AffineDNDData['draggable'] | null = null;
|
||||||
@@ -168,30 +168,24 @@ export class DndService extends Service {
|
|||||||
if (!dndAPI) {
|
if (!dndAPI) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const encoded = source.getStringData(dndAPI.mimeType);
|
||||||
if (source.types.includes(dndAPI.mimeType)) {
|
if (!encoded) {
|
||||||
const from = {
|
return null;
|
||||||
at: 'blocksuite-editor',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
let entity: Entity | null = null;
|
|
||||||
|
|
||||||
const encoded = source.getStringData(dndAPI.mimeType);
|
|
||||||
const snapshot = encoded ? dndAPI.decodeSnapshot(encoded) : null;
|
|
||||||
entity = snapshot ? this.resolveBlockSnapshot(snapshot) : null;
|
|
||||||
|
|
||||||
if (!entity) {
|
|
||||||
return {
|
|
||||||
from,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
entity,
|
|
||||||
from,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
const snapshot = dndAPI.decodeSnapshot(encoded);
|
||||||
|
if (!snapshot) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const entity = this.resolveBlockSnapshot(snapshot);
|
||||||
|
if (!entity) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
entity,
|
||||||
|
from: {
|
||||||
|
at: 'blocksuite-editor',
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly resolveHTML: EntityResolver = html => {
|
private readonly resolveHTML: EntityResolver = html => {
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ function resolveToEntity(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const WorkbenchLink = forwardRef<HTMLAnchorElement, WorkbenchLinkProps>(
|
export const WorkbenchLink = forwardRef<HTMLAnchorElement, WorkbenchLinkProps>(
|
||||||
function WorkbenchLink({ to, onClick, replaceHistory, ...other }, ref) {
|
function WorkbenchLink(
|
||||||
|
{ to, onClick, draggable = true, replaceHistory, ...other },
|
||||||
|
ref
|
||||||
|
) {
|
||||||
const { workbenchService } = useServices({
|
const { workbenchService } = useServices({
|
||||||
WorkbenchService,
|
WorkbenchService,
|
||||||
});
|
});
|
||||||
@@ -79,8 +82,10 @@ export const WorkbenchLink = forwardRef<HTMLAnchorElement, WorkbenchLinkProps>(
|
|||||||
to: stringTo,
|
to: stringTo,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
canDrag:
|
||||||
|
typeof draggable === 'boolean' ? draggable : draggable === 'true',
|
||||||
};
|
};
|
||||||
}, [to, basename, stringTo]);
|
}, [to, basename, stringTo, draggable]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
|
|||||||
Reference in New Issue
Block a user