From eedff63399c8914e7a4edd0cfa7c0dccc98126c5 Mon Sep 17 00:00:00 2001 From: moklick Date: Sat, 23 Mar 2024 16:25:01 +0100 Subject: [PATCH] feat(devtools): add change logger --- .../ReactFlowDevTools/ChangeLogger.tsx | 82 ++++++++++++++++++ .../ReactFlowDevTools/NodeInspector.tsx | 86 +++++++++++++------ .../DevTools/ReactFlowDevTools/index.tsx | 4 + 3 files changed, 146 insertions(+), 26 deletions(-) create mode 100644 examples/react/src/examples/DevTools/ReactFlowDevTools/ChangeLogger.tsx diff --git a/examples/react/src/examples/DevTools/ReactFlowDevTools/ChangeLogger.tsx b/examples/react/src/examples/DevTools/ReactFlowDevTools/ChangeLogger.tsx new file mode 100644 index 00000000..7a137e33 --- /dev/null +++ b/examples/react/src/examples/DevTools/ReactFlowDevTools/ChangeLogger.tsx @@ -0,0 +1,82 @@ +import { useEffect, useRef, useState } from 'react'; +import { NodeChange, OnNodesChange, useStore, useStoreApi } from '@xyflow/react'; + +type ChangeLoggerProps = { + color?: string; + limit?: number; +}; + +type ChangeInfoProps = { + change: NodeChange; +}; + +function ChangeInfo({ change, index }: ChangeInfoProps) { + const id = 'id' in change ? change.id : '-'; + const { type } = change; + + return ( +
+
node id: {id}
+
+ {type === 'add' ? JSON.stringify(change.item, null, 2) : null} + {type === 'dimensions' ? `${change.dimensions?.width} × ${change.dimensions?.height}` : null} + {type === 'position' ? `position: ${change.position?.x.toFixed(1)}, ${change.position?.y.toFixed(1)}` : null} + {type === 'remove' ? 'remove' : null} + {type === 'replace' ? JSON.stringify(change.item, null, 2) : null} + {type === 'select' ? (change.selected ? 'select' : 'unselect') : null} +
+
+ ); +} + +export default function ChangeLogger({ color = '#555', limit = 20 }: ChangeLoggerProps) { + const [changes, setChanges] = useState([]); + const onNodesChangeIntercepted = useRef(false); + const onNodesChange = useStore((s) => s.onNodesChange); + const store = useStoreApi(); + + useEffect(() => { + if (!onNodesChange || onNodesChangeIntercepted.current) { + return; + } + + onNodesChangeIntercepted.current = true; + const userOnNodesChange = onNodesChange; + + const onNodesChangeLogger: OnNodesChange = (changes) => { + userOnNodesChange(changes); + + requestAnimationFrame(() => { + setChanges((c) => { + changes.forEach((change) => { + if (c.length >= limit) { + c.pop(); + } + + c = [change, ...c]; + }); + return c; + }); + }); + }; + + store.setState({ onNodesChange: onNodesChangeLogger }); + }, [onNodesChange]); + + return ( +
+
+ Change Logger +
+
+ {changes.length === 0 ? ( + <>no changes triggered + ) : ( + changes.map((change, index) => ) + )} +
+
+ ); +} diff --git a/examples/react/src/examples/DevTools/ReactFlowDevTools/NodeInspector.tsx b/examples/react/src/examples/DevTools/ReactFlowDevTools/NodeInspector.tsx index 34ebc361..ae07029a 100644 --- a/examples/react/src/examples/DevTools/ReactFlowDevTools/NodeInspector.tsx +++ b/examples/react/src/examples/DevTools/ReactFlowDevTools/NodeInspector.tsx @@ -1,40 +1,74 @@ import { useNodes, ViewportPortal } from '@xyflow/react'; +type NodeInfoProps = { + id: string; + type: string; + x: number; + y: number; + width?: number; + height?: number; + data: any; +}; + +function NodeInfo({ id, type, x, y, width, height, data }: NodeInfoProps) { + if (!width || !height) { + return
; + } + + return ( +
+
id: {id}
+
type: {type}
+
+ position: {x.toFixed(1)}, {y.toFixed(1)} +
+
+ dimensions: {width} × {height} +
+
data: {JSON.stringify(data, null, 2)}
+
+ ); +} + type NodeInspectorProps = { color: string; }; -export default function NodeInspector({ color = '#aaa' }: NodeInspectorProps) { +export default function NodeInspector({ color = '#555' }: NodeInspectorProps) { const nodes = useNodes(); return ( - {nodes.map((node) => { - const x = node.computed?.positionAbsolute?.x || 0; - const y = node.computed?.positionAbsolute?.y || 0; - const height = node.computed?.height || 0; +
+ {nodes.map((node) => { + const x = node.computed?.positionAbsolute?.x || 0; + const y = node.computed?.positionAbsolute?.y || 0; + const width = node.computed?.width || 0; + const height = node.computed?.height || 0; - return ( -
-
id: {node.id}
-
type: {node.type}
-
- position: {x.toFixed(1)}, {y.toFixed(1)} -
-
data: {JSON.stringify(node.data, null, 2)}
-
- ); - })} + return ( + + ); + })} +
); } diff --git a/examples/react/src/examples/DevTools/ReactFlowDevTools/index.tsx b/examples/react/src/examples/DevTools/ReactFlowDevTools/index.tsx index 8c521aa0..2b488b7c 100644 --- a/examples/react/src/examples/DevTools/ReactFlowDevTools/index.tsx +++ b/examples/react/src/examples/DevTools/ReactFlowDevTools/index.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { Panel, PanelPosition } from '@xyflow/react'; import NodeInspector from './NodeInspector'; +import ChangeLogger from './ChangeLogger'; type ReactFlowDevToolsProps = { position?: PanelPosition; @@ -10,13 +11,16 @@ type ReactFlowDevToolsProps = { export default function ReactFlowDevTools({ color = '#aaa', position = 'top-left' }: ReactFlowDevToolsProps) { const [isNodeInspectorActive, setIsNodeInspectorActive] = useState(false); + const [isChangeLoggerActive, setIsChangeLoggerActive] = useState(false); return ( <> + {isNodeInspectorActive && } + {isChangeLoggerActive && } ); }