From 9527261719e4358efed67e8d90a16192612450bb Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 25 Jan 2023 17:47:42 +0100 Subject: [PATCH] feat(onError): add onError prop #2772 --- packages/core/src/components/Edges/wrapEdge.tsx | 4 ++-- packages/core/src/components/Handle/index.tsx | 7 +++++-- packages/core/src/components/Handle/utils.ts | 2 +- .../core/src/components/StoreUpdater/index.tsx | 3 +++ .../src/container/EdgeRenderer/MarkerSymbols.tsx | 5 +++-- packages/core/src/container/GraphView/index.tsx | 14 +++++++++----- .../core/src/container/NodeRenderer/index.tsx | 13 +++++-------- .../core/src/container/NodeRenderer/utils.ts | 2 -- packages/core/src/container/ReactFlow/index.tsx | 2 ++ packages/core/src/container/ReactFlow/utils.ts | 3 ++- packages/core/src/hooks/useDrag/index.ts | 3 ++- packages/core/src/hooks/useDrag/utils.ts | 9 +++++---- packages/core/src/hooks/useResizeHandler.ts | 8 ++++---- .../core/src/hooks/useUpdateNodePositions.ts | 16 ++++++++++++---- packages/core/src/store/initialState.ts | 2 ++ packages/core/src/styles/init.css | 2 +- packages/core/src/types/component-props.ts | 2 ++ packages/core/src/types/general.ts | 3 +++ packages/core/src/utils/graph.ts | 6 +++--- packages/core/src/utils/index.ts | 4 ++-- 20 files changed, 68 insertions(+), 42 deletions(-) diff --git a/packages/core/src/components/Edges/wrapEdge.tsx b/packages/core/src/components/Edges/wrapEdge.tsx index 36c49615..9d10f944 100644 --- a/packages/core/src/components/Edges/wrapEdge.tsx +++ b/packages/core/src/components/Edges/wrapEdge.tsx @@ -9,7 +9,7 @@ import { EdgeAnchor } from './EdgeAnchor'; import { getMarkerId } from '../../utils/graph'; import { getMouseHandler } from './utils'; import { elementSelectionKeys } from '../../utils'; -import type { EdgeProps, WrapEdgeProps, Connection } from '../../types'; +import type { EdgeProps, WrapEdgeProps, Connection, HandleType } from '../../types'; export default (EdgeComponent: ComponentType) => { const EdgeWrapper = ({ @@ -115,7 +115,7 @@ export default (EdgeComponent: ComponentType) => { getState: store.getState, setState: store.setState, isValidConnection, - elementEdgeUpdaterType: handleType, + edgeUpdaterType: handleType, onEdgeUpdateEnd: _onEdgeUpdateEnd, }); }; diff --git a/packages/core/src/components/Handle/index.tsx b/packages/core/src/components/Handle/index.tsx index aa887d2f..1bf631b9 100644 --- a/packages/core/src/components/Handle/index.tsx +++ b/packages/core/src/components/Handle/index.tsx @@ -5,7 +5,7 @@ import { shallow } from 'zustand/shallow'; import { useStore, useStoreApi } from '../../hooks/useStore'; import { useNodeId } from '../../contexts/NodeIdContext'; import { handlePointerDown } from './handler'; -import { devWarn, getHostForElement, isMouseEvent } from '../../utils'; +import { getHostForElement, isMouseEvent } from '../../utils'; import { addEdge } from '../../utils/graph'; import { Position } from '../../types'; import type { HandleProps, Connection, ReactFlowState } from '../../types'; @@ -42,7 +42,10 @@ const Handle = forwardRef( const nodeId = useNodeId(); if (!nodeId) { - devWarn('Handle: No node id found. Make sure to only use a Handle inside a custom Node.'); + store + .getState() + .onError?.('010', 'Handle: No node id found. Make sure to only use a Handle inside a custom Node.'); + return null; } diff --git a/packages/core/src/components/Handle/utils.ts b/packages/core/src/components/Handle/utils.ts index 74c0ef5a..a049a2cc 100644 --- a/packages/core/src/components/Handle/utils.ts +++ b/packages/core/src/components/Handle/utils.ts @@ -98,7 +98,7 @@ export function isValidHandle( const isValid = connectionMode === ConnectionMode.Strict ? (isTarget && handleIsSource) || (!isTarget && handleIsTarget) - : handleNodeId !== handle.nodeId || handleId !== handle.id; + : handleNodeId !== fromNodeId || handleId !== fromHandleId; if (isValid) { result.isValid = isValidConnection(connection); diff --git a/packages/core/src/components/StoreUpdater/index.tsx b/packages/core/src/components/StoreUpdater/index.tsx index 55af8fb4..c5645944 100644 --- a/packages/core/src/components/StoreUpdater/index.tsx +++ b/packages/core/src/components/StoreUpdater/index.tsx @@ -47,6 +47,7 @@ type StoreUpdaterProps = Pick< | 'elevateNodesOnSelect' | 'autoPanOnConnect' | 'autoPanOnNodeDrag' + | 'onError' > & { rfId: string }; const selector = (s: ReactFlowState) => ({ @@ -123,6 +124,7 @@ const StoreUpdater = ({ rfId, autoPanOnConnect, autoPanOnNodeDrag, + onError, }: StoreUpdaterProps) => { const { setNodes, @@ -178,6 +180,7 @@ const StoreUpdater = ({ useDirectStoreUpdater('rfId', rfId, store.setState); useDirectStoreUpdater('autoPanOnConnect', autoPanOnConnect, store.setState); useDirectStoreUpdater('autoPanOnNodeDrag', autoPanOnNodeDrag, store.setState); + useDirectStoreUpdater('onError', onError, store.setState); useStoreUpdater(nodes, setNodes); useStoreUpdater(edges, setEdges); diff --git a/packages/core/src/container/EdgeRenderer/MarkerSymbols.tsx b/packages/core/src/container/EdgeRenderer/MarkerSymbols.tsx index fbdd4972..d95fd70d 100644 --- a/packages/core/src/container/EdgeRenderer/MarkerSymbols.tsx +++ b/packages/core/src/container/EdgeRenderer/MarkerSymbols.tsx @@ -1,8 +1,8 @@ import { useMemo } from 'react'; -import { devWarn } from '../../utils'; import { MarkerType } from '../../types'; import type { EdgeMarker } from '../../types'; +import { useStoreApi } from '../../hooks/useStore'; type SymbolProps = Omit; @@ -38,11 +38,12 @@ export const MarkerSymbols = { }; export function useMarkerSymbol(type: MarkerType) { + const store = useStoreApi(); const symbol = useMemo(() => { const symbolExists = Object.prototype.hasOwnProperty.call(MarkerSymbols, type); if (!symbolExists) { - devWarn(`Marker type "${type}" doesn't exist. Help: https://reactflow.dev/error#900`); + store.getState().onError?.('009', `Marker type "${type}" doesn't exist.`); return null; } diff --git a/packages/core/src/container/GraphView/index.tsx b/packages/core/src/container/GraphView/index.tsx index 425d9180..3de2550c 100644 --- a/packages/core/src/container/GraphView/index.tsx +++ b/packages/core/src/container/GraphView/index.tsx @@ -6,6 +6,7 @@ import EdgeRenderer from '../EdgeRenderer'; import ViewportWrapper from '../Viewport'; import useOnInitHandler from '../../hooks/useOnInitHandler'; import type { EdgeTypesWrapped, NodeTypesWrapped, ReactFlowProps } from '../../types'; +import ConnectionLine from '../../components/ConnectionLine'; export type GraphViewProps = Omit & Required< @@ -149,10 +150,6 @@ const GraphView = ({ edgeTypes={edgeTypes} onEdgeClick={onEdgeClick} onEdgeDoubleClick={onEdgeDoubleClick} - connectionLineType={connectionLineType} - connectionLineStyle={connectionLineStyle} - connectionLineComponent={connectionLineComponent} - connectionLineContainerStyle={connectionLineContainerStyle} onEdgeUpdate={onEdgeUpdate} onlyRenderVisibleElements={onlyRenderVisibleElements} onEdgeContextMenu={onEdgeContextMenu} @@ -167,7 +164,14 @@ const GraphView = ({ elevateEdgesOnSelect={!!elevateEdgesOnSelect} disableKeyboardA11y={disableKeyboardA11y} rfId={rfId} - /> + > + +
({ nodesFocusable: s.nodesFocusable, elementsSelectable: s.elementsSelectable, updateNodeDimensions: s.updateNodeDimensions, + onError: s.onError, }); const NodeRenderer = (props: NodeRendererProps) => { - const { nodesDraggable, nodesConnectable, nodesFocusable, elementsSelectable, updateNodeDimensions } = useStore( - selector, - shallow - ); + const { nodesDraggable, nodesConnectable, nodesFocusable, elementsSelectable, updateNodeDimensions, onError } = + useStore(selector, shallow); const nodes = useVisibleNodes(props.onlyRenderVisibleElements); const resizeObserverRef = useRef(); @@ -78,9 +77,7 @@ const NodeRenderer = (props: NodeRendererProps) => { let nodeType = node.type || 'default'; if (!props.nodeTypes[nodeType]) { - devWarn( - `Node type "${nodeType}" not found. Using fallback type "default". Help: https://reactflow.dev/error#300` - ); + onError?.('003', `Node type "${nodeType}" not found. Using fallback type "default".`); nodeType = 'default'; } diff --git a/packages/core/src/container/NodeRenderer/utils.ts b/packages/core/src/container/NodeRenderer/utils.ts index ebac7413..6abc695d 100644 --- a/packages/core/src/container/NodeRenderer/utils.ts +++ b/packages/core/src/container/NodeRenderer/utils.ts @@ -5,7 +5,6 @@ import InputNode from '../../components/Nodes/InputNode'; import OutputNode from '../../components/Nodes/OutputNode'; import GroupNode from '../../components/Nodes/GroupNode'; import wrapNode from '../../components/Nodes/wrapNode'; -import { devWarn } from '../../utils'; import type { NodeTypes, NodeProps, NodeTypesWrapped, NodeOrigin, XYPosition } from '../../types'; export type CreateNodeTypes = (nodeTypes: NodeTypes) => NodeTypesWrapped; @@ -51,7 +50,6 @@ export const getPositionWithOrigin = ({ } if (origin[0] < 0 || origin[1] < 0 || origin[0] > 1 || origin[1] > 1) { - devWarn('nodeOrigin must be between 0 and 1'); return { x, y }; } diff --git a/packages/core/src/container/ReactFlow/index.tsx b/packages/core/src/container/ReactFlow/index.tsx index 67964f0f..120a5efa 100644 --- a/packages/core/src/container/ReactFlow/index.tsx +++ b/packages/core/src/container/ReactFlow/index.tsx @@ -163,6 +163,7 @@ const ReactFlow = forwardRef( autoPanOnConnect = true, autoPanOnNodeDrag = true, connectionRadius = 20, + onError, style, id, ...rest @@ -292,6 +293,7 @@ const ReactFlow = forwardRef( rfId={rfId} autoPanOnConnect={autoPanOnConnect} autoPanOnNodeDrag={autoPanOnNodeDrag} + onError={onError} /> {children} diff --git a/packages/core/src/container/ReactFlow/utils.ts b/packages/core/src/container/ReactFlow/utils.ts index 2f8a9239..f76f91f1 100644 --- a/packages/core/src/container/ReactFlow/utils.ts +++ b/packages/core/src/container/ReactFlow/utils.ts @@ -17,7 +17,8 @@ export function useNodeOrEdgeTypes(nodeOrEdgeTypes: any, createTypes: any): any const typeKeys = Object.keys(nodeOrEdgeTypes); if (shallow(typesKeysRef.current, typeKeys)) { devWarn( - "It looks like you have created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them. Help: https://reactflow.dev/error#200" + '002', + "It looks like you've created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them." ); } diff --git a/packages/core/src/hooks/useDrag/index.ts b/packages/core/src/hooks/useDrag/index.ts index 868b6387..c6f3d269 100644 --- a/packages/core/src/hooks/useDrag/index.ts +++ b/packages/core/src/hooks/useDrag/index.ts @@ -61,6 +61,7 @@ function useDrag({ snapGrid, snapToGrid, nodeOrigin, + onError, } = store.getState(); lastPos.current = { x, y }; @@ -75,7 +76,7 @@ function useDrag({ nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]); } - const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent, nodeOrigin); + const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent, nodeOrigin, onError); // we want to make sure that we only fire a change event when there is a changes hasChange = hasChange || n.position.x !== updatedPos.position.x || n.position.y !== updatedPos.position.y; diff --git a/packages/core/src/hooks/useDrag/utils.ts b/packages/core/src/hooks/useDrag/utils.ts index 74484baf..028387aa 100644 --- a/packages/core/src/hooks/useDrag/utils.ts +++ b/packages/core/src/hooks/useDrag/utils.ts @@ -1,7 +1,7 @@ import type { RefObject } from 'react'; -import { clampPosition, devWarn, isNumeric } from '../../utils'; -import type { CoordinateExtent, Node, NodeDragItem, NodeInternals, NodeOrigin, XYPosition } from '../../types'; +import { clampPosition, isNumeric } from '../../utils'; +import type { CoordinateExtent, Node, NodeDragItem, NodeInternals, NodeOrigin, OnError, XYPosition } from '../../types'; import { getNodePositionWithOrigin } from '../../utils/graph'; export function isParentSelected(node: Node, nodeInternals: NodeInternals): boolean { @@ -62,7 +62,8 @@ export function calcNextPosition( nextPosition: XYPosition, nodeInternals: NodeInternals, nodeExtent?: CoordinateExtent, - nodeOrigin: NodeOrigin = [0, 0] + nodeOrigin: NodeOrigin = [0, 0], + onError?: OnError ): { position: XYPosition; positionAbsolute: XYPosition } { let currentExtent = node.extent || nodeExtent; @@ -81,7 +82,7 @@ export function calcNextPosition( ] : currentExtent; } else { - devWarn('Only child nodes can use a parent extent. Help: https://reactflow.dev/error#500'); + onError?.('005', 'Only child nodes can use a parent extent.'); currentExtent = nodeExtent; } diff --git a/packages/core/src/hooks/useResizeHandler.ts b/packages/core/src/hooks/useResizeHandler.ts index d2ca3e63..5fc9aebc 100644 --- a/packages/core/src/hooks/useResizeHandler.ts +++ b/packages/core/src/hooks/useResizeHandler.ts @@ -2,7 +2,7 @@ import { useEffect } from 'react'; import type { MutableRefObject } from 'react'; import { useStoreApi } from '../hooks/useStore'; -import { devWarn, getDimensions } from '../utils'; +import { getDimensions } from '../utils'; function useResizeHandler(rendererNode: MutableRefObject): void { const store = useStoreApi(); @@ -18,9 +18,9 @@ function useResizeHandler(rendererNode: MutableRefObject) const size = getDimensions(rendererNode.current); if (size.height === 0 || size.width === 0) { - devWarn( - 'The React Flow parent container needs a width and a height to render the graph. Help: https://reactflow.dev/error#400' - ); + store + .getState() + .onError?.('004', 'The React Flow parent container needs a width and a height to render the graph.'); } store.setState({ width: size.width || 500, height: size.height || 500 }); diff --git a/packages/core/src/hooks/useUpdateNodePositions.ts b/packages/core/src/hooks/useUpdateNodePositions.ts index b20ea4d1..131f85cd 100644 --- a/packages/core/src/hooks/useUpdateNodePositions.ts +++ b/packages/core/src/hooks/useUpdateNodePositions.ts @@ -7,7 +7,8 @@ function useUpdateNodePositions() { const store = useStoreApi(); const updatePositions = useCallback((params: { x: number; y: number; isShiftPressed: boolean }) => { - const { nodeInternals, nodeExtent, updateNodePositions, getNodes, snapToGrid, snapGrid } = store.getState(); + const { nodeInternals, nodeExtent, updateNodePositions, getNodes, snapToGrid, snapGrid, onError } = + store.getState(); const selectedNodes = getNodes().filter((n) => n.selected); // by default a node moves 5px on each key press, or 20px if shift is pressed // if snap grid is enabled, we use that for the velocity. @@ -27,10 +28,17 @@ function useUpdateNodePositions() { nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]); } - const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent); + const { positionAbsolute, position } = calcNextPosition( + n, + nextPosition, + nodeInternals, + nodeExtent, + undefined, + onError + ); - n.position = updatedPos.position; - n.positionAbsolute = updatedPos.positionAbsolute; + n.position = position; + n.positionAbsolute = positionAbsolute; } return n; diff --git a/packages/core/src/store/initialState.ts b/packages/core/src/store/initialState.ts index 6c475b5b..afccec2b 100644 --- a/packages/core/src/store/initialState.ts +++ b/packages/core/src/store/initialState.ts @@ -1,3 +1,4 @@ +import { devWarn } from '../utils'; import { ConnectionMode } from '../types'; import type { CoordinateExtent, ReactFlowStore } from '../types'; @@ -59,6 +60,7 @@ const initialState: ReactFlowStore = { autoPanOnConnect: true, autoPanOnNodeDrag: true, connectionRadius: 20, + onError: devWarn, }; export default initialState; diff --git a/packages/core/src/styles/init.css b/packages/core/src/styles/init.css index a621794f..9cecc294 100644 --- a/packages/core/src/styles/init.css +++ b/packages/core/src/styles/init.css @@ -98,7 +98,7 @@ .react-flow__connection { pointer-events: none; - &.animated { + .animated { stroke-dasharray: 5; animation: dashdraw 0.5s linear infinite; } diff --git a/packages/core/src/types/component-props.ts b/packages/core/src/types/component-props.ts index bafb8d5e..1614805a 100644 --- a/packages/core/src/types/component-props.ts +++ b/packages/core/src/types/component-props.ts @@ -36,6 +36,7 @@ import type { EdgeMouseHandler, HandleType, SelectionMode, + OnError, } from '.'; export type ReactFlowProps = HTMLAttributes & { @@ -142,6 +143,7 @@ export type ReactFlowProps = HTMLAttributes & { autoPanOnNodeDrag?: boolean; autoPanOnConnect?: boolean; connectionRadius?: number; + onError?: OnError; }; export type ReactFlowRefType = HTMLDivElement; diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts index ba0de157..9dbd3dce 100644 --- a/packages/core/src/types/general.ts +++ b/packages/core/src/types/general.ts @@ -206,6 +206,7 @@ export type ReactFlowStore = { onNodesDelete?: OnNodesDelete; onEdgesDelete?: OnEdgesDelete; + onError?: OnError; // event handlers onViewportChangeStart?: OnViewportChange; @@ -270,3 +271,5 @@ export type SelectionRect = Rect & { startX: number; startY: number; }; + +export type OnError = (id: string, message: string) => void; diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts index 7e94f766..4b1182f4 100644 --- a/packages/core/src/utils/graph.ts +++ b/packages/core/src/utils/graph.ts @@ -70,7 +70,7 @@ const connectionExists = (edge: Edge, edges: Edge[]) => { export const addEdge = (edgeParams: Edge | Connection, edges: Edge[]): Edge[] => { if (!edgeParams.source || !edgeParams.target) { - devWarn("Can't create edge. An edge needs a source and a target. Help: https://reactflow.dev/error#600"); + devWarn('006', "Can't create edge. An edge needs a source and a target."); return edges; } @@ -94,7 +94,7 @@ export const addEdge = (edgeParams: Edge | Connection, edges: Edge[]): Edge[] => export const updateEdge = (oldEdge: Edge, newConnection: Connection, edges: Edge[]): Edge[] => { if (!newConnection.source || !newConnection.target) { - devWarn("Can't create a new edge. An edge needs a source and a target. Help: https://reactflow.dev/error#600"); + devWarn('006', "Can't create a new edge. An edge needs a source and a target."); return edges; } @@ -102,7 +102,7 @@ export const updateEdge = (oldEdge: Edge, newConnection: Connection, edges: Edge const foundEdge = edges.find((e) => e.id === oldEdge.id) as Edge; if (!foundEdge) { - devWarn(`The old edge with id=${oldEdge.id} does not exist. Help: https://reactflow.dev/error#700`); + devWarn('007', `The old edge with id=${oldEdge.id} does not exist.`); return edges; } diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 5bfd52c2..de449277 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -89,9 +89,9 @@ export const internalsSymbol = Symbol.for('internals'); // used for a11y key board controls for nodes and edges export const elementSelectionKeys = ['Enter', ' ', 'Escape']; -export const devWarn = (message: string) => { +export const devWarn = (id: string, message: string) => { if (process.env.NODE_ENV === 'development') { - console.warn(`[React Flow]: ${message}`); + console.warn(`[React Flow]: ${message} Help: https://reactflow.dev/error#${id}`); } };