refactor: NodeRenderer (#51)

* refactor: NodeRenderer

* fix getNodesInside partially param

* add `skipInvisible` prop

* refactor(noderenderer): rename skipInvisble to onlyRenderVisibleNodes

closes #40
This commit is contained in:
AndyLnd
2019-10-22 21:03:11 +02:00
committed by Moritz
parent 429ede3386
commit bd60ad0ecd
13 changed files with 303 additions and 2356 deletions
+130 -1153
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+130 -1153
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -20,6 +20,7 @@ export interface GraphViewProps {
backgroundType: GridType; backgroundType: GridType;
snapToGrid: boolean; snapToGrid: boolean;
snapGrid: [number, number]; snapGrid: [number, number];
onlyRenderVisibleNodes: boolean;
} }
declare const GraphView: React.MemoExoticComponent<({ nodeTypes, edgeTypes, onMove, onLoad, onElementClick, onNodeDragStop, connectionLineType, connectionLineStyle, selectionKeyCode, onElementsRemove, deleteKeyCode, elements, showBackground, backgroundGap, backgroundColor, backgroundType, onConnect, snapToGrid, snapGrid, }: GraphViewProps) => JSX.Element>; declare const GraphView: React.MemoExoticComponent<({ nodeTypes, edgeTypes, onMove, onLoad, onElementClick, onNodeDragStop, connectionLineType, connectionLineStyle, selectionKeyCode, onElementsRemove, deleteKeyCode, elements, showBackground, backgroundGap, backgroundColor, backgroundType, onConnect, snapToGrid, snapGrid, onlyRenderVisibleNodes }: GraphViewProps) => JSX.Element>;
export default GraphView; export default GraphView;
+2 -1
View File
@@ -4,6 +4,7 @@ interface NodeRendererProps {
nodeTypes: NodeTypesType; nodeTypes: NodeTypesType;
onElementClick: () => void; onElementClick: () => void;
onNodeDragStop: () => void; onNodeDragStop: () => void;
onlyRenderVisibleNodes?: boolean;
} }
declare const NodeRenderer: React.MemoExoticComponent<(props: NodeRendererProps) => JSX.Element>; declare const NodeRenderer: React.MemoExoticComponent<({ onlyRenderVisibleNodes, ...props }: NodeRendererProps) => JSX.Element>;
export default NodeRenderer; export default NodeRenderer;
+4 -2
View File
@@ -20,10 +20,11 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
backgroundColor: string; backgroundColor: string;
backgroundType: GridType; backgroundType: GridType;
snapToGrid: boolean; snapToGrid: boolean;
snapGrid: [16, 16]; snapGrid: [number, number];
onlyRenderVisibleNodes: boolean;
} }
declare const ReactFlow: { declare const ReactFlow: {
({ style, onElementClick, elements, children, nodeTypes, edgeTypes, onLoad, onMove, onElementsRemove, onConnect, onNodeDragStop, connectionLineType, connectionLineStyle, deleteKeyCode, selectionKeyCode, showBackground, backgroundGap, backgroundType, backgroundColor, snapToGrid, snapGrid, }: ReactFlowProps): JSX.Element; ({ style, onElementClick, elements, children, nodeTypes, edgeTypes, onLoad, onMove, onElementsRemove, onConnect, onNodeDragStop, connectionLineType, connectionLineStyle, deleteKeyCode, selectionKeyCode, showBackground, backgroundGap, backgroundType, backgroundColor, snapToGrid, snapGrid, onlyRenderVisibleNodes }: ReactFlowProps): JSX.Element;
displayName: string; displayName: string;
defaultProps: { defaultProps: {
onElementClick: () => void; onElementClick: () => void;
@@ -52,6 +53,7 @@ declare const ReactFlow: {
backgroundType: GridType; backgroundType: GridType;
snapToGrid: boolean; snapToGrid: boolean;
snapGrid: number[]; snapGrid: number[];
onlyRenderVisibleNodes: boolean;
}; };
}; };
export default ReactFlow; export default ReactFlow;
+6 -1
View File
@@ -2,7 +2,12 @@ import { CSSProperties, SVGAttributes } from 'react';
export declare type ElementId = string; export declare type ElementId = string;
export declare type Elements = Array<Node | Edge>; export declare type Elements = Array<Node | Edge>;
export declare type Transform = [number, number, number]; export declare type Transform = [number, number, number];
export declare type Position = 'left' | 'top' | 'right' | 'bottom'; export declare enum Position {
Left = "left",
Top = "top",
Right = "right",
Bottom = "bottom"
}
export declare type XYPosition = { export declare type XYPosition = {
x: number; x: number;
y: number; y: number;
+1 -1
View File
@@ -7,7 +7,7 @@ export declare const addEdge: (edgeParams: Edge, elements: (Node | Edge)[]) => (
export declare const parseElement: (element: Node | Edge, transform: [number, number, number], snapToGrid: boolean, snapGrid: [number, number]) => Node | Edge; export declare const parseElement: (element: Node | Edge, transform: [number, number, number], snapToGrid: boolean, snapGrid: [number, number]) => Node | Edge;
export declare const getBoundsofRects: (rect1: Rect, rect2: Rect) => Rect; export declare const getBoundsofRects: (rect1: Rect, rect2: Rect) => Rect;
export declare const getRectOfNodes: (nodes: Node[]) => Rect; export declare const getRectOfNodes: (nodes: Node[]) => Rect;
export declare const graphPosToZoomedPos: (pos: XYPosition, transform: [number, number, number]) => XYPosition; export declare const graphPosToZoomedPos: ({ x, y }: XYPosition, [tx, ty, tScale]: [number, number, number]) => XYPosition;
export declare const getNodesInside: (nodes: Node[], rect: Rect, [tx, ty, tScale]?: [number, number, number], partially?: boolean) => Node[]; export declare const getNodesInside: (nodes: Node[], rect: Rect, [tx, ty, tScale]?: [number, number, number], partially?: boolean) => Node[];
export declare const getConnectedEdges: (nodes: Node[], edges: Edge[]) => Edge[]; export declare const getConnectedEdges: (nodes: Node[], edges: Edge[]) => Edge[];
export declare const fitView: ({ padding }?: FitViewParams) => void; export declare const fitView: ({ padding }?: FitViewParams) => void;
+3
View File
@@ -40,6 +40,7 @@ export interface GraphViewProps {
backgroundType: GridType; backgroundType: GridType;
snapToGrid: boolean; snapToGrid: boolean;
snapGrid: [number, number]; snapGrid: [number, number];
onlyRenderVisibleNodes: boolean
} }
const GraphView = memo( const GraphView = memo(
@@ -63,6 +64,7 @@ const GraphView = memo(
onConnect, onConnect,
snapToGrid, snapToGrid,
snapGrid, snapGrid,
onlyRenderVisibleNodes
}: GraphViewProps) => { }: GraphViewProps) => {
const zoomPane = useRef<HTMLDivElement>(null); const zoomPane = useRef<HTMLDivElement>(null);
const rendererNode = useRef<HTMLDivElement>(null); const rendererNode = useRef<HTMLDivElement>(null);
@@ -136,6 +138,7 @@ const GraphView = memo(
nodeTypes={nodeTypes} nodeTypes={nodeTypes}
onElementClick={onElementClick} onElementClick={onElementClick}
onNodeDragStop={onNodeDragStop} onNodeDragStop={onNodeDragStop}
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
/> />
<EdgeRenderer <EdgeRenderer
width={state.width} width={state.width}
+16 -39
View File
@@ -1,45 +1,24 @@
import React, { memo, ComponentType } from 'react'; import React, { memo, ComponentType } from 'react';
import { useStoreState } from '../../store/hooks'; import { useStoreState } from '../../store/hooks';
import { isNode } from '../../utils/graph'; import { getNodesInside } from '../../utils/graph';
import { import { Node, Transform, NodeTypesType, NodeComponentProps, Elements } from '../../types';
Node,
Transform,
NodeTypesType,
NodeComponentProps,
} from '../../types';
interface NodeRendererProps { interface NodeRendererProps {
nodeTypes: NodeTypesType; nodeTypes: NodeTypesType;
onElementClick: () => void; onElementClick: () => void;
onNodeDragStop: () => void; onNodeDragStop: () => void;
onlyRenderVisibleNodes?: boolean;
} }
interface NodeRendererState { function renderNode(node: Node, props: NodeRendererProps, transform: Transform, selectedElements: Elements) {
nodes: Node[];
transform: Transform;
selectedElements: any;
}
function renderNode(
node: Node,
props: NodeRendererProps,
state: NodeRendererState
) {
const nodeType = node.type || 'default'; const nodeType = node.type || 'default';
const NodeComponent = (props.nodeTypes[nodeType] || props.nodeTypes.default) as ComponentType<NodeComponentProps>;
if (!props.nodeTypes[nodeType]) { if (!props.nodeTypes[nodeType]) {
console.warn( console.warn(`No node type found for type "${nodeType}". Using fallback type "default".`);
`No node type found for type "${nodeType}". Using fallback type "default".`
);
} }
const NodeComponent = (props.nodeTypes[nodeType] || const isSelected = selectedElements.some(({ id }) => id === node.id);
props.nodeTypes.default) as ComponentType<NodeComponentProps>;
const selected = state.selectedElements
.filter(isNode)
.map((e: Node) => e.id)
.includes(node.id);
return ( return (
<NodeComponent <NodeComponent
@@ -51,28 +30,26 @@ function renderNode(
yPos={node.__rg.position.y} yPos={node.__rg.position.y}
onClick={props.onElementClick} onClick={props.onElementClick}
onNodeDragStop={props.onNodeDragStop} onNodeDragStop={props.onNodeDragStop}
transform={state.transform} transform={transform}
selected={selected} selected={isSelected}
style={node.style} style={node.style}
/> />
); );
} }
const NodeRenderer = memo((props: NodeRendererProps) => { const NodeRenderer = memo(({ onlyRenderVisibleNodes = true, ...props }: NodeRendererProps) => {
const state: NodeRendererState = useStoreState(s => ({ const { nodes, transform, selectedElements, width, height } = useStoreState(s => s);
nodes: s.nodes,
transform: s.transform,
selectedElements: s.selectedElements,
}));
const { transform, nodes } = state; const [tx, ty, tScale] = transform;
const transformStyle = { const transformStyle = {
transform: `translate(${transform[0]}px,${transform[1]}px) scale(${transform[2]})`, transform: `translate(${tx}px,${ty}px) scale(${tScale})`,
}; };
const renderNodes = onlyRenderVisibleNodes ? getNodesInside(nodes, { x: 0, y: 0, width, height }, transform, true) : nodes;
return ( return (
<div className="react-flow__nodes" style={transformStyle}> <div className="react-flow__nodes" style={transformStyle}>
{nodes.map(node => renderNode(node, props, state))} {renderNodes.map(node => renderNode(node, props, transform, selectedElements))}
</div> </div>
); );
}); });
+5 -1
View File
@@ -48,7 +48,8 @@ export interface ReactFlowProps
backgroundColor: string; backgroundColor: string;
backgroundType: GridType; backgroundType: GridType;
snapToGrid: boolean; snapToGrid: boolean;
snapGrid: [16, 16]; snapGrid: [number, number];
onlyRenderVisibleNodes: boolean
} }
const ReactFlow = ({ const ReactFlow = ({
@@ -73,6 +74,7 @@ const ReactFlow = ({
backgroundColor, backgroundColor,
snapToGrid, snapToGrid,
snapGrid, snapGrid,
onlyRenderVisibleNodes
}: ReactFlowProps) => { }: ReactFlowProps) => {
const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []); const nodeTypesParsed = useMemo(() => createNodeTypes(nodeTypes), []);
const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []); const edgeTypesParsed = useMemo(() => createEdgeTypes(edgeTypes), []);
@@ -100,6 +102,7 @@ const ReactFlow = ({
backgroundType={backgroundType} backgroundType={backgroundType}
snapToGrid={snapToGrid} snapToGrid={snapToGrid}
snapGrid={snapGrid} snapGrid={snapGrid}
onlyRenderVisibleNodes={onlyRenderVisibleNodes}
/> />
{children} {children}
</StoreProvider> </StoreProvider>
@@ -136,6 +139,7 @@ ReactFlow.defaultProps = {
backgroundType: GridType.Dots, backgroundType: GridType.Dots,
snapToGrid: false, snapToGrid: false,
snapGrid: [16, 16], snapGrid: [16, 16],
onlyRenderVisibleNodes: true
}; };
export default ReactFlow; export default ReactFlow;
+2 -2
View File
@@ -157,10 +157,10 @@ export const getNodesInside = (
const overlappingArea = const overlappingArea =
(Math.max(rBox.x, nBox.x) - Math.min(rBox.x2, nBox.x2)) * (Math.max(rBox.y, nBox.y) - Math.min(rBox.y2, nBox.y2)); (Math.max(rBox.x, nBox.x) - Math.min(rBox.x2, nBox.x2)) * (Math.max(rBox.y, nBox.y) - Math.min(rBox.y2, nBox.y2));
if (partially) { if (partially) {
return overlappingArea > 0; return overlappingArea >= 0;
} }
const area = width * height; const area = width * height;
return overlappingArea === area; return overlappingArea >= area;
}); });
}; };