Skip to content

Commit

Permalink
[2978] Add useCallback to ReactFlow edge & drag events
Browse files Browse the repository at this point in the history
Bug: #2978
Signed-off-by: Michaël Charfadi <michael.charfadi@obeosoft.com>
  • Loading branch information
mcharfadi committed Jan 23, 2024
1 parent 05b1f7e commit 3ae3f71
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 91 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Expand Up @@ -116,6 +116,7 @@ Now, for a multi-valued feature, the values are properly displayed and the delet
- https://github.com/eclipse-sirius/sirius-web/issues/2934[#2934] [portal] Fix the initial position of representations added into portals
- https://github.com/eclipse-sirius/sirius-web/issues/2907[#2907] [diagram] Fix an issue on some browsers where a tool could not be executed if the tooltip was visible
- https://github.com/eclipse-sirius/sirius-web/issues/2901[#2901] [diagram] Fix an issue where the connectionLine could be stuck on invalid nodes.
- https://github.com/eclipse-sirius/sirius-web/issues/2978[#2978] [diagram] Fix issues with ReactFlow edge and drag events that were slowing down the application.

=== New Features

Expand Down
Expand Up @@ -12,7 +12,7 @@
*******************************************************************************/

import { Theme, useTheme } from '@material-ui/core/styles';
import { useContext } from 'react';
import { useCallback, useContext } from 'react';
import {
Connection,
Edge,
Expand Down Expand Up @@ -93,38 +93,38 @@ export const useConnector = (): UseConnectorValue => {
},
};

const onConnect: OnConnect = (connection: Connection) => {
const onConnect: OnConnect = useCallback((connection: Connection) => {
setConnection(connection);
};
}, []);

const onConnectStart: OnConnectStart = (
_event: React.MouseEvent | React.TouchEvent,
params: OnConnectStartParams
) => {
hideDiagramElementPalette();
resetConnection();
if (params.nodeId) {
updateNodeInternals(params.nodeId);
}
};
const onConnectStart: OnConnectStart = useCallback(
(_event: React.MouseEvent | React.TouchEvent, params: OnConnectStartParams) => {
hideDiagramElementPalette();
resetConnection();
if (params.nodeId) {
updateNodeInternals(params.nodeId);
}
},
[]
);

const onConnectorContextualMenuClose = () => resetConnection();

const onConnectionStartElementClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const onConnectionStartElementClick = useCallback((event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (event.button === 0) {
setIsNewConnection(true);
}
};
}, []);

const onConnectEnd: OnConnectEnd = (event: MouseEvent | TouchEvent) => {
const onConnectEnd: OnConnectEnd = useCallback((event: MouseEvent | TouchEvent) => {
if ('clientX' in event && 'clientY' in event) {
setPosition({ x: event.clientX || 0, y: event.clientY });
} else if ('touches' in event) {
const touchEvent = event as TouchEvent;
setPosition({ x: touchEvent.touches[0]?.clientX || 0, y: touchEvent.touches[0]?.clientY || 0 });
}
setIsNewConnection(false);
};
}, []);

const reactFlowInstance = useReactFlow<NodeData, EdgeData>();

Expand Down
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2023 Obeo.
* Copyright (c) 2023, 2024 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
Expand All @@ -13,9 +13,10 @@
import { gql, useMutation } from '@apollo/client';
import { DRAG_SOURCES_TYPE, useMultiToast } from '@eclipse-sirius/sirius-components-core';
import { useCallback, useContext, useEffect } from 'react';
import { Viewport, XYPosition, useStoreApi, useViewport } from 'reactflow';
import { useReactFlow } from 'reactflow';
import { DiagramContext } from '../../contexts/DiagramContext';
import { DiagramContextValue } from '../../contexts/DiagramContext.types';
import { EdgeData, NodeData } from '../DiagramRenderer.types';
import {
GQLDropOnDiagramData,
GQLDropOnDiagramInput,
Expand Down Expand Up @@ -49,17 +50,6 @@ const dropOnDiagramMutation = gql`
}
`;

const computeDropPosition = (
event: MouseEvent | React.MouseEvent,
{ x: viewportX, y: viewportY, zoom: viewportZoom }: Viewport,
bounds?: DOMRect
): XYPosition => {
return {
x: (event.clientX - (bounds?.left ?? 0) - viewportX) / viewportZoom,
y: (event.clientY - (bounds?.top ?? 0) - viewportY) / viewportZoom,
};
};

const isErrorPayload = (payload: GQLDropOnDiagramPayload): payload is GQLErrorPayload =>
payload.__typename === 'ErrorPayload';
const isSuccessPayload = (payload: GQLDropOnDiagramPayload): payload is GQLDropOnDiagramSuccessPayload =>
Expand All @@ -72,9 +62,6 @@ export const useDrop = (): UseDropValue => {
GQLDropOnDiagramData,
GQLDropOnDiagramVariables
>(dropOnDiagramMutation);
const viewport = useViewport();
const { domNode } = useStoreApi().getState();
const element = domNode?.getBoundingClientRect();

useEffect(() => {
if (droponDiagramError) {
Expand All @@ -90,39 +77,39 @@ export const useDrop = (): UseDropValue => {
}
}
}, [droponDiagramElementData, droponDiagramError]);
const reactflow = useReactFlow<NodeData, EdgeData>();
const onDrop = useCallback((event: React.DragEvent, diagramElementId?: string) => {
event.preventDefault();
event.stopPropagation();

const onDrop = useCallback(
(event: React.DragEvent, diagramElementId?: string) => {
event.preventDefault();
event.stopPropagation();

const data = event.dataTransfer.getData(DRAG_SOURCES_TYPE);
const data = event.dataTransfer.getData(DRAG_SOURCES_TYPE);

// check if the dropped element is valid
if (data === '') {
return;
}
// check if the dropped element is valid
if (data === '') {
return;
}
const dropPosition = reactflow.screenToFlowPosition({
x: event.clientX,
y: event.clientY,
});

const dropPosition = computeDropPosition(event, viewport, element);
const selectedIds = JSON.parse(data).map((entry) => entry.id);
const selectedIds = JSON.parse(data).map((entry) => entry.id);

const input: GQLDropOnDiagramInput = {
id: crypto.randomUUID(),
editingContextId,
representationId: diagramId,
objectIds: selectedIds,
startingPositionX: dropPosition.x,
startingPositionY: dropPosition.y,
diagramTargetElementId: diagramElementId ? diagramElementId : diagramId,
};
dropMutation({ variables: { input } });
},
[element?.top, element?.left, viewport]
);
const input: GQLDropOnDiagramInput = {
id: crypto.randomUUID(),
editingContextId,
representationId: diagramId,
objectIds: selectedIds,
startingPositionX: dropPosition.x,
startingPositionY: dropPosition.y,
diagramTargetElementId: diagramElementId ? diagramElementId : diagramId,
};
dropMutation({ variables: { input } });
}, []);

const onDragOver = (event: React.DragEvent) => {
const onDragOver = useCallback((event: React.DragEvent) => {
event.preventDefault();
};
}, []);

return { onDrop, onDragOver };
};
Expand Up @@ -14,12 +14,13 @@ import { gql, useMutation } from '@apollo/client';
import { useMultiToast } from '@eclipse-sirius/sirius-components-core';
import { useTheme } from '@material-ui/core/styles';
import { useCallback, useContext, useEffect } from 'react';
import { Node, NodeDragHandler, useReactFlow, useStoreApi, useViewport, Viewport, XYPosition } from 'reactflow';
import { Node, NodeDragHandler, XYPosition, useReactFlow } from 'reactflow';
import { DiagramContext } from '../../contexts/DiagramContext';
import { DiagramContextValue } from '../../contexts/DiagramContext.types';
import { useDiagramDescription } from '../../contexts/useDiagramDescription';
import { GQLDropNodeCompatibility } from '../../representation/DiagramRepresentation.types';
import { EdgeData, NodeData } from '../DiagramRenderer.types';
import { ListNodeData } from '../node/ListNode.types';
import { DropNodeContext } from './DropNodeContext';
import { DropNodeContextValue } from './DropNodeContext.types';
import {
Expand All @@ -32,7 +33,6 @@ import {
GQLSuccessPayload,
UseDropNodeValue,
} from './useDropNode.types';
import { ListNodeData } from '../node/ListNode.types';

const dropNodeMutation = gql`
mutation dropNode($input: DropNodeInput!) {
Expand Down Expand Up @@ -129,17 +129,6 @@ const isDescendantOf = (parent: Node, candidate: Node, nodeById: (string) => Nod
}
};

const computeDropPosition = (
event: MouseEvent | React.MouseEvent,
{ x: viewportX, y: viewportY, zoom: viewportZoom }: Viewport,
bounds?: DOMRect
): XYPosition => {
return {
x: (event.clientX - (bounds?.left ?? 0) - viewportX) / viewportZoom,
y: (event.clientY - (bounds?.top ?? 0) - viewportY) / viewportZoom,
};
};

export const useDropNode = (): UseDropNodeValue => {
const {
initialParentId,
Expand All @@ -156,9 +145,6 @@ export const useDropNode = (): UseDropNodeValue => {

const onDropNode = useDropNodeMutation();
const { getNodes, getIntersectingNodes } = useReactFlow<NodeData, EdgeData>();
const viewport = useViewport();
const { domNode } = useStoreApi().getState();
const element = domNode?.getBoundingClientRect();

const getNodeById: (string) => Node | undefined = (id: string) => getNodes().find((n) => n.id === id);

Expand All @@ -170,7 +156,7 @@ export const useDropNode = (): UseDropNodeValue => {
return node;
};

const onNodeDragStart: NodeDragHandler = (_event, node) => {
const onNodeDragStart: NodeDragHandler = useCallback((_event, node) => {
const computedNode = getDraggableNode(node);

const dropDataEntry: GQLDropNodeCompatibility | undefined = diagramDescription.dropNodeCompatibility.find(
Expand All @@ -190,23 +176,32 @@ export const useDropNode = (): UseDropNodeValue => {
compatibleNodeIds: compatibleNodes,
droppableOnDiagram: dropDataEntry?.droppableOnDiagram || false,
});
};

const onNodeDrag: NodeDragHandler = (_event, node) => {
if (draggedNode && !draggedNode.data.isBorderNode) {
const intersections = getIntersectingNodes(node).filter((intersectingNode) => !intersectingNode.hidden);
const newParentId =
[...intersections]
.filter((intersectingNode) => !isDescendantOf(draggedNode, intersectingNode, getNodeById))
.sort((n1, n2) => getNodeDepth(n2, intersections) - getNodeDepth(n1, intersections))[0]?.id || null;
setTargetNodeId(newParentId);
}
};
}, []);

const onNodeDrag: NodeDragHandler = useCallback(
(_event, node) => {
if (draggedNode && !draggedNode.data.isBorderNode) {
console.log('onNodeDragStop');
const intersections = getIntersectingNodes(node).filter((intersectingNode) => !intersectingNode.hidden);
const newParentId =
[...intersections]
.filter((intersectingNode) => !isDescendantOf(draggedNode, intersectingNode, getNodeById))
.sort((n1, n2) => getNodeDepth(n2, intersections) - getNodeDepth(n1, intersections))[0]?.id || null;
setTargetNodeId(newParentId);
}
},
[draggedNode?.id]
);

const reactflow = useReactFlow<NodeData, EdgeData>();
const onNodeDragStop: (onDragCancelled: (node: Node) => void) => NodeDragHandler = useCallback(
(onDragCancelled) => {
return (event, _node) => {
const dropPosition = computeDropPosition(event, viewport, element);
console.log('onNodeDragStop');
const dropPosition = reactflow.screenToFlowPosition({
x: event.clientX,
y: event.clientY,
});
if (draggedNode) {
const oldParentId: string | null = draggedNode.parentNode || null;
const newParentId: string | null = targetNodeId;
Expand All @@ -228,7 +223,7 @@ export const useDropNode = (): UseDropNodeValue => {
resetDrop();
};
},
[element?.top, element?.left, viewport, draggedNode, targetNodeId, droppableOnDiagram, compatibleNodeIds]
[draggedNode?.id, targetNodeId, droppableOnDiagram, compatibleNodeIds.join('-')]
);

const hasDroppedNodeParentChanged = (): boolean => {
Expand Down

0 comments on commit 3ae3f71

Please sign in to comment.