Merge pull request #504 from wbkd/develop

Develop
This commit is contained in:
Moritz
2020-09-11 17:07:10 +02:00
committed by GitHub
7 changed files with 65 additions and 69 deletions
+28 -48
View File
@@ -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}
+7
View File
@@ -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]
+2
View File
@@ -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}
+5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
} }