Graph Prop: isInteractive (#56)

* feat(props): add isInteractive

* refactor(selection): dont allow if not interactive

* feat(renderer): add class if is interactive

* chore(inactive): add tests

* refactor(inactive): no connection line, no edge selection

closes #49
This commit is contained in:
Moritz
2019-10-23 18:42:17 +02:00
committed by GitHub
parent cf4bd7199a
commit 873fb50b19
28 changed files with 2528 additions and 377 deletions
+6 -10
View File
@@ -10,6 +10,7 @@ interface ConnectionLineProps {
connectionLineType?: string | null;
nodes: Node[];
transform: Transform;
isInteractive: boolean;
connectionLineStyle?: SVGAttributes<{}>;
className?: string;
}
@@ -23,6 +24,7 @@ export default ({
nodes = [],
className,
transform,
isInteractive,
}: ConnectionLineProps) => {
const [sourceNode, setSourceNode] = useState<Node | null>(null);
const hasHandleId = connectionSourceId.includes('__');
@@ -35,23 +37,17 @@ export default ({
setSourceNode(nextSourceNode);
}, []);
if (!sourceNode) {
if (!sourceNode || !isInteractive) {
return null;
}
const edgeClasses: string = cx('react-flow__edge', 'connection', className);
const sourceHandle = handleId
? sourceNode.__rg.handleBounds.source.find(
(d: HandleElement) => d.id === handleId
)
? sourceNode.__rg.handleBounds.source.find((d: HandleElement) => d.id === handleId)
: sourceNode.__rg.handleBounds.source[0];
const sourceHandleX = sourceHandle
? sourceHandle.x + sourceHandle.width / 2
: sourceNode.__rg.width / 2;
const sourceHandleY = sourceHandle
? sourceHandle.y + sourceHandle.height / 2
: sourceNode.__rg.height;
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : sourceNode.__rg.width / 2;
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.__rg.height;
const sourceX = sourceNode.__rg.position.x + sourceHandleX;
const sourceY = sourceNode.__rg.position.y + sourceHandleY;
+3 -11
View File
@@ -13,23 +13,15 @@ interface EdgeWrapperProps {
onClick: (edge: Edge) => void;
animated: boolean;
selected: boolean;
isInteractive: boolean;
}
export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
const EdgeWrapper = memo(
({
id,
source,
target,
type,
animated,
selected,
onClick,
...rest
}: EdgeWrapperProps) => {
({ id, source, target, type, animated, selected, onClick, isInteractive, ...rest }: EdgeWrapperProps) => {
const edgeClasses = cx('react-flow__edge', { selected, animated });
const onEdgeClick = (evt: MouseEvent): void => {
if (isInputDOMNode(evt)) {
if (isInputDOMNode(evt) || !isInteractive) {
return;
}
+12 -72
View File
@@ -1,11 +1,4 @@
import React, {
useEffect,
useRef,
useState,
memo,
ComponentType,
CSSProperties,
} from 'react';
import React, { useEffect, useRef, useState, memo, ComponentType } from 'react';
import { DraggableCore, DraggableEvent } from 'react-draggable';
import cx from 'classnames';
import { ResizeObserver } from 'resize-observer';
@@ -21,21 +14,9 @@ import {
Transform,
ElementId,
NodeComponentProps,
WrapNodeProps,
} from '../../types';
interface WrapNodeProps {
id: ElementId;
type: string;
data: any;
selected: boolean;
transform: Transform;
xPos: number;
yPos: number;
onClick: (node: Node) => void | undefined;
onNodeDragStop: (node: Node) => void;
style?: CSSProperties;
}
const isHandle = (evt: MouseEvent | DraggableEvent) => {
const target = evt.target as HTMLElement;
@@ -65,17 +46,13 @@ const getHandleBounds = (
const bounds = handle.getBoundingClientRect();
const dimensions = getDimensions(handle);
const nodeIdAttr = handle.getAttribute('data-nodeid');
const handlePosition = (handle.getAttribute(
'data-handlepos'
) as unknown) as Position;
const handlePosition = (handle.getAttribute('data-handlepos') as unknown) as Position;
const nodeIdSplitted = nodeIdAttr ? nodeIdAttr.split('__') : null;
let handleId = null;
if (nodeIdSplitted) {
handleId = (nodeIdSplitted.length
? nodeIdSplitted[1]
: nodeIdSplitted) as string;
handleId = (nodeIdSplitted.length ? nodeIdSplitted[1] : nodeIdSplitted) as string;
}
return {
@@ -171,6 +148,7 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
onClick,
onNodeDragStop,
style,
isInteractive,
}: WrapNodeProps) => {
const nodeElement = useRef<HTMLDivElement>(null);
const [offset, setOffset] = useState({ x: 0, y: 0 });
@@ -192,18 +170,8 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
const bounds = nodeElement.current.getBoundingClientRect();
const dimensions = getDimensions(nodeElement.current);
const handleBounds = {
source: getHandleBounds(
'.source',
nodeElement.current,
bounds,
storeState.transform[2]
),
target: getHandleBounds(
'.target',
nodeElement.current,
bounds,
storeState.transform[2]
),
source: getHandleBounds('.source', nodeElement.current, bounds, storeState.transform[2]),
target: getHandleBounds('.target', nodeElement.current, bounds, storeState.transform[2]),
};
store.dispatch.updateNodeData({ id, ...dimensions, handleBounds });
};
@@ -232,43 +200,15 @@ export default (NodeComponent: ComponentType<NodeComponentProps>) => {
return (
<DraggableCore
onStart={evt =>
onStart(
evt as MouseEvent,
onClick,
id,
type,
data,
setOffset,
transform,
position
)
}
onDrag={evt =>
onDrag(evt as MouseEvent, setDragging, id, offset, transform)
}
onStop={() =>
onStop(
onNodeDragStop,
isDragging,
setDragging,
id,
type,
position,
data
)
}
onStart={evt => onStart(evt as MouseEvent, onClick, id, type, data, setOffset, transform, position)}
onDrag={evt => onDrag(evt as MouseEvent, setDragging, id, offset, transform)}
onStop={() => onStop(onNodeDragStop, isDragging, setDragging, id, type, position, data)}
scale={transform[2]}
disabled={!isInteractive}
>
<div className={nodeClasses} ref={nodeElement} style={nodeStyle}>
<Provider value={id}>
<NodeComponent
id={id}
data={data}
type={type}
style={style}
selected={selected}
/>
<NodeComponent id={id} data={data} type={type} style={style} selected={selected} />
</Provider>
</div>
</DraggableCore>
+11 -7
View File
@@ -3,6 +3,10 @@ import React, { useEffect, useRef, useState, memo } from 'react';
import { useStoreActions } from '../../store/hooks';
import { SelectionRect } from '../../types';
type UserSelectionProps = {
isInteractive: boolean;
};
const initialRect: SelectionRect = {
startX: 0,
startY: 0,
@@ -27,13 +31,17 @@ function getMousePosition(evt: MouseEvent) {
};
}
export default memo(() => {
export default memo(({ isInteractive }: UserSelectionProps) => {
const selectionPane = useRef<HTMLDivElement>(null);
const [rect, setRect] = useState(initialRect);
const setSelection = useStoreActions(a => a.setSelection);
const updateSelection = useStoreActions(a => a.updateSelection);
const setNodesSelection = useStoreActions(a => a.setNodesSelection);
if (!isInteractive) {
return null;
}
useEffect(() => {
function onMouseDown(evt: MouseEvent): void {
const mousePos = getMousePosition(evt);
@@ -70,12 +78,8 @@ export default memo(() => {
...currentRect,
x: negativeX ? mousePos.x : currentRect.x,
y: negativeY ? mousePos.y : currentRect.y,
width: negativeX
? currentRect.startX - mousePos.x
: mousePos.x - currentRect.startX,
height: negativeY
? currentRect.startY - mousePos.y
: mousePos.y - currentRect.startY,
width: negativeX ? currentRect.startX - mousePos.x : mousePos.x - currentRect.startX,
height: negativeY ? currentRect.startY - mousePos.y : mousePos.y - currentRect.startY,
};
updateSelection(nextRect);