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',
};
const Background = memo(
({ variant = BackgroundVariant.Dots, gap = 24, size = 0.5, color, style, className }: BackgroundProps) => {
const [x, y, scale] = useStoreState((s) => s.transform);
const Background = ({
variant = BackgroundVariant.Dots,
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 scaledGap = gap * scale;
const xOffset = x % scaledGap;
const yOffset = y % scaledGap;
const bgClasses = cc(['react-flow__background', className]);
const scaledGap = gap * scale;
const xOffset = x % scaledGap;
const yOffset = y % scaledGap;
const bgSvgTile = useMemo(() => {
const isLines = variant === BackgroundVariant.Lines;
const bgColor = color ? color : defaultColors[variant];
const path = isLines ? createGridLinesPath(scaledGap, size, bgColor) : createGridDotsPath(size, bgColor);
const bgSvgTile = useMemo(() => {
const isLines = variant === BackgroundVariant.Lines;
const bgColor = color ? color : defaultColors[variant];
const path = isLines ? createGridLinesPath(scaledGap, size, bgColor) : createGridDotsPath(size, bgColor);
return encodeURIComponent(
`<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>
return encodeURIComponent(
`<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>
);
};
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;
}
const Controls = memo(
({ style, showZoom = true, showFitView = true, showInteractive = true, className }: ControlProps) => {
const setInteractive = useStoreActions((actions) => actions.setInteractive);
const fitView = useStoreActions((actions) => actions.fitView);
const zoomIn = useStoreActions((actions) => actions.zoomIn);
const zoomOut = useStoreActions((actions) => actions.zoomOut);
const Controls = ({ style, showZoom = true, showFitView = true, showInteractive = true, className }: ControlProps) => {
const setInteractive = useStoreActions((actions) => actions.setInteractive);
const fitView = useStoreActions((actions) => actions.fitView);
const zoomIn = useStoreActions((actions) => actions.zoomIn);
const zoomOut = useStoreActions((actions) => actions.zoomOut);
const isInteractive = useStoreState((s) => s.nodesDraggable && s.nodesConnectable && s.elementsSelectable);
const mapClasses = cc(['react-flow__controls', className]);
const isInteractive = useStoreState((s) => s.nodesDraggable && s.nodesConnectable && s.elementsSelectable);
const mapClasses = cc(['react-flow__controls', className]);
return (
<div className={mapClasses} style={style}>
{showZoom && (
<>
<div className="react-flow__controls-button react-flow__controls-zoomin" onClick={() => zoomIn()}>
<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 />
return (
<div className={mapClasses} style={style}>
{showZoom && (
<>
<div className="react-flow__controls-button react-flow__controls-zoomin" onClick={() => zoomIn()}>
<PlusIcon />
</div>
)}
{showInteractive && (
<div
className="react-flow__controls-button react-flow__controls-interactive"
onClick={() => setInteractive(!isInteractive)}
>
{isInteractive ? <UnlockIcon /> : <LockIcon />}
<div className="react-flow__controls-button react-flow__controls-zoomout" onClick={() => zoomOut()}>
<MinusIcon />
</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';
export default Controls;
export default memo(Controls);
@@ -10,7 +10,7 @@ interface MiniMapNodeProps {
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 fill = (color || background || backgroundColor) as string;
@@ -26,8 +26,8 @@ const MiniMapNode = memo(({ x, y, width, height, style, color, borderRadius }: M
fill={fill}
/>
);
});
};
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 defaultHeight = 150;
const MiniMap = memo(
({
style = { backgroundColor: '#f8f8f8' },
className,
nodeColor = '#ddd',
nodeBorderRadius = 5,
maskColor = 'rgba(10, 10, 10, .25)',
}: MiniMapProps) => {
const containerWidth = useStoreState((s) => s.width);
const containerHeight = useStoreState((s) => s.height);
const [tX, tY, tScale] = useStoreState((s) => s.transform);
const nodes = useStoreState((s) => s.nodes);
const MiniMap = ({
style = { backgroundColor: '#f8f8f8' },
className,
nodeColor = '#ddd',
nodeBorderRadius = 5,
maskColor = 'rgba(10, 10, 10, .25)',
}: MiniMapProps) => {
const containerWidth = useStoreState((s) => s.width);
const containerHeight = useStoreState((s) => s.height);
const [tX, tY, tScale] = useStoreState((s) => s.transform);
const nodes = useStoreState((s) => s.nodes);
const mapClasses = cc(['react-flow__minimap', className]);
const elementWidth = (style.width || defaultWidth)! as number;
const elementHeight = (style.height || defaultHeight)! as number;
const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as StringFunc;
const hasNodes = nodes && nodes.length;
const bb = getRectOfNodes(nodes);
const viewBB: Rect = {
x: -tX / tScale,
y: -tY / tScale,
width: containerWidth / tScale,
height: containerHeight / tScale,
};
const boundingRect = hasNodes ? getBoundsofRects(bb, viewBB) : viewBB;
const scaledWidth = boundingRect.width / elementWidth;
const scaledHeight = boundingRect.height / elementHeight;
const viewScale = Math.max(scaledWidth, scaledHeight);
const viewWidth = viewScale * elementWidth;
const viewHeight = viewScale * elementHeight;
const offset = 5 * viewScale;
const x = boundingRect.x - (viewWidth - boundingRect.width) / 2 - offset;
const y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset;
const width = viewWidth + offset * 2;
const height = viewHeight + offset * 2;
const mapClasses = cc(['react-flow__minimap', className]);
const elementWidth = (style.width || defaultWidth)! as number;
const elementHeight = (style.height || defaultHeight)! as number;
const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as StringFunc;
const hasNodes = nodes && nodes.length;
const bb = getRectOfNodes(nodes);
const viewBB: Rect = {
x: -tX / tScale,
y: -tY / tScale,
width: containerWidth / tScale,
height: containerHeight / tScale,
};
const boundingRect = hasNodes ? getBoundsofRects(bb, viewBB) : viewBB;
const scaledWidth = boundingRect.width / elementWidth;
const scaledHeight = boundingRect.height / elementHeight;
const viewScale = Math.max(scaledWidth, scaledHeight);
const viewWidth = viewScale * elementWidth;
const viewHeight = viewScale * elementHeight;
const offset = 5 * viewScale;
const x = boundingRect.x - (viewWidth - boundingRect.width) / 2 - offset;
const y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset;
const width = viewWidth + offset * 2;
const height = viewHeight + offset * 2;
return (
<svg
width={elementWidth}
height={elementHeight}
viewBox={`${x} ${y} ${width} ${height}`}
style={style}
className={mapClasses}
>
{nodes
.filter((node) => !node.isHidden)
.map((node) => (
<MiniMapNode
key={node.id}
x={node.__rf.position.x}
y={node.__rf.position.y}
width={node.__rf.width}
height={node.__rf.height}
style={node.style}
color={nodeColorFunc(node)}
borderRadius={nodeBorderRadius}
/>
))}
<path
className="react-flow__minimap-mask"
d={`M${x - offset},${y - offset}h${width + offset * 2}v${height + offset * 2}h${-width - offset * 2}z
return (
<svg
width={elementWidth}
height={elementHeight}
viewBox={`${x} ${y} ${width} ${height}`}
style={style}
className={mapClasses}
>
{nodes
.filter((node) => !node.isHidden)
.map((node) => (
<MiniMapNode
key={node.id}
x={node.__rf.position.x}
y={node.__rf.position.y}
width={node.__rf.width}
height={node.__rf.height}
style={node.style}
color={nodeColorFunc(node)}
borderRadius={nodeBorderRadius}
/>
))}
<path
className="react-flow__minimap-mask"
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`}
fill={maskColor}
fillRule="evenodd"
/>
</svg>
);
}
);
fill={maskColor}
fillRule="evenodd"
/>
</svg>
);
};
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>) => {
const EdgeWrapper = memo(
({
id,
source,
target,
type,
animated,
selected,
onClick,
elementsSelectable,
label,
labelStyle,
labelShowBg,
labelBgStyle,
className,
isHidden,
data,
...rest
}: EdgeWrapperProps) => {
const setSelectedElements = useStoreActions((a) => a.setSelectedElements);
const EdgeWrapper = ({
id,
source,
target,
type,
animated,
selected,
onClick,
elementsSelectable,
label,
labelStyle,
labelShowBg,
labelBgStyle,
className,
isHidden,
data,
...rest
}: EdgeWrapperProps) => {
const setSelectedElements = useStoreActions((a) => a.setSelectedElements);
if (isHidden) {
return null;
if (isHidden) {
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 }]);
const edgeGroupStyle: CSSProperties = {
pointerEvents: elementsSelectable ? 'all' : 'none',
};
const onEdgeClick = (): void => {
if (!elementsSelectable) {
return;
}
setSelectedElements({ id, source, target });
setSelectedElements({ id, source, target });
if (onClick) {
onClick({ id, source, target, type });
}
};
if (onClick) {
onClick({ id, source, target, type });
}
};
return (
<g className={edgeClasses} onClick={onEdgeClick} style={edgeGroupStyle}>
<EdgeComponent
id={id}
source={source}
target={target}
type={type}
animated={animated}
selected={selected}
onClick={onClick}
label={label}
labelStyle={labelStyle}
labelShowBg={labelShowBg}
labelBgStyle={labelBgStyle}
data={data}
{...rest}
/>
</g>
);
}
);
return (
<g className={edgeClasses} onClick={onEdgeClick} style={edgeGroupStyle}>
<EdgeComponent
id={id}
source={source}
target={target}
type={type}
animated={animated}
selected={selected}
onClick={onClick}
label={label}
labelStyle={labelStyle}
labelShowBg={labelShowBg}
labelBgStyle={labelBgStyle}
data={data}
{...rest}
/>
</g>
);
};
EdgeWrapper.displayName = 'EdgeWrapper';
return EdgeWrapper;
return memo(EdgeWrapper);
};
+36 -38
View File
@@ -130,47 +130,45 @@ function onMouseDown(
document.addEventListener('mouseup', onMouseUp);
}
const BaseHandle = memo(
({
type,
nodeId,
onConnect,
position,
setConnectionNodeId,
setPosition,
const BaseHandle = ({
type,
nodeId,
onConnect,
position,
setConnectionNodeId,
setPosition,
className,
id = false,
isValidConnection,
...rest
}: BaseHandleProps) => {
const isTarget = type === 'target';
const handleClasses = cc([
'react-flow__handle',
`react-flow__handle-${position}`,
'nodrag',
className,
id = false,
isValidConnection,
...rest
}: BaseHandleProps) => {
const isTarget = type === 'target';
const handleClasses = cc([
'react-flow__handle',
`react-flow__handle-${position}`,
'nodrag',
className,
{
source: !isTarget,
target: isTarget,
},
]);
{
source: !isTarget,
target: isTarget,
},
]);
const nodeIdWithHandleId = id ? `${nodeId}__${id}` : nodeId;
const nodeIdWithHandleId = id ? `${nodeId}__${id}` : nodeId;
return (
<div
data-nodeid={nodeIdWithHandleId}
data-handlepos={position}
className={handleClasses}
onMouseDown={(evt) =>
onMouseDown(evt, nodeIdWithHandleId, setConnectionNodeId, setPosition, onConnect, isTarget, isValidConnection)
}
{...rest}
/>
);
}
);
return (
<div
data-nodeid={nodeIdWithHandleId}
data-handlepos={position}
className={handleClasses}
onMouseDown={(evt) =>
onMouseDown(evt, nodeIdWithHandleId, setConnectionNodeId, setPosition, onConnect, isTarget, isValidConnection)
}
{...rest}
/>
);
};
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';
const Handle = memo(
({
type = 'source',
position = Position.Top,
onConnect = () => {},
isValidConnection = () => true,
isConnectable = true,
style,
className,
id,
}: HandleProps) => {
const nodeId = useContext(NodeIdContext) as ElementId;
const setPosition = useStoreActions((a) => a.setConnectionPosition);
const setConnectionNodeId = useStoreActions((a) => a.setConnectionNodeId);
const onConnectAction = useStoreState((s) => s.onConnect);
const onConnectExtended = (params: Connection) => {
onConnectAction(params);
onConnect(params);
};
const handleClasses = cc([className, { connectable: isConnectable }]);
const Handle = ({
type = 'source',
position = Position.Top,
onConnect = () => {},
isValidConnection = () => true,
isConnectable = true,
style,
className,
id,
}: HandleProps) => {
const nodeId = useContext(NodeIdContext) as ElementId;
const setPosition = useStoreActions((a) => a.setConnectionPosition);
const setConnectionNodeId = useStoreActions((a) => a.setConnectionNodeId);
const onConnectAction = useStoreState((s) => s.onConnect);
const onConnectExtended = (params: Connection) => {
onConnectAction(params);
onConnect(params);
};
const handleClasses = cc([className, { connectable: isConnectable }]);
return (
<BaseHandle
className={handleClasses}
id={id}
nodeId={nodeId}
setPosition={setPosition}
setConnectionNodeId={setConnectionNodeId}
onConnect={onConnectExtended}
type={type}
position={position}
isValidConnection={isValidConnection}
style={style}
/>
);
}
);
return (
<BaseHandle
className={handleClasses}
id={id}
nodeId={nodeId}
setPosition={setPosition}
setConnectionNodeId={setConnectionNodeId}
onConnect={onConnectExtended}
type={type}
position={position}
isValidConnection={isValidConnection}
style={style}
/>
);
};
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 { NodeProps, Position } from '../../types';
const DefaultNode = memo(
({ data, isConnectable, targetPosition = Position.Top, sourcePosition = Position.Bottom }: NodeProps) => (
<>
<Handle type="target" position={targetPosition} isConnectable={isConnectable} />
{data.label}
<Handle type="source" position={sourcePosition} isConnectable={isConnectable} />
</>
)
const DefaultNode = ({
data,
isConnectable,
targetPosition = Position.Top,
sourcePosition = Position.Bottom,
}: NodeProps) => (
<>
<Handle type="target" position={targetPosition} isConnectable={isConnectable} />
{data.label}
<Handle type="source" position={sourcePosition} isConnectable={isConnectable} />
</>
);
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 { NodeProps, Position } from '../../types';
const InputNode = memo(({ data, isConnectable, sourcePosition = Position.Bottom }: NodeProps) => (
const InputNode = ({ data, isConnectable, sourcePosition = Position.Bottom }: NodeProps) => (
<>
{data.label}
<Handle type="source" position={sourcePosition} isConnectable={isConnectable} />
</>
));
);
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 { 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} />
{data.label}
</>
));
);
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>) => {
const NodeWrapper = memo(
({
id,
type,
data,
transform,
xPos,
yPos,
selected,
onClick,
onMouseEnter,
onMouseMove,
onMouseLeave,
onContextMenu,
onNodeDragStart,
onNodeDragStop,
style,
const NodeWrapper = ({
id,
type,
data,
transform,
xPos,
yPos,
selected,
onClick,
onMouseEnter,
onMouseMove,
onMouseLeave,
onContextMenu,
onNodeDragStart,
onNodeDragStop,
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,
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,
{
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);
}
}
{
selected,
selectable: isSelectable,
},
]);
const node = { id, type, position, data };
const onMouseEnterHandler = useMemo(() => {
if (!onMouseEnter || isDragging) {
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 = {
zIndex: selected ? 10 : 3,
transform: `translate(${xPos}px,${yPos}px)`,
pointerEvents: isSelectable || isDraggable ? 'all' : 'none',
...style,
};
return (evt: MouseEvent) => onMouseEnter(evt, node);
}, [onMouseEnter, isDragging]);
return (
<DraggableCore
onStart={(evt) =>
onStart({
evt: evt as MouseEvent,
selectNodesOnDrag,
isSelectable,
onNodeDragStart,
id,
type,
data,
setOffset,
transform,
position,
setSelectedElements,
})
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;
}, [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({
onNodeDragStop,
selectNodesOnDrag,
isSelectable,
onClick,
isDragging,
setDragging,
id,
type,
position,
data,
setSelectedElements,
})
});
resizeObserver.observe(nodeElement.current);
return () => {
if (resizeObserver && nodeElement.current) {
resizeObserver.unobserve(nodeElement.current);
}
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>
);
};
}
return;
}, [id]);
if (isHidden) {
return null;
}
);
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';
return NodeWrapper;
return memo(NodeWrapper);
};
+3 -3
View File
@@ -3,7 +3,7 @@
* 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 { useStoreState, useStoreActions } from '../../store/hooks';
@@ -27,7 +27,7 @@ function getStartPositions(nodes: Node[]): StartPositions {
}, startPositions);
}
export default memo(() => {
export default () => {
const [offset, setOffset] = useState<XYPosition>({ x: 0, y: 0 });
const [startPositions, setStartPositions] = useState<StartPositions>({});
const [tX, tY, tScale] = useStoreState((s) => s.transform);
@@ -110,4 +110,4 @@ export default memo(() => {
</ReactDraggable>
</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 edges = useStoreState((s) => s.edges);
const nodes = useStoreState((s) => s.nodes);
@@ -234,8 +234,8 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
</g>
</svg>
);
});
};
EdgeRenderer.displayName = 'EdgeRenderer';
export default EdgeRenderer;
export default memo(EdgeRenderer);
+175 -177
View File
@@ -62,200 +62,198 @@ export interface GraphViewProps {
zoomOnDoubleClick: boolean;
}
const GraphView = memo(
({
nodeTypes,
edgeTypes,
const GraphView = ({
nodeTypes,
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,
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,
selectionKeyPressed,
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);
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 });
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!);
}
if (d3Initialised) {
const initialTransform = {
x: defaultPosition[0],
y: defaultPosition[1],
k: defaultZoom,
};
}, []);
useD3Zoom({
zoomPane,
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 }),
});
if (initialTransform.x !== 0 || initialTransform.y !== 0 || initialTransform.k !== 1) {
setInitTransform(initialTransform);
}
}
}, [d3Initialised, onLoad]);
if (d3Initialised) {
const initialTransform = {
x: defaultPosition[0],
y: defaultPosition[1],
k: defaultZoom,
};
useEffect(() => {
setSnapGrid({ snapToGrid, snapGrid });
}, [snapToGrid]);
if (initialTransform.x !== 0 || initialTransform.y !== 0 || initialTransform.k !== 1) {
setInitTransform(initialTransform);
}
}
}, [d3Initialised, onLoad]);
useEffect(() => {
setNodesDraggable(nodesDraggable);
}, [nodesDraggable]);
useEffect(() => {
setSnapGrid({ snapToGrid, snapGrid });
}, [snapToGrid]);
useEffect(() => {
setNodesConnectable(nodesConnectable);
}, [nodesConnectable]);
useEffect(() => {
setNodesDraggable(nodesDraggable);
}, [nodesDraggable]);
useEffect(() => {
setElementsSelectable(elementsSelectable);
}, [elementsSelectable]);
useEffect(() => {
setNodesConnectable(nodesConnectable);
}, [nodesConnectable]);
useEffect(() => {
setMinMaxZoom({ minZoom, maxZoom });
}, [minZoom, maxZoom]);
useEffect(() => {
setElementsSelectable(elementsSelectable);
}, [elementsSelectable]);
useGlobalKeyHandler({ onElementsRemove, deleteKeyCode });
useElementUpdater(elements);
useEffect(() => {
setMinMaxZoom({ minZoom, maxZoom });
}, [minZoom, maxZoom]);
useGlobalKeyHandler({ onElementsRemove, deleteKeyCode });
useElementUpdater(elements);
return (
<div className="react-flow__renderer" ref={rendererNode}>
<NodeRenderer
nodeTypes={nodeTypes}
onElementClick={onElementClick}
onNodeMouseEnter={onNodeMouseEnter}
onNodeMouseMove={onNodeMouseMove}
onNodeMouseLeave={onNodeMouseLeave}
onNodeContextMenu={onNodeContextMenu}
onNodeDragStop={onNodeDragStop}
onNodeDragStart={onNodeDragStart}
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
selectNodesOnDrag={selectNodesOnDrag}
/>
<EdgeRenderer
width={width}
height={height}
edgeTypes={edgeTypes}
onElementClick={onElementClick}
connectionLineType={connectionLineType}
connectionLineStyle={connectionLineStyle}
arrowHeadColor={arrowHeadColor}
markerEndId={markerEndId}
/>
<UserSelection selectionKeyPressed={selectionKeyPressed} />
{nodesSelectionActive && <NodesSelection />}
<div className="react-flow__zoompane" onClick={onZoomPaneClick} ref={zoomPane} />
</div>
);
}
);
return (
<div className="react-flow__renderer" ref={rendererNode}>
<NodeRenderer
nodeTypes={nodeTypes}
onElementClick={onElementClick}
onNodeMouseEnter={onNodeMouseEnter}
onNodeMouseMove={onNodeMouseMove}
onNodeMouseLeave={onNodeMouseLeave}
onNodeContextMenu={onNodeContextMenu}
onNodeDragStop={onNodeDragStop}
onNodeDragStart={onNodeDragStart}
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
selectNodesOnDrag={selectNodesOnDrag}
/>
<EdgeRenderer
width={width}
height={height}
edgeTypes={edgeTypes}
onElementClick={onElementClick}
connectionLineType={connectionLineType}
connectionLineStyle={connectionLineStyle}
arrowHeadColor={arrowHeadColor}
markerEndId={markerEndId}
/>
<UserSelection selectionKeyPressed={selectionKeyPressed} />
{nodesSelectionActive && <NodesSelection />}
<div className="react-flow__zoompane" onClick={onZoomPaneClick} ref={zoomPane} />
</div>
);
};
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 transform = useStoreState((s) => s.transform);
const selectedElements = useStoreState((s) => s.selectedElements);
@@ -94,8 +94,8 @@ const NodeRenderer = memo(({ onlyRenderVisibleNodes = true, ...props }: NodeRend
)}
</div>
);
});
};
NodeRenderer.displayName = 'NodeRenderer';
export default NodeRenderer;
export default memo(NodeRenderer);