refactor(components): show displayName in react profiler correctly

This commit is contained in:
moklick
2020-07-27 18:50:53 +02:00
parent 05786fcee6
commit 30e811225a
15 changed files with 643 additions and 649 deletions
+33 -28
View File
@@ -19,38 +19,43 @@ const defaultColors = {
[BackgroundVariant.Lines]: '#eee', [BackgroundVariant.Lines]: '#eee',
}; };
const Background = memo( const Background = ({
({ variant = BackgroundVariant.Dots, gap = 24, size = 0.5, color, style, className }: BackgroundProps) => { variant = BackgroundVariant.Dots,
const [x, y, scale] = useStoreState((s) => s.transform); gap = 24,
size = 0.5,
color,
style,
className,
}: BackgroundProps) => {
const [x, y, scale] = useStoreState((s) => s.transform);
const bgClasses = cc(['react-flow__background', className]); const bgClasses = cc(['react-flow__background', className]);
const scaledGap = gap * scale; const scaledGap = gap * scale;
const xOffset = x % scaledGap; const xOffset = x % scaledGap;
const yOffset = y % scaledGap; const yOffset = y % scaledGap;
const bgSvgTile = useMemo(() => { const bgSvgTile = useMemo(() => {
const isLines = variant === BackgroundVariant.Lines; const isLines = variant === BackgroundVariant.Lines;
const bgColor = color ? color : defaultColors[variant]; const bgColor = color ? color : defaultColors[variant];
const path = isLines ? createGridLinesPath(scaledGap, size, bgColor) : createGridDotsPath(size, bgColor); const path = isLines ? createGridLinesPath(scaledGap, size, bgColor) : createGridDotsPath(size, bgColor);
return encodeURIComponent( return encodeURIComponent(
`<svg width="${scaledGap}" height="${scaledGap}" xmlns='http://www.w3.org/2000/svg'>${path}</svg>` `<svg width="${scaledGap}" height="${scaledGap}" xmlns='http://www.w3.org/2000/svg'>${path}</svg>`
);
}, [variant, scaledGap, size, color]);
return (
<div
className={bgClasses}
style={{
...style,
backgroundImage: `url("data:image/svg+xml;utf8,${bgSvgTile}")`,
backgroundPosition: `${xOffset}px ${yOffset}px`,
}}
></div>
); );
} }, [variant, scaledGap, size, color]);
);
return (
<div
className={bgClasses}
style={{
...style,
backgroundImage: `url("data:image/svg+xml;utf8,${bgSvgTile}")`,
backgroundPosition: `${xOffset}px ${yOffset}px`,
}}
></div>
);
};
Background.displayName = 'Background'; Background.displayName = 'Background';
export default Background; export default memo(Background);
+37 -39
View File
@@ -17,49 +17,47 @@ interface ControlProps extends React.HTMLAttributes<HTMLDivElement> {
showInteractive?: boolean; showInteractive?: boolean;
} }
const Controls = memo( const Controls = ({ style, showZoom = true, showFitView = true, showInteractive = true, className }: ControlProps) => {
({ style, showZoom = true, showFitView = true, showInteractive = true, className }: ControlProps) => { const setInteractive = useStoreActions((actions) => actions.setInteractive);
const setInteractive = useStoreActions((actions) => actions.setInteractive); const fitView = useStoreActions((actions) => actions.fitView);
const fitView = useStoreActions((actions) => actions.fitView); const zoomIn = useStoreActions((actions) => actions.zoomIn);
const zoomIn = useStoreActions((actions) => actions.zoomIn); const zoomOut = useStoreActions((actions) => actions.zoomOut);
const zoomOut = useStoreActions((actions) => actions.zoomOut);
const isInteractive = useStoreState((s) => s.nodesDraggable && s.nodesConnectable && s.elementsSelectable); const isInteractive = useStoreState((s) => s.nodesDraggable && s.nodesConnectable && s.elementsSelectable);
const mapClasses = cc(['react-flow__controls', className]); const mapClasses = cc(['react-flow__controls', className]);
return ( return (
<div className={mapClasses} style={style}> <div className={mapClasses} style={style}>
{showZoom && ( {showZoom && (
<> <>
<div className="react-flow__controls-button react-flow__controls-zoomin" onClick={() => zoomIn()}> <div className="react-flow__controls-button react-flow__controls-zoomin" onClick={() => zoomIn()}>
<PlusIcon /> <PlusIcon />
</div>
<div className="react-flow__controls-button react-flow__controls-zoomout" onClick={() => zoomOut()}>
<MinusIcon />
</div>
</>
)}
{showFitView && (
<div
className="react-flow__controls-button react-flow__controls-fitview"
onClick={() => fitView({ padding: 0.1 })}
>
<FitviewIcon />
</div> </div>
)} <div className="react-flow__controls-button react-flow__controls-zoomout" onClick={() => zoomOut()}>
{showInteractive && ( <MinusIcon />
<div
className="react-flow__controls-button react-flow__controls-interactive"
onClick={() => setInteractive(!isInteractive)}
>
{isInteractive ? <UnlockIcon /> : <LockIcon />}
</div> </div>
)} </>
</div> )}
); {showFitView && (
} <div
); className="react-flow__controls-button react-flow__controls-fitview"
onClick={() => fitView({ padding: 0.1 })}
>
<FitviewIcon />
</div>
)}
{showInteractive && (
<div
className="react-flow__controls-button react-flow__controls-interactive"
onClick={() => setInteractive(!isInteractive)}
>
{isInteractive ? <UnlockIcon /> : <LockIcon />}
</div>
)}
</div>
);
};
Controls.displayName = 'Controls'; Controls.displayName = 'Controls';
export default Controls; export default memo(Controls);
@@ -10,7 +10,7 @@ interface MiniMapNodeProps {
style?: CSSProperties; style?: CSSProperties;
} }
const MiniMapNode = memo(({ x, y, width, height, style, color, borderRadius }: MiniMapNodeProps) => { const MiniMapNode = ({ x, y, width, height, style, color, borderRadius }: MiniMapNodeProps) => {
const { background, backgroundColor } = style || {}; const { background, backgroundColor } = style || {};
const fill = (color || background || backgroundColor) as string; const fill = (color || background || backgroundColor) as string;
@@ -26,8 +26,8 @@ const MiniMapNode = memo(({ x, y, width, height, style, color, borderRadius }: M
fill={fill} fill={fill}
/> />
); );
}); };
MiniMapNode.displayName = 'MiniMapNode'; MiniMapNode.displayName = 'MiniMapNode';
export default MiniMapNode; export default memo(MiniMapNode);
+66 -68
View File
@@ -19,77 +19,75 @@ interface MiniMapProps extends React.HTMLAttributes<SVGSVGElement> {
const defaultWidth = 200; const defaultWidth = 200;
const defaultHeight = 150; const defaultHeight = 150;
const MiniMap = memo( const MiniMap = ({
({ style = { backgroundColor: '#f8f8f8' },
style = { backgroundColor: '#f8f8f8' }, className,
className, nodeColor = '#ddd',
nodeColor = '#ddd', nodeBorderRadius = 5,
nodeBorderRadius = 5, maskColor = 'rgba(10, 10, 10, .25)',
maskColor = 'rgba(10, 10, 10, .25)', }: MiniMapProps) => {
}: MiniMapProps) => { const containerWidth = useStoreState((s) => s.width);
const containerWidth = useStoreState((s) => s.width); const containerHeight = useStoreState((s) => s.height);
const containerHeight = useStoreState((s) => s.height); const [tX, tY, tScale] = useStoreState((s) => s.transform);
const [tX, tY, tScale] = useStoreState((s) => s.transform); const nodes = useStoreState((s) => s.nodes);
const nodes = useStoreState((s) => s.nodes);
const mapClasses = cc(['react-flow__minimap', className]); const mapClasses = cc(['react-flow__minimap', className]);
const elementWidth = (style.width || defaultWidth)! as number; const elementWidth = (style.width || defaultWidth)! as number;
const elementHeight = (style.height || defaultHeight)! as number; const elementHeight = (style.height || defaultHeight)! as number;
const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as StringFunc; const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as StringFunc;
const hasNodes = nodes && nodes.length; const hasNodes = nodes && nodes.length;
const bb = getRectOfNodes(nodes); const bb = getRectOfNodes(nodes);
const viewBB: Rect = { const viewBB: Rect = {
x: -tX / tScale, x: -tX / tScale,
y: -tY / tScale, y: -tY / tScale,
width: containerWidth / tScale, width: containerWidth / tScale,
height: containerHeight / tScale, height: containerHeight / tScale,
}; };
const boundingRect = hasNodes ? getBoundsofRects(bb, viewBB) : viewBB; const boundingRect = hasNodes ? getBoundsofRects(bb, viewBB) : viewBB;
const scaledWidth = boundingRect.width / elementWidth; const scaledWidth = boundingRect.width / elementWidth;
const scaledHeight = boundingRect.height / elementHeight; const scaledHeight = boundingRect.height / elementHeight;
const viewScale = Math.max(scaledWidth, scaledHeight); const viewScale = Math.max(scaledWidth, scaledHeight);
const viewWidth = viewScale * elementWidth; const viewWidth = viewScale * elementWidth;
const viewHeight = viewScale * elementHeight; const viewHeight = viewScale * elementHeight;
const offset = 5 * viewScale; const offset = 5 * viewScale;
const x = boundingRect.x - (viewWidth - boundingRect.width) / 2 - offset; const x = boundingRect.x - (viewWidth - boundingRect.width) / 2 - offset;
const y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset; const y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset;
const width = viewWidth + offset * 2; const width = viewWidth + offset * 2;
const height = viewHeight + offset * 2; const height = viewHeight + offset * 2;
return ( return (
<svg <svg
width={elementWidth} width={elementWidth}
height={elementHeight} height={elementHeight}
viewBox={`${x} ${y} ${width} ${height}`} viewBox={`${x} ${y} ${width} ${height}`}
style={style} style={style}
className={mapClasses} className={mapClasses}
> >
{nodes {nodes
.filter((node) => !node.isHidden) .filter((node) => !node.isHidden)
.map((node) => ( .map((node) => (
<MiniMapNode <MiniMapNode
key={node.id} key={node.id}
x={node.__rf.position.x} x={node.__rf.position.x}
y={node.__rf.position.y} y={node.__rf.position.y}
width={node.__rf.width} width={node.__rf.width}
height={node.__rf.height} height={node.__rf.height}
style={node.style} style={node.style}
color={nodeColorFunc(node)} color={nodeColorFunc(node)}
borderRadius={nodeBorderRadius} borderRadius={nodeBorderRadius}
/> />
))} ))}
<path <path
className="react-flow__minimap-mask" className="react-flow__minimap-mask"
d={`M${x - offset},${y - offset}h${width + offset * 2}v${height + offset * 2}h${-width - offset * 2}z d={`M${x - offset},${y - offset}h${width + offset * 2}v${height + offset * 2}h${-width - offset * 2}z
M${viewBB.x},${viewBB.y}h${viewBB.width}v${viewBB.height}h${-viewBB.width}z`} M${viewBB.x},${viewBB.y}h${viewBB.width}v${viewBB.height}h${-viewBB.width}z`}
fill={maskColor} fill={maskColor}
fillRule="evenodd" fillRule="evenodd"
/> />
</svg> </svg>
); );
} };
);
MiniMap.displayName = 'MiniMap'; MiniMap.displayName = 'MiniMap';
export default MiniMap; export default memo(MiniMap);
+56 -58
View File
@@ -23,70 +23,68 @@ interface EdgeWrapperProps {
} }
export default (EdgeComponent: ComponentType<EdgeCompProps>) => { export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
const EdgeWrapper = memo( const EdgeWrapper = ({
({ id,
id, source,
source, target,
target, type,
type, animated,
animated, selected,
selected, onClick,
onClick, elementsSelectable,
elementsSelectable, label,
label, labelStyle,
labelStyle, labelShowBg,
labelShowBg, labelBgStyle,
labelBgStyle, className,
className, isHidden,
isHidden, data,
data, ...rest
...rest }: EdgeWrapperProps) => {
}: EdgeWrapperProps) => { const setSelectedElements = useStoreActions((a) => a.setSelectedElements);
const setSelectedElements = useStoreActions((a) => a.setSelectedElements);
if (isHidden) { if (isHidden) {
return null; return null;
}
const edgeClasses = cc(['react-flow__edge', `react-flow__edge-${type}`, className, { selected, animated }]);
const edgeGroupStyle: CSSProperties = {
pointerEvents: elementsSelectable ? 'all' : 'none',
};
const onEdgeClick = (): void => {
if (!elementsSelectable) {
return;
} }
const edgeClasses = cc(['react-flow__edge', `react-flow__edge-${type}`, className, { selected, animated }]); setSelectedElements({ id, source, target });
const edgeGroupStyle: CSSProperties = {
pointerEvents: elementsSelectable ? 'all' : 'none',
};
const onEdgeClick = (): void => {
if (!elementsSelectable) {
return;
}
setSelectedElements({ id, source, target }); if (onClick) {
onClick({ id, source, target, type });
}
};
if (onClick) { return (
onClick({ id, source, target, type }); <g className={edgeClasses} onClick={onEdgeClick} style={edgeGroupStyle}>
} <EdgeComponent
}; id={id}
source={source}
return ( target={target}
<g className={edgeClasses} onClick={onEdgeClick} style={edgeGroupStyle}> type={type}
<EdgeComponent animated={animated}
id={id} selected={selected}
source={source} onClick={onClick}
target={target} label={label}
type={type} labelStyle={labelStyle}
animated={animated} labelShowBg={labelShowBg}
selected={selected} labelBgStyle={labelBgStyle}
onClick={onClick} data={data}
label={label} {...rest}
labelStyle={labelStyle} />
labelShowBg={labelShowBg} </g>
labelBgStyle={labelBgStyle} );
data={data} };
{...rest}
/>
</g>
);
}
);
EdgeWrapper.displayName = 'EdgeWrapper'; EdgeWrapper.displayName = 'EdgeWrapper';
return EdgeWrapper; return memo(EdgeWrapper);
}; };
+36 -38
View File
@@ -130,47 +130,45 @@ function onMouseDown(
document.addEventListener('mouseup', onMouseUp); document.addEventListener('mouseup', onMouseUp);
} }
const BaseHandle = memo( const BaseHandle = ({
({ type,
type, nodeId,
nodeId, onConnect,
onConnect, position,
position, setConnectionNodeId,
setConnectionNodeId, setPosition,
setPosition, className,
id = false,
isValidConnection,
...rest
}: BaseHandleProps) => {
const isTarget = type === 'target';
const handleClasses = cc([
'react-flow__handle',
`react-flow__handle-${position}`,
'nodrag',
className, className,
id = false, {
isValidConnection, source: !isTarget,
...rest target: isTarget,
}: BaseHandleProps) => { },
const isTarget = type === 'target'; ]);
const handleClasses = cc([
'react-flow__handle',
`react-flow__handle-${position}`,
'nodrag',
className,
{
source: !isTarget,
target: isTarget,
},
]);
const nodeIdWithHandleId = id ? `${nodeId}__${id}` : nodeId; const nodeIdWithHandleId = id ? `${nodeId}__${id}` : nodeId;
return ( return (
<div <div
data-nodeid={nodeIdWithHandleId} data-nodeid={nodeIdWithHandleId}
data-handlepos={position} data-handlepos={position}
className={handleClasses} className={handleClasses}
onMouseDown={(evt) => onMouseDown={(evt) =>
onMouseDown(evt, nodeIdWithHandleId, setConnectionNodeId, setPosition, onConnect, isTarget, isValidConnection) onMouseDown(evt, nodeIdWithHandleId, setConnectionNodeId, setPosition, onConnect, isTarget, isValidConnection)
} }
{...rest} {...rest}
/> />
); );
} };
);
BaseHandle.displayName = 'BaseHandle'; BaseHandle.displayName = 'BaseHandle';
export default BaseHandle; export default memo(BaseHandle);
+35 -37
View File
@@ -7,44 +7,42 @@ import NodeIdContext from '../../contexts/NodeIdContext';
import { HandleProps, ElementId, Position, Connection } from '../../types'; import { HandleProps, ElementId, Position, Connection } from '../../types';
const Handle = memo( const Handle = ({
({ type = 'source',
type = 'source', position = Position.Top,
position = Position.Top, onConnect = () => {},
onConnect = () => {}, isValidConnection = () => true,
isValidConnection = () => true, isConnectable = true,
isConnectable = true, style,
style, className,
className, id,
id, }: HandleProps) => {
}: HandleProps) => { const nodeId = useContext(NodeIdContext) as ElementId;
const nodeId = useContext(NodeIdContext) as ElementId; const setPosition = useStoreActions((a) => a.setConnectionPosition);
const setPosition = useStoreActions((a) => a.setConnectionPosition); const setConnectionNodeId = useStoreActions((a) => a.setConnectionNodeId);
const setConnectionNodeId = useStoreActions((a) => a.setConnectionNodeId); const onConnectAction = useStoreState((s) => s.onConnect);
const onConnectAction = useStoreState((s) => s.onConnect); const onConnectExtended = (params: Connection) => {
const onConnectExtended = (params: Connection) => { onConnectAction(params);
onConnectAction(params); onConnect(params);
onConnect(params); };
}; const handleClasses = cc([className, { connectable: isConnectable }]);
const handleClasses = cc([className, { connectable: isConnectable }]);
return ( return (
<BaseHandle <BaseHandle
className={handleClasses} className={handleClasses}
id={id} id={id}
nodeId={nodeId} nodeId={nodeId}
setPosition={setPosition} setPosition={setPosition}
setConnectionNodeId={setConnectionNodeId} setConnectionNodeId={setConnectionNodeId}
onConnect={onConnectExtended} onConnect={onConnectExtended}
type={type} type={type}
position={position} position={position}
isValidConnection={isValidConnection} isValidConnection={isValidConnection}
style={style} style={style}
/> />
); );
} };
);
Handle.displayName = 'Handle'; Handle.displayName = 'Handle';
export default Handle; export default memo(Handle);
+12 -9
View File
@@ -3,16 +3,19 @@ import React, { memo } from 'react';
import Handle from '../../components/Handle'; import Handle from '../../components/Handle';
import { NodeProps, Position } from '../../types'; import { NodeProps, Position } from '../../types';
const DefaultNode = memo( const DefaultNode = ({
({ data, isConnectable, targetPosition = Position.Top, sourcePosition = Position.Bottom }: NodeProps) => ( data,
<> isConnectable,
<Handle type="target" position={targetPosition} isConnectable={isConnectable} /> targetPosition = Position.Top,
{data.label} sourcePosition = Position.Bottom,
<Handle type="source" position={sourcePosition} isConnectable={isConnectable} /> }: NodeProps) => (
</> <>
) <Handle type="target" position={targetPosition} isConnectable={isConnectable} />
{data.label}
<Handle type="source" position={sourcePosition} isConnectable={isConnectable} />
</>
); );
DefaultNode.displayName = 'DefaultNode'; DefaultNode.displayName = 'DefaultNode';
export default DefaultNode; export default memo(DefaultNode);
+3 -3
View File
@@ -3,13 +3,13 @@ import React, { memo } from 'react';
import Handle from '../../components/Handle'; import Handle from '../../components/Handle';
import { NodeProps, Position } from '../../types'; import { NodeProps, Position } from '../../types';
const InputNode = memo(({ data, isConnectable, sourcePosition = Position.Bottom }: NodeProps) => ( const InputNode = ({ data, isConnectable, sourcePosition = Position.Bottom }: NodeProps) => (
<> <>
{data.label} {data.label}
<Handle type="source" position={sourcePosition} isConnectable={isConnectable} /> <Handle type="source" position={sourcePosition} isConnectable={isConnectable} />
</> </>
)); );
InputNode.displayName = 'InputNode'; InputNode.displayName = 'InputNode';
export default InputNode; export default memo(InputNode);
+3 -3
View File
@@ -3,13 +3,13 @@ import React, { memo } from 'react';
import Handle from '../../components/Handle'; import Handle from '../../components/Handle';
import { NodeProps, Position } from '../../types'; import { NodeProps, Position } from '../../types';
const OutputNode = memo(({ data, isConnectable, targetPosition = Position.Top }: NodeProps) => ( const OutputNode = ({ data, isConnectable, targetPosition = Position.Top }: NodeProps) => (
<> <>
<Handle type="target" position={targetPosition} isConnectable={isConnectable} /> <Handle type="target" position={targetPosition} isConnectable={isConnectable} />
{data.label} {data.label}
</> </>
)); );
OutputNode.displayName = 'OutputNode'; OutputNode.displayName = 'OutputNode';
export default OutputNode; export default memo(OutputNode);
+175 -177
View File
@@ -160,192 +160,190 @@ const onStop = ({
}; };
export default (NodeComponent: ComponentType<NodeComponentProps>) => { export default (NodeComponent: ComponentType<NodeComponentProps>) => {
const NodeWrapper = memo( const NodeWrapper = ({
({ id,
id, type,
type, data,
data, transform,
transform, xPos,
xPos, yPos,
yPos, selected,
selected, onClick,
onClick, onMouseEnter,
onMouseEnter, onMouseMove,
onMouseMove, onMouseLeave,
onMouseLeave, onContextMenu,
onContextMenu, onNodeDragStart,
onNodeDragStart, onNodeDragStop,
onNodeDragStop, style,
style, className,
isDraggable,
isSelectable,
isConnectable,
selectNodesOnDrag,
sourcePosition,
targetPosition,
isHidden,
}: WrapNodeProps) => {
const updateNodeDimensions = useStoreActions((a) => a.updateNodeDimensions);
const setSelectedElements = useStoreActions((a) => a.setSelectedElements);
const updateNodePos = useStoreActions((a) => a.updateNodePos);
const nodeElement = useRef<HTMLDivElement>(null);
const [offset, setOffset] = useState({ x: 0, y: 0 });
const [isDragging, setDragging] = useState(false);
const position = { x: xPos, y: yPos };
const nodeClasses = cc([
'react-flow__node',
`react-flow__node-${type}`,
className, className,
isDraggable, {
isSelectable, selected,
isConnectable, selectable: isSelectable,
selectNodesOnDrag, },
sourcePosition, ]);
targetPosition, const node = { id, type, position, data };
isHidden, const onMouseEnterHandler = useMemo(() => {
}: WrapNodeProps) => { if (!onMouseEnter || isDragging) {
const updateNodeDimensions = useStoreActions((a) => a.updateNodeDimensions);
const setSelectedElements = useStoreActions((a) => a.setSelectedElements);
const updateNodePos = useStoreActions((a) => a.updateNodePos);
const nodeElement = useRef<HTMLDivElement>(null);
const [offset, setOffset] = useState({ x: 0, y: 0 });
const [isDragging, setDragging] = useState(false);
const position = { x: xPos, y: yPos };
const nodeClasses = cc([
'react-flow__node',
`react-flow__node-${type}`,
className,
{
selected,
selectable: isSelectable,
},
]);
const node = { id, type, position, data };
const onMouseEnterHandler = useMemo(() => {
if (!onMouseEnter || isDragging) {
return noop;
}
return (evt: MouseEvent) => onMouseEnter(evt, node);
}, [onMouseEnter, isDragging]);
const onMouseMoveHandler = useMemo(() => {
if (!onMouseMove || isDragging) {
return noop;
}
return (evt: MouseEvent) => onMouseMove(evt, node);
}, [onMouseMove, isDragging]);
const onMouseLeaveHandler = useMemo(() => {
if (!onMouseLeave || isDragging) {
return noop;
}
return (evt: MouseEvent) => onMouseLeave(evt, node);
}, [onMouseLeave, isDragging]);
const onContextMenuHandler = useMemo(() => {
if (!onContextMenu) {
return noop;
}
return (evt: MouseEvent) => onContextMenu(evt, node);
}, [onContextMenu]);
const onSelectNodeHandler = useCallback(() => {
if (!isDraggable && isSelectable) {
setSelectedElements({ id: node.id, type: node.type } as Node);
if (onClick) {
onClick(node);
}
}
return noop; return noop;
}, [isSelectable, isDraggable, node]);
useEffect(() => {
if (nodeElement.current) {
updateNodeDimensions({ id, nodeElement: nodeElement.current });
const resizeObserver = new ResizeObserver((entries) => {
for (let _ of entries) {
updateNodeDimensions({ id, nodeElement: nodeElement.current! });
}
});
resizeObserver.observe(nodeElement.current);
return () => {
if (resizeObserver && nodeElement.current) {
resizeObserver.unobserve(nodeElement.current);
}
};
}
return;
}, [id]);
if (isHidden) {
return null;
} }
const nodeStyle: CSSProperties = { return (evt: MouseEvent) => onMouseEnter(evt, node);
zIndex: selected ? 10 : 3, }, [onMouseEnter, isDragging]);
transform: `translate(${xPos}px,${yPos}px)`,
pointerEvents: isSelectable || isDraggable ? 'all' : 'none',
...style,
};
return ( const onMouseMoveHandler = useMemo(() => {
<DraggableCore if (!onMouseMove || isDragging) {
onStart={(evt) => return noop;
onStart({ }
evt: evt as MouseEvent,
selectNodesOnDrag, return (evt: MouseEvent) => onMouseMove(evt, node);
isSelectable, }, [onMouseMove, isDragging]);
onNodeDragStart,
id, const onMouseLeaveHandler = useMemo(() => {
type, if (!onMouseLeave || isDragging) {
data, return noop;
setOffset, }
transform,
position, return (evt: MouseEvent) => onMouseLeave(evt, node);
setSelectedElements, }, [onMouseLeave, isDragging]);
})
const onContextMenuHandler = useMemo(() => {
if (!onContextMenu) {
return noop;
}
return (evt: MouseEvent) => onContextMenu(evt, node);
}, [onContextMenu]);
const onSelectNodeHandler = useCallback(() => {
if (!isDraggable && isSelectable) {
setSelectedElements({ id: node.id, type: node.type } as Node);
if (onClick) {
onClick(node);
}
}
return noop;
}, [isSelectable, isDraggable, node]);
useEffect(() => {
if (nodeElement.current) {
updateNodeDimensions({ id, nodeElement: nodeElement.current });
const resizeObserver = new ResizeObserver((entries) => {
for (let _ of entries) {
updateNodeDimensions({ id, nodeElement: nodeElement.current! });
} }
onDrag={(evt) => onDrag({ evt: evt as MouseEvent, setDragging, id, offset, transform, updateNodePos })} });
onStop={() =>
onStop({ resizeObserver.observe(nodeElement.current);
onNodeDragStop,
selectNodesOnDrag, return () => {
isSelectable, if (resizeObserver && nodeElement.current) {
onClick, resizeObserver.unobserve(nodeElement.current);
isDragging,
setDragging,
id,
type,
position,
data,
setSelectedElements,
})
} }
scale={transform[2]} };
disabled={!isDraggable} }
cancel=".nodrag"
> return;
<div }, [id]);
className={nodeClasses}
ref={nodeElement} if (isHidden) {
style={nodeStyle} return null;
onMouseEnter={onMouseEnterHandler}
onMouseMove={onMouseMoveHandler}
onMouseLeave={onMouseLeaveHandler}
onContextMenu={onContextMenuHandler}
onClick={onSelectNodeHandler}
>
<Provider value={id}>
<NodeComponent
id={id}
data={data}
type={type}
selected={selected}
isConnectable={isConnectable}
sourcePosition={sourcePosition}
targetPosition={targetPosition}
/>
</Provider>
</div>
</DraggableCore>
);
} }
);
const nodeStyle: CSSProperties = {
zIndex: selected ? 10 : 3,
transform: `translate(${xPos}px,${yPos}px)`,
pointerEvents: isSelectable || isDraggable ? 'all' : 'none',
...style,
};
return (
<DraggableCore
onStart={(evt) =>
onStart({
evt: evt as MouseEvent,
selectNodesOnDrag,
isSelectable,
onNodeDragStart,
id,
type,
data,
setOffset,
transform,
position,
setSelectedElements,
})
}
onDrag={(evt) => onDrag({ evt: evt as MouseEvent, setDragging, id, offset, transform, updateNodePos })}
onStop={() =>
onStop({
onNodeDragStop,
selectNodesOnDrag,
isSelectable,
onClick,
isDragging,
setDragging,
id,
type,
position,
data,
setSelectedElements,
})
}
scale={transform[2]}
disabled={!isDraggable}
cancel=".nodrag"
>
<div
className={nodeClasses}
ref={nodeElement}
style={nodeStyle}
onMouseEnter={onMouseEnterHandler}
onMouseMove={onMouseMoveHandler}
onMouseLeave={onMouseLeaveHandler}
onContextMenu={onContextMenuHandler}
onClick={onSelectNodeHandler}
>
<Provider value={id}>
<NodeComponent
id={id}
data={data}
type={type}
selected={selected}
isConnectable={isConnectable}
sourcePosition={sourcePosition}
targetPosition={targetPosition}
/>
</Provider>
</div>
</DraggableCore>
);
};
NodeWrapper.displayName = 'NodeWrapper'; NodeWrapper.displayName = 'NodeWrapper';
return NodeWrapper; return memo(NodeWrapper);
}; };
+3 -3
View File
@@ -3,7 +3,7 @@
* made a selectio with on or several nodes * made a selectio with on or several nodes
*/ */
import React, { useState, memo } from 'react'; import React, { useState } from 'react';
import ReactDraggable from 'react-draggable'; import ReactDraggable from 'react-draggable';
import { useStoreState, useStoreActions } from '../../store/hooks'; import { useStoreState, useStoreActions } from '../../store/hooks';
@@ -27,7 +27,7 @@ function getStartPositions(nodes: Node[]): StartPositions {
}, startPositions); }, startPositions);
} }
export default memo(() => { export default () => {
const [offset, setOffset] = useState<XYPosition>({ x: 0, y: 0 }); const [offset, setOffset] = useState<XYPosition>({ x: 0, y: 0 });
const [startPositions, setStartPositions] = useState<StartPositions>({}); const [startPositions, setStartPositions] = useState<StartPositions>({});
const [tX, tY, tScale] = useStoreState((s) => s.transform); const [tX, tY, tScale] = useStoreState((s) => s.transform);
@@ -110,4 +110,4 @@ export default memo(() => {
</ReactDraggable> </ReactDraggable>
</div> </div>
); );
}); };
+3 -3
View File
@@ -193,7 +193,7 @@ function renderEdge(
); );
} }
const EdgeRenderer = memo((props: EdgeRendererProps) => { const EdgeRenderer = (props: EdgeRendererProps) => {
const [tX, tY, tScale] = useStoreState((s) => s.transform); const [tX, tY, tScale] = useStoreState((s) => s.transform);
const edges = useStoreState((s) => s.edges); const edges = useStoreState((s) => s.edges);
const nodes = useStoreState((s) => s.nodes); const nodes = useStoreState((s) => s.nodes);
@@ -234,8 +234,8 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
</g> </g>
</svg> </svg>
); );
}); };
EdgeRenderer.displayName = 'EdgeRenderer'; EdgeRenderer.displayName = 'EdgeRenderer';
export default EdgeRenderer; export default memo(EdgeRenderer);
+175 -177
View File
@@ -62,200 +62,198 @@ export interface GraphViewProps {
zoomOnDoubleClick: boolean; zoomOnDoubleClick: boolean;
} }
const GraphView = memo( const GraphView = ({
({ nodeTypes,
nodeTypes, edgeTypes,
edgeTypes, onMove,
onMoveStart,
onMoveEnd,
onLoad,
onElementClick,
onNodeMouseEnter,
onNodeMouseMove,
onNodeMouseLeave,
onNodeContextMenu,
onNodeDragStart,
onNodeDragStop,
connectionLineType,
connectionLineStyle,
selectionKeyCode,
onElementsRemove,
deleteKeyCode,
elements,
onConnect,
snapToGrid,
snapGrid,
onlyRenderVisibleNodes,
nodesDraggable,
nodesConnectable,
elementsSelectable,
selectNodesOnDrag,
minZoom,
maxZoom,
defaultZoom,
defaultPosition,
arrowHeadColor,
markerEndId,
zoomOnScroll,
zoomOnDoubleClick,
}: GraphViewProps) => {
const zoomPane = useRef<HTMLDivElement>(null);
const rendererNode = useRef<HTMLDivElement>(null);
const width = useStoreState((s) => s.width);
const height = useStoreState((s) => s.height);
const d3Initialised = useStoreState((s) => s.d3Initialised);
const nodesSelectionActive = useStoreState((s) => s.nodesSelectionActive);
const updateSize = useStoreActions((actions) => actions.updateSize);
const setNodesSelection = useStoreActions((actions) => actions.setNodesSelection);
const setOnConnect = useStoreActions((a) => a.setOnConnect);
const setSnapGrid = useStoreActions((actions) => actions.setSnapGrid);
const setNodesDraggable = useStoreActions((actions) => actions.setNodesDraggable);
const setNodesConnectable = useStoreActions((actions) => actions.setNodesConnectable);
const setElementsSelectable = useStoreActions((actions) => actions.setElementsSelectable);
const setInitTransform = useStoreActions((actions) => actions.setInitTransform);
const setMinMaxZoom = useStoreActions((actions) => actions.setMinMaxZoom);
const fitView = useStoreActions((actions) => actions.fitView);
const zoom = useStoreActions((actions) => actions.zoom);
const selectionKeyPressed = useKeyPress(selectionKeyCode);
const onZoomPaneClick = () => setNodesSelection({ isActive: false });
const updateDimensions = () => {
if (!rendererNode.current) {
return;
}
const size = getDimensions(rendererNode.current);
if (size.height === 0 || size.width === 0) {
throw new Error('The React Flow parent container needs a width and a height to render the graph.');
}
updateSize(size);
};
useEffect(() => {
let resizeObserver: ResizeObserver;
updateDimensions();
window.onresize = updateDimensions;
if (onConnect) {
setOnConnect(onConnect);
}
if (rendererNode.current) {
resizeObserver = new ResizeObserver((entries) => {
for (let _ of entries) {
updateDimensions();
}
});
resizeObserver.observe(rendererNode.current);
}
return () => {
window.onresize = null;
if (resizeObserver && rendererNode.current) {
resizeObserver.unobserve(rendererNode.current!);
}
};
}, []);
useD3Zoom({
zoomPane,
onMove, onMove,
onMoveStart, onMoveStart,
onMoveEnd, onMoveEnd,
onLoad, selectionKeyPressed,
onElementClick,
onNodeMouseEnter,
onNodeMouseMove,
onNodeMouseLeave,
onNodeContextMenu,
onNodeDragStart,
onNodeDragStop,
connectionLineType,
connectionLineStyle,
selectionKeyCode,
onElementsRemove,
deleteKeyCode,
elements,
onConnect,
snapToGrid,
snapGrid,
onlyRenderVisibleNodes,
nodesDraggable,
nodesConnectable,
elementsSelectable,
selectNodesOnDrag,
minZoom,
maxZoom,
defaultZoom,
defaultPosition,
arrowHeadColor,
markerEndId,
zoomOnScroll, zoomOnScroll,
zoomOnDoubleClick, zoomOnDoubleClick,
}: GraphViewProps) => { });
const zoomPane = useRef<HTMLDivElement>(null);
const rendererNode = useRef<HTMLDivElement>(null);
const width = useStoreState((s) => s.width);
const height = useStoreState((s) => s.height);
const d3Initialised = useStoreState((s) => s.d3Initialised);
const nodesSelectionActive = useStoreState((s) => s.nodesSelectionActive);
const updateSize = useStoreActions((actions) => actions.updateSize);
const setNodesSelection = useStoreActions((actions) => actions.setNodesSelection);
const setOnConnect = useStoreActions((a) => a.setOnConnect);
const setSnapGrid = useStoreActions((actions) => actions.setSnapGrid);
const setNodesDraggable = useStoreActions((actions) => actions.setNodesDraggable);
const setNodesConnectable = useStoreActions((actions) => actions.setNodesConnectable);
const setElementsSelectable = useStoreActions((actions) => actions.setElementsSelectable);
const setInitTransform = useStoreActions((actions) => actions.setInitTransform);
const setMinMaxZoom = useStoreActions((actions) => actions.setMinMaxZoom);
const fitView = useStoreActions((actions) => actions.fitView);
const zoom = useStoreActions((actions) => actions.zoom);
const selectionKeyPressed = useKeyPress(selectionKeyCode); useEffect(() => {
if (d3Initialised && onLoad) {
onLoad({
fitView: (params = { padding: 0.1 }) => fitView(params),
zoomIn: () => zoom(0.2),
zoomOut: () => zoom(-0.2),
project,
getElements,
setTransform: (transform: FlowTransform) =>
setInitTransform({ x: transform.x, y: transform.y, k: transform.zoom }),
});
}
const onZoomPaneClick = () => setNodesSelection({ isActive: false }); if (d3Initialised) {
const initialTransform = {
const updateDimensions = () => { x: defaultPosition[0],
if (!rendererNode.current) { y: defaultPosition[1],
return; k: defaultZoom,
}
const size = getDimensions(rendererNode.current);
if (size.height === 0 || size.width === 0) {
throw new Error('The React Flow parent container needs a width and a height to render the graph.');
}
updateSize(size);
};
useEffect(() => {
let resizeObserver: ResizeObserver;
updateDimensions();
window.onresize = updateDimensions;
if (onConnect) {
setOnConnect(onConnect);
}
if (rendererNode.current) {
resizeObserver = new ResizeObserver((entries) => {
for (let _ of entries) {
updateDimensions();
}
});
resizeObserver.observe(rendererNode.current);
}
return () => {
window.onresize = null;
if (resizeObserver && rendererNode.current) {
resizeObserver.unobserve(rendererNode.current!);
}
}; };
}, []);
useD3Zoom({ if (initialTransform.x !== 0 || initialTransform.y !== 0 || initialTransform.k !== 1) {
zoomPane, setInitTransform(initialTransform);
onMove,
onMoveStart,
onMoveEnd,
selectionKeyPressed,
zoomOnScroll,
zoomOnDoubleClick,
});
useEffect(() => {
if (d3Initialised && onLoad) {
onLoad({
fitView: (params = { padding: 0.1 }) => fitView(params),
zoomIn: () => zoom(0.2),
zoomOut: () => zoom(-0.2),
project,
getElements,
setTransform: (transform: FlowTransform) =>
setInitTransform({ x: transform.x, y: transform.y, k: transform.zoom }),
});
} }
}
}, [d3Initialised, onLoad]);
if (d3Initialised) { useEffect(() => {
const initialTransform = { setSnapGrid({ snapToGrid, snapGrid });
x: defaultPosition[0], }, [snapToGrid]);
y: defaultPosition[1],
k: defaultZoom,
};
if (initialTransform.x !== 0 || initialTransform.y !== 0 || initialTransform.k !== 1) { useEffect(() => {
setInitTransform(initialTransform); setNodesDraggable(nodesDraggable);
} }, [nodesDraggable]);
}
}, [d3Initialised, onLoad]);
useEffect(() => { useEffect(() => {
setSnapGrid({ snapToGrid, snapGrid }); setNodesConnectable(nodesConnectable);
}, [snapToGrid]); }, [nodesConnectable]);
useEffect(() => { useEffect(() => {
setNodesDraggable(nodesDraggable); setElementsSelectable(elementsSelectable);
}, [nodesDraggable]); }, [elementsSelectable]);
useEffect(() => { useEffect(() => {
setNodesConnectable(nodesConnectable); setMinMaxZoom({ minZoom, maxZoom });
}, [nodesConnectable]); }, [minZoom, maxZoom]);
useEffect(() => { useGlobalKeyHandler({ onElementsRemove, deleteKeyCode });
setElementsSelectable(elementsSelectable); useElementUpdater(elements);
}, [elementsSelectable]);
useEffect(() => { return (
setMinMaxZoom({ minZoom, maxZoom }); <div className="react-flow__renderer" ref={rendererNode}>
}, [minZoom, maxZoom]); <NodeRenderer
nodeTypes={nodeTypes}
useGlobalKeyHandler({ onElementsRemove, deleteKeyCode }); onElementClick={onElementClick}
useElementUpdater(elements); onNodeMouseEnter={onNodeMouseEnter}
onNodeMouseMove={onNodeMouseMove}
return ( onNodeMouseLeave={onNodeMouseLeave}
<div className="react-flow__renderer" ref={rendererNode}> onNodeContextMenu={onNodeContextMenu}
<NodeRenderer onNodeDragStop={onNodeDragStop}
nodeTypes={nodeTypes} onNodeDragStart={onNodeDragStart}
onElementClick={onElementClick} onlyRenderVisibleNodes={onlyRenderVisibleNodes}
onNodeMouseEnter={onNodeMouseEnter} selectNodesOnDrag={selectNodesOnDrag}
onNodeMouseMove={onNodeMouseMove} />
onNodeMouseLeave={onNodeMouseLeave} <EdgeRenderer
onNodeContextMenu={onNodeContextMenu} width={width}
onNodeDragStop={onNodeDragStop} height={height}
onNodeDragStart={onNodeDragStart} edgeTypes={edgeTypes}
onlyRenderVisibleNodes={onlyRenderVisibleNodes} onElementClick={onElementClick}
selectNodesOnDrag={selectNodesOnDrag} connectionLineType={connectionLineType}
/> connectionLineStyle={connectionLineStyle}
<EdgeRenderer arrowHeadColor={arrowHeadColor}
width={width} markerEndId={markerEndId}
height={height} />
edgeTypes={edgeTypes} <UserSelection selectionKeyPressed={selectionKeyPressed} />
onElementClick={onElementClick} {nodesSelectionActive && <NodesSelection />}
connectionLineType={connectionLineType} <div className="react-flow__zoompane" onClick={onZoomPaneClick} ref={zoomPane} />
connectionLineStyle={connectionLineStyle} </div>
arrowHeadColor={arrowHeadColor} );
markerEndId={markerEndId} };
/>
<UserSelection selectionKeyPressed={selectionKeyPressed} />
{nodesSelectionActive && <NodesSelection />}
<div className="react-flow__zoompane" onClick={onZoomPaneClick} ref={zoomPane} />
</div>
);
}
);
GraphView.displayName = 'GraphView'; GraphView.displayName = 'GraphView';
export default GraphView; export default memo(GraphView);
+3 -3
View File
@@ -68,7 +68,7 @@ function renderNode(
); );
} }
const NodeRenderer = memo(({ onlyRenderVisibleNodes = true, ...props }: NodeRendererProps) => { const NodeRenderer = ({ onlyRenderVisibleNodes = true, ...props }: NodeRendererProps) => {
const nodes = useStoreState((s) => s.nodes); const nodes = useStoreState((s) => s.nodes);
const transform = useStoreState((s) => s.transform); const transform = useStoreState((s) => s.transform);
const selectedElements = useStoreState((s) => s.selectedElements); const selectedElements = useStoreState((s) => s.selectedElements);
@@ -94,8 +94,8 @@ const NodeRenderer = memo(({ onlyRenderVisibleNodes = true, ...props }: NodeRend
)} )}
</div> </div>
); );
}); };
NodeRenderer.displayName = 'NodeRenderer'; NodeRenderer.displayName = 'NodeRenderer';
export default NodeRenderer; export default memo(NodeRenderer);