@@ -11,16 +11,12 @@ import React, {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import { DraggableCore } from 'react-draggable';
|
import { DraggableCore } from 'react-draggable';
|
||||||
import cc from 'classcat';
|
import cc from 'classcat';
|
||||||
|
|
||||||
import { ResizeObserver } from 'resize-observer';
|
import { ResizeObserver } from 'resize-observer';
|
||||||
import { useStoreActions } from '../../store/hooks';
|
|
||||||
|
|
||||||
|
import { useStoreActions } from '../../store/hooks';
|
||||||
import { Provider } from '../../contexts/NodeIdContext';
|
import { Provider } from '../../contexts/NodeIdContext';
|
||||||
import { Node, NodeComponentProps, WrapNodeProps } from '../../types';
|
import { Node, NodeComponentProps, WrapNodeProps } from '../../types';
|
||||||
|
|
||||||
const getMouseEvent = (event: MouseEvent | TouchEvent) =>
|
|
||||||
typeof TouchEvent !== 'undefined' && event instanceof TouchEvent ? event.touches[0] : (event as MouseEvent);
|
|
||||||
|
|
||||||
export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
||||||
const NodeWrapper = ({
|
const NodeWrapper = ({
|
||||||
id,
|
id,
|
||||||
@@ -47,18 +43,20 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
targetPosition,
|
targetPosition,
|
||||||
isHidden,
|
isHidden,
|
||||||
isInitialized,
|
isInitialized,
|
||||||
|
snapToGrid,
|
||||||
|
snapGrid,
|
||||||
}: WrapNodeProps) => {
|
}: WrapNodeProps) => {
|
||||||
const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions);
|
const updateNodeDimensions = useStoreActions((actions) => actions.updateNodeDimensions);
|
||||||
const setSelectedElements = useStoreActions((actions) => actions.setSelectedElements);
|
const setSelectedElements = useStoreActions((actions) => actions.setSelectedElements);
|
||||||
const updateNodePos = useStoreActions((actions) => actions.updateNodePos);
|
const updateNodePosDiff = useStoreActions((actions) => actions.updateNodePosDiff);
|
||||||
const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
|
const unsetNodesSelection = useStoreActions((actions) => actions.unsetNodesSelection);
|
||||||
|
|
||||||
const nodeElement = useRef<HTMLDivElement>(null);
|
const nodeElement = useRef<HTMLDivElement>(null);
|
||||||
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
|
||||||
const [isDragging, setDragging] = useState(false);
|
const [isDragging, setDragging] = useState(false);
|
||||||
|
|
||||||
const position = useMemo(() => ({ x: xPos, y: yPos }), [xPos, yPos]);
|
const node = useMemo(() => ({ id, type, position: { x: xPos, y: yPos }, data }), [id, type, xPos, yPos, data]);
|
||||||
const node = useMemo(() => ({ id, type, position, data }), [id, type, position, data]);
|
const grid = useMemo(() => (snapToGrid ? snapGrid : [1, 1])! as [number, number], [snapToGrid, snapGrid]);
|
||||||
|
|
||||||
const nodeStyle: CSSProperties = useMemo(
|
const nodeStyle: CSSProperties = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
zIndex: selected ? 10 : 3,
|
zIndex: selected ? 10 : 3,
|
||||||
@@ -109,9 +107,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
setSelectedElements({ id: node.id, type: node.type } as Node);
|
setSelectedElements({ id: node.id, type: node.type } as Node);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onClick) {
|
onClick?.(event, node);
|
||||||
onClick(event, node);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isSelectable, isDraggable, onClick, node]
|
[isSelectable, isDraggable, onClick, node]
|
||||||
@@ -119,66 +115,51 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
|
|
||||||
const onDragStart = useCallback(
|
const onDragStart = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
const startEvent = getMouseEvent(event);
|
onNodeDragStart?.(event as MouseEvent, node);
|
||||||
|
|
||||||
const scaledClientX = startEvent.clientX / transform[2];
|
|
||||||
const scaledClientY = startEvent.clientY / transform[2];
|
|
||||||
|
|
||||||
const offsetX = scaledClientX - position.x - transform[0];
|
|
||||||
const offsetY = scaledClientY - position.y - transform[1];
|
|
||||||
|
|
||||||
setOffset({ x: offsetX, y: offsetY });
|
|
||||||
|
|
||||||
if (onNodeDragStart) {
|
|
||||||
onNodeDragStart(event as MouseEvent, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectNodesOnDrag && isSelectable) {
|
if (selectNodesOnDrag && isSelectable) {
|
||||||
unsetNodesSelection();
|
unsetNodesSelection();
|
||||||
setSelectedElements({ id: node.id, type: node.type } as Node);
|
setSelectedElements({ id: node.id, type: node.type } as Node);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[node, transform, position, selectNodesOnDrag, isSelectable, onNodeDragStart]
|
[node, transform, selectNodesOnDrag, isSelectable, onNodeDragStart]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDrag = useCallback(
|
const onDrag = useCallback(
|
||||||
(event) => {
|
(_, data) => {
|
||||||
const dragEvent = getMouseEvent(event);
|
|
||||||
|
|
||||||
const scaledClientX = dragEvent.clientX / transform[2];
|
|
||||||
const scaledClientY = dragEvent.clientY / transform[2];
|
|
||||||
|
|
||||||
setDragging(true);
|
setDragging(true);
|
||||||
updateNodePos({
|
updateNodePosDiff({
|
||||||
id,
|
id,
|
||||||
pos: {
|
diff: {
|
||||||
x: scaledClientX - transform[0] - offset.x,
|
x: data.deltaX,
|
||||||
y: scaledClientY - transform[1] - offset.y,
|
y: data.deltaY,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[id, transform, offset]
|
[id, transform]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDragStop = useCallback(
|
const onDragStop = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
|
// onDragStop also gets called when user just clicks on a node.
|
||||||
|
// Because of that we set dragging to true inside the onDrag handler and handle the click here
|
||||||
if (!isDragging) {
|
if (!isDragging) {
|
||||||
if (isSelectable && !selectNodesOnDrag) {
|
if (isSelectable && !selectNodesOnDrag) {
|
||||||
setSelectedElements({ id: node.id, type: node.type } as Node);
|
setSelectedElements({ id: node.id, type: node.type } as Node);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onClick) {
|
onClick?.(event as MouseEvent, node);
|
||||||
onClick(event as MouseEvent, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setDragging(false);
|
setDragging(false);
|
||||||
|
updateNodePosDiff({
|
||||||
|
id,
|
||||||
|
isDragging: false,
|
||||||
|
});
|
||||||
|
|
||||||
if (onNodeDragStop) {
|
onNodeDragStop?.(event as MouseEvent, node);
|
||||||
onNodeDragStop(event as MouseEvent, node);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[node, isDragging, isSelectable, selectNodesOnDrag, onClick, onNodeDragStop]
|
[node, isDragging, isSelectable, selectNodesOnDrag, onClick, onNodeDragStop]
|
||||||
);
|
);
|
||||||
@@ -187,11 +168,9 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
if (nodeElement.current) {
|
if (nodeElement.current) {
|
||||||
updateNodeDimensions({ id, nodeElement: nodeElement.current });
|
updateNodeDimensions({ id, nodeElement: nodeElement.current });
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
for (let _ of entries) {
|
if (nodeElement.current) {
|
||||||
if (nodeElement.current) {
|
updateNodeDimensions({ id, nodeElement: nodeElement.current });
|
||||||
updateNodeDimensions({ id, nodeElement: nodeElement.current });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -230,6 +209,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
|
|||||||
disabled={!isDraggable}
|
disabled={!isDraggable}
|
||||||
cancel=".nodrag"
|
cancel=".nodrag"
|
||||||
nodeRef={nodeElement}
|
nodeRef={nodeElement}
|
||||||
|
grid={grid}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={nodeClasses}
|
className={nodeClasses}
|
||||||
|
|||||||
@@ -73,6 +73,13 @@ export default ({
|
|||||||
|
|
||||||
const onStop = useCallback(
|
const onStop = useCallback(
|
||||||
(event: MouseEvent) => {
|
(event: MouseEvent) => {
|
||||||
|
selectedNodes?.forEach((node) => {
|
||||||
|
updateNodePosDiff({
|
||||||
|
id: node.id,
|
||||||
|
isDragging: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
onSelectionDragStop?.(event, selectedNodes);
|
onSelectionDragStop?.(event, selectedNodes);
|
||||||
},
|
},
|
||||||
[selectedNodes, onSelectionDragStop]
|
[selectedNodes, onSelectionDragStop]
|
||||||
|
|||||||
@@ -279,6 +279,8 @@ const GraphView = ({
|
|||||||
onNodeDragStart={onNodeDragStart}
|
onNodeDragStart={onNodeDragStart}
|
||||||
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
|
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
|
||||||
selectNodesOnDrag={selectNodesOnDrag}
|
selectNodesOnDrag={selectNodesOnDrag}
|
||||||
|
snapToGrid={snapToGrid}
|
||||||
|
snapGrid={snapGrid}
|
||||||
/>
|
/>
|
||||||
<EdgeRenderer
|
<EdgeRenderer
|
||||||
edgeTypes={edgeTypes}
|
edgeTypes={edgeTypes}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ interface NodeRendererProps {
|
|||||||
onNodeDragStart?: (event: MouseEvent, node: Node) => void;
|
onNodeDragStart?: (event: MouseEvent, node: Node) => void;
|
||||||
onNodeDragStop?: (event: MouseEvent, node: Node) => void;
|
onNodeDragStop?: (event: MouseEvent, node: Node) => void;
|
||||||
onlyRenderVisibleNodes?: boolean;
|
onlyRenderVisibleNodes?: boolean;
|
||||||
|
snapToGrid?: boolean;
|
||||||
|
snapGrid?: [number, number];
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNode(
|
function renderNode(
|
||||||
@@ -47,6 +49,7 @@ function renderNode(
|
|||||||
data={node.data}
|
data={node.data}
|
||||||
xPos={node.__rf.position.x}
|
xPos={node.__rf.position.x}
|
||||||
yPos={node.__rf.position.y}
|
yPos={node.__rf.position.y}
|
||||||
|
isDragging={node.__rf.isDragging}
|
||||||
onClick={props.onElementClick}
|
onClick={props.onElementClick}
|
||||||
onMouseEnter={props.onNodeMouseEnter}
|
onMouseEnter={props.onNodeMouseEnter}
|
||||||
onMouseMove={props.onNodeMouseMove}
|
onMouseMove={props.onNodeMouseMove}
|
||||||
@@ -66,6 +69,8 @@ function renderNode(
|
|||||||
selectNodesOnDrag={props.selectNodesOnDrag}
|
selectNodesOnDrag={props.selectNodesOnDrag}
|
||||||
isHidden={node.isHidden}
|
isHidden={node.isHidden}
|
||||||
isInitialized={isInitialized}
|
isInitialized={isInitialized}
|
||||||
|
snapGrid={props.snapGrid}
|
||||||
|
snapToGrid={props.snapToGrid}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-18
@@ -227,11 +227,9 @@ export const storeModel: StoreModel = {
|
|||||||
|
|
||||||
state.elements.forEach((n) => {
|
state.elements.forEach((n) => {
|
||||||
if (n.id === id && isNode(n)) {
|
if (n.id === id && isNode(n)) {
|
||||||
n.__rf = {
|
n.__rf.width = dimensions.width;
|
||||||
...n.__rf,
|
n.__rf.height = dimensions.height;
|
||||||
...dimensions,
|
n.__rf.handleBounds = handleBounds;
|
||||||
handleBounds,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
@@ -249,24 +247,21 @@ export const storeModel: StoreModel = {
|
|||||||
|
|
||||||
state.elements.forEach((n) => {
|
state.elements.forEach((n) => {
|
||||||
if (n.id === id && isNode(n)) {
|
if (n.id === id && isNode(n)) {
|
||||||
n.__rf = {
|
n.__rf.position = position;
|
||||||
...n.__rf,
|
|
||||||
position,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateNodePosDiff: action((state, { id, diff }) => {
|
updateNodePosDiff: action((state, { id, diff = null, isDragging = true }) => {
|
||||||
state.elements.forEach((n) => {
|
state.elements.forEach((n) => {
|
||||||
if (n.id === id && isNode(n)) {
|
if (n.id === id && isNode(n)) {
|
||||||
n.__rf = {
|
if (diff) {
|
||||||
...n.__rf,
|
n.__rf.position = {
|
||||||
position: {
|
|
||||||
x: n.__rf.position.x + diff.x,
|
x: n.__rf.position.x + diff.x,
|
||||||
y: n.__rf.position.y + diff.y,
|
y: n.__rf.position.y + diff.y,
|
||||||
},
|
};
|
||||||
};
|
}
|
||||||
|
n.__rf.isDragging = isDragging;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
@@ -316,7 +311,7 @@ export const storeModel: StoreModel = {
|
|||||||
|
|
||||||
if (!selectedNodes) {
|
if (!selectedNodes) {
|
||||||
state.selectionActive = false;
|
state.selectionActive = false;
|
||||||
state.userSelectionRect = { ...state.userSelectionRect, draw: false };
|
state.userSelectionRect.draw = false;
|
||||||
state.nodesSelectionActive = false;
|
state.nodesSelectionActive = false;
|
||||||
state.selectedElements = null;
|
state.selectedElements = null;
|
||||||
|
|
||||||
@@ -328,7 +323,7 @@ export const storeModel: StoreModel = {
|
|||||||
state.nodesSelectionActive = true;
|
state.nodesSelectionActive = true;
|
||||||
state.selectedNodesBbox = selectedNodesBbox;
|
state.selectedNodesBbox = selectedNodesBbox;
|
||||||
|
|
||||||
state.userSelectionRect = { ...state.userSelectionRect, draw: false };
|
state.userSelectionRect.draw = false;
|
||||||
state.selectionActive = false;
|
state.selectionActive = false;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -454,7 +449,9 @@ export const storeModel: StoreModel = {
|
|||||||
// we need to sync the d3 zoom transform with the fitted transform
|
// we need to sync the d3 zoom transform with the fitted transform
|
||||||
d3Selection.property('__zoom', fittedTransform);
|
d3Selection.property('__zoom', fittedTransform);
|
||||||
|
|
||||||
state.transform = [fittedTransform.x, fittedTransform.y, fittedTransform.k];
|
state.transform[0] = fittedTransform.x;
|
||||||
|
state.transform[1] = fittedTransform.y;
|
||||||
|
state.transform[2] = fittedTransform.k;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
zoomTo: action((state, zoomLevel) => {
|
zoomTo: action((state, zoomLevel) => {
|
||||||
|
|||||||
+5
-1
@@ -172,6 +172,9 @@ export interface WrapNodeProps {
|
|||||||
targetPosition?: Position;
|
targetPosition?: Position;
|
||||||
isHidden?: boolean;
|
isHidden?: boolean;
|
||||||
isInitialized?: boolean;
|
isInitialized?: boolean;
|
||||||
|
snapToGrid?: boolean;
|
||||||
|
snapGrid?: [number, number];
|
||||||
|
isDragging?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FitViewParams = {
|
export type FitViewParams = {
|
||||||
@@ -269,7 +272,8 @@ export type NodePosUpdate = {
|
|||||||
|
|
||||||
export type NodeDiffUpdate = {
|
export type NodeDiffUpdate = {
|
||||||
id: ElementId;
|
id: ElementId;
|
||||||
diff: XYPosition;
|
diff?: XYPosition;
|
||||||
|
isDragging?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FlowTransform = {
|
export type FlowTransform = {
|
||||||
|
|||||||
+3
-2
@@ -126,6 +126,7 @@ export const parseElement = (element: Node | Edge): Node | Edge => {
|
|||||||
width: null,
|
width: null,
|
||||||
height: null,
|
height: null,
|
||||||
handleBounds: {},
|
handleBounds: {},
|
||||||
|
isDragging: false,
|
||||||
},
|
},
|
||||||
} as Node;
|
} as Node;
|
||||||
};
|
};
|
||||||
@@ -182,13 +183,13 @@ export const getNodesInside = (
|
|||||||
height: rect.height / tScale,
|
height: rect.height / tScale,
|
||||||
});
|
});
|
||||||
|
|
||||||
return nodes.filter(({ __rf: { position, width, height } }) => {
|
return nodes.filter(({ __rf: { position, width, height, isDragging } }) => {
|
||||||
const nBox = rectToBox({ ...position, width, height });
|
const nBox = rectToBox({ ...position, width, height });
|
||||||
const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x));
|
const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x));
|
||||||
const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y));
|
const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y));
|
||||||
const overlappingArea = xOverlap * yOverlap;
|
const overlappingArea = xOverlap * yOverlap;
|
||||||
|
|
||||||
if (width === null || height === null) {
|
if (width === null || height === null || isDragging) {
|
||||||
// at the beginnning all nodes have width & height === 0
|
// at the beginnning all nodes have width & height === 0
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user