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:
@@ -113,7 +113,13 @@ function getEdgePositions(
|
||||
};
|
||||
}
|
||||
|
||||
function renderEdge(edge: Edge, props: EdgeRendererProps, nodes: Node[], selectedElements: Elements) {
|
||||
function renderEdge(
|
||||
edge: Edge,
|
||||
props: EdgeRendererProps,
|
||||
nodes: Node[],
|
||||
selectedElements: Elements,
|
||||
isInteractive: boolean
|
||||
) {
|
||||
const [sourceId, sourceHandleId] = edge.source.split('__');
|
||||
const [targetId, targetHandleId] = edge.target.split('__');
|
||||
|
||||
@@ -128,7 +134,7 @@ function renderEdge(edge: Edge, props: EdgeRendererProps, nodes: Node[], selecte
|
||||
throw new Error(`couldn't create edge for target id: ${targetId}`);
|
||||
}
|
||||
|
||||
if (!sourceNode.__rg.width || !sourceNode.__rg.height) {
|
||||
if (!sourceNode.__rg.width || !sourceNode.__rg.height) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -171,6 +177,7 @@ function renderEdge(edge: Edge, props: EdgeRendererProps, nodes: Node[], selecte
|
||||
targetY={targetY}
|
||||
sourcePosition={sourcePosition}
|
||||
targetPosition={targetPosition}
|
||||
isInteractive={isInteractive}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -183,7 +190,16 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
|
||||
connectionSourceId,
|
||||
connectionPosition: { x, y },
|
||||
selectedElements,
|
||||
} = useStoreState(s => s);
|
||||
isInteractive,
|
||||
} = useStoreState(s => ({
|
||||
transform: s.transform,
|
||||
edges: s.edges,
|
||||
nodes: s.nodes,
|
||||
connectionSourceId: s.connectionSourceId,
|
||||
connectionPosition: s.connectionPosition,
|
||||
selectedElements: s.selectedElements,
|
||||
isInteractive: s.isInteractive,
|
||||
}));
|
||||
|
||||
const { width, height, connectionLineStyle, connectionLineType } = props;
|
||||
|
||||
@@ -197,7 +213,7 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
|
||||
return (
|
||||
<svg width={width} height={height} className="react-flow__edges">
|
||||
<g transform={transformStyle}>
|
||||
{edges.map((e: Edge) => renderEdge(e, props, nodes, selectedElements))}
|
||||
{edges.map((e: Edge) => renderEdge(e, props, nodes, selectedElements, isInteractive))}
|
||||
{connectionSourceId && (
|
||||
<ConnectionLine
|
||||
nodes={nodes}
|
||||
@@ -207,6 +223,7 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
|
||||
transform={transform}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
connectionLineType={connectionLineType}
|
||||
isInteractive={isInteractive}
|
||||
/>
|
||||
)}
|
||||
</g>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useRef, memo, SVGAttributes } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { useStoreState, useStoreActions } from '../../store/hooks';
|
||||
import NodeRenderer from '../NodeRenderer';
|
||||
@@ -12,13 +13,7 @@ import useGlobalKeyHandler from '../../hooks/useGlobalKeyHandler';
|
||||
import useElementUpdater from '../../hooks/useElementUpdater';
|
||||
import { getDimensions } from '../../utils';
|
||||
import { fitView, zoomIn, zoomOut } from '../../utils/graph';
|
||||
import {
|
||||
Elements,
|
||||
NodeTypesType,
|
||||
EdgeTypesType,
|
||||
GridType,
|
||||
OnLoadFunc,
|
||||
} from '../../types';
|
||||
import { Elements, NodeTypesType, EdgeTypesType, GridType, OnLoadFunc } from '../../types';
|
||||
|
||||
export interface GraphViewProps {
|
||||
elements: Elements;
|
||||
@@ -40,7 +35,8 @@ export interface GraphViewProps {
|
||||
backgroundType: GridType;
|
||||
snapToGrid: boolean;
|
||||
snapGrid: [number, number];
|
||||
onlyRenderVisibleNodes: boolean
|
||||
onlyRenderVisibleNodes: boolean;
|
||||
isInteractive: boolean;
|
||||
}
|
||||
|
||||
const GraphView = memo(
|
||||
@@ -64,7 +60,8 @@ const GraphView = memo(
|
||||
onConnect,
|
||||
snapToGrid,
|
||||
snapGrid,
|
||||
onlyRenderVisibleNodes
|
||||
onlyRenderVisibleNodes,
|
||||
isInteractive,
|
||||
}: GraphViewProps) => {
|
||||
const zoomPane = useRef<HTMLDivElement>(null);
|
||||
const rendererNode = useRef<HTMLDivElement>(null);
|
||||
@@ -77,13 +74,12 @@ const GraphView = memo(
|
||||
nodesSelectionActive: s.nodesSelectionActive,
|
||||
}));
|
||||
const updateSize = useStoreActions(actions => actions.updateSize);
|
||||
const setNodesSelection = useStoreActions(
|
||||
actions => actions.setNodesSelection
|
||||
);
|
||||
const setNodesSelection = useStoreActions(actions => actions.setNodesSelection);
|
||||
const setOnConnect = useStoreActions(a => a.setOnConnect);
|
||||
const setSnapGrid = useStoreActions(actions => actions.setSnapGrid);
|
||||
|
||||
const setInteractive = useStoreActions(actions => actions.setInteractive);
|
||||
const selectionKeyPressed = useKeyPress(selectionKeyCode);
|
||||
const rendererClasses = classnames('react-flow__renderer', { 'is-interactive': isInteractive });
|
||||
|
||||
const onZoomPaneClick = () => setNodesSelection({ isActive: false });
|
||||
|
||||
@@ -122,17 +118,17 @@ const GraphView = memo(
|
||||
setSnapGrid({ snapToGrid, snapGrid });
|
||||
}, [snapToGrid]);
|
||||
|
||||
useEffect(() => {
|
||||
setInteractive(isInteractive);
|
||||
}, [isInteractive]);
|
||||
|
||||
useGlobalKeyHandler({ onElementsRemove, deleteKeyCode });
|
||||
useElementUpdater(elements);
|
||||
|
||||
return (
|
||||
<div className="react-flow__renderer" ref={rendererNode}>
|
||||
<div className={rendererClasses} ref={rendererNode}>
|
||||
{showBackground && (
|
||||
<BackgroundGrid
|
||||
gap={backgroundGap}
|
||||
color={backgroundColor}
|
||||
backgroundType={backgroundType}
|
||||
/>
|
||||
<BackgroundGrid gap={backgroundGap} color={backgroundColor} backgroundType={backgroundType} />
|
||||
)}
|
||||
<NodeRenderer
|
||||
nodeTypes={nodeTypes}
|
||||
@@ -148,13 +144,9 @@ const GraphView = memo(
|
||||
connectionLineType={connectionLineType}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
/>
|
||||
{selectionKeyPressed && <UserSelection />}
|
||||
{selectionKeyPressed && <UserSelection isInteractive={isInteractive} />}
|
||||
{state.nodesSelectionActive && <NodesSelection />}
|
||||
<div
|
||||
className="react-flow__zoompane"
|
||||
onClick={onZoomPaneClick}
|
||||
ref={zoomPane}
|
||||
/>
|
||||
<div className="react-flow__zoompane" onClick={onZoomPaneClick} ref={zoomPane} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { memo, ComponentType } from 'react';
|
||||
|
||||
import { useStoreState } from '../../store/hooks';
|
||||
import { getNodesInside } from '../../utils/graph';
|
||||
import { Node, Transform, NodeTypesType, NodeComponentProps, Elements } from '../../types';
|
||||
import { Node, Transform, NodeTypesType, WrapNodeProps, Elements } from '../../types';
|
||||
|
||||
interface NodeRendererProps {
|
||||
nodeTypes: NodeTypesType;
|
||||
@@ -11,9 +11,15 @@ interface NodeRendererProps {
|
||||
onlyRenderVisibleNodes?: boolean;
|
||||
}
|
||||
|
||||
function renderNode(node: Node, props: NodeRendererProps, transform: Transform, selectedElements: Elements) {
|
||||
function renderNode(
|
||||
node: Node,
|
||||
props: NodeRendererProps,
|
||||
transform: Transform,
|
||||
selectedElements: Elements,
|
||||
isInteractive: boolean
|
||||
) {
|
||||
const nodeType = node.type || 'default';
|
||||
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<NodeComponentProps>;
|
||||
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<WrapNodeProps>;
|
||||
if (!props.nodeTypes[nodeType]) {
|
||||
console.warn(`No node type found for type "${nodeType}". Using fallback type "default".`);
|
||||
}
|
||||
@@ -33,23 +39,33 @@ function renderNode(node: Node, props: NodeRendererProps, transform: Transform,
|
||||
transform={transform}
|
||||
selected={isSelected}
|
||||
style={node.style}
|
||||
isInteractive={isInteractive}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const NodeRenderer = memo(({ onlyRenderVisibleNodes = true, ...props }: NodeRendererProps) => {
|
||||
const { nodes, transform, selectedElements, width, height } = useStoreState(s => s);
|
||||
const { nodes, transform, selectedElements, width, height, isInteractive } = useStoreState(s => ({
|
||||
nodes: s.nodes,
|
||||
transform: s.transform,
|
||||
selectedElements: s.selectedElements,
|
||||
width: s.width,
|
||||
height: s.height,
|
||||
isInteractive: s.isInteractive,
|
||||
}));
|
||||
|
||||
const [tx, ty, tScale] = transform;
|
||||
const transformStyle = {
|
||||
transform: `translate(${tx}px,${ty}px) scale(${tScale})`,
|
||||
};
|
||||
|
||||
const renderNodes = onlyRenderVisibleNodes ? getNodesInside(nodes, { x: 0, y: 0, width, height }, transform, true) : nodes;
|
||||
const renderNodes = onlyRenderVisibleNodes
|
||||
? getNodesInside(nodes, { x: 0, y: 0, width, height }, transform, true)
|
||||
: nodes;
|
||||
|
||||
return (
|
||||
<div className="react-flow__nodes" style={transformStyle}>
|
||||
{renderNodes.map(node => renderNode(node, props, transform, selectedElements))}
|
||||
{renderNodes.map(node => renderNode(node, props, transform, selectedElements, isInteractive))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -18,18 +18,11 @@ import StraightEdge from '../../components/Edges/StraightEdge';
|
||||
import StepEdge from '../../components/Edges/StepEdge';
|
||||
import { createEdgeTypes } from '../EdgeRenderer/utils';
|
||||
import store from '../../store';
|
||||
import {
|
||||
Elements,
|
||||
NodeTypesType,
|
||||
EdgeTypesType,
|
||||
GridType,
|
||||
OnLoadFunc,
|
||||
} from '../../types';
|
||||
import { Elements, NodeTypesType, EdgeTypesType, GridType, OnLoadFunc } from '../../types';
|
||||
|
||||
import '../../style.css';
|
||||
|
||||
export interface ReactFlowProps
|
||||
extends Omit<HTMLAttributes<HTMLDivElement>, 'onLoad'> {
|
||||
export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onLoad'> {
|
||||
elements: Elements;
|
||||
onElementClick: () => void;
|
||||
onElementsRemove: (elements: Elements) => void;
|
||||
@@ -49,7 +42,8 @@ export interface ReactFlowProps
|
||||
backgroundType: GridType;
|
||||
snapToGrid: boolean;
|
||||
snapGrid: [number, number];
|
||||
onlyRenderVisibleNodes: boolean
|
||||
onlyRenderVisibleNodes: boolean;
|
||||
isInteractive: boolean;
|
||||
}
|
||||
|
||||
const ReactFlow = ({
|
||||
@@ -74,7 +68,8 @@ const ReactFlow = ({
|
||||
backgroundColor,
|
||||
snapToGrid,
|
||||
snapGrid,
|
||||
onlyRenderVisibleNodes
|
||||
onlyRenderVisibleNodes,
|
||||
isInteractive,
|
||||
}: ReactFlowProps) => {
|
||||
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []);
|
||||
const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []);
|
||||
@@ -103,6 +98,7 @@ const ReactFlow = ({
|
||||
snapToGrid={snapToGrid}
|
||||
snapGrid={snapGrid}
|
||||
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
|
||||
isInteractive={isInteractive}
|
||||
/>
|
||||
{children}
|
||||
</StoreProvider>
|
||||
@@ -139,7 +135,8 @@ ReactFlow.defaultProps = {
|
||||
backgroundType: GridType.Dots,
|
||||
snapToGrid: false,
|
||||
snapGrid: [16, 16],
|
||||
onlyRenderVisibleNodes: true
|
||||
onlyRenderVisibleNodes: true,
|
||||
isInteractive: true,
|
||||
};
|
||||
|
||||
export default ReactFlow;
|
||||
|
||||
Reference in New Issue
Block a user