Files
xyflow/examples/react/src/examples/Stress/index.tsx
2024-07-08 13:18:07 +02:00

245 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useCallback, useRef, useEffect } from 'react';
import {
ReactFlow,
Edge,
Node,
NodeChange,
applyNodeChanges,
Connection,
addEdge,
applyEdgeChanges,
EdgeChange,
Controls,
Background,
Panel,
} from '@xyflow/react';
import { getNodesAndEdges } from './utils';
import { FrameRecorder, generateMouseEventParamsTargetingNode, nextFrame } from './performanceUtils';
const { nodes: initialNodes, edges: initialEdges } = getNodesAndEdges(25, 25);
const StressFlow = () => {
const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges);
const onConnect = useCallback((connection: Connection) => {
setEdges((eds) => addEdge(connection, eds));
}, []);
const dragInViewport = async () => {
// Note: selecting specifically node 18, as its normally located in the right part of the viewport
// which means dragging it left is safe to do without scrolling the viewport.
const nodeElement = document.querySelector('.react-flow__node[data-id="18"]');
if (!nodeElement) throw new Error('Node with id 18 not found');
const frameRecorder = new FrameRecorder();
// Hold down the mouse
frameRecorder.setStage('mousedown');
const mouseDownEvent = generateMouseEventParamsTargetingNode(nodeElement);
nodeElement.dispatchEvent(new MouseEvent('mousedown', mouseDownEvent));
await nextFrame();
// Start at the node position and move the mouse 5px to the left on every frame
frameRecorder.setStage('mousemove');
let currentXPosition = mouseDownEvent.clientX;
for (let iteration = 0; iteration < 20; ++iteration) {
const movementX = -5;
currentXPosition += movementX;
nodeElement.dispatchEvent(
new MouseEvent('mousemove', {
...mouseDownEvent,
clientX: currentXPosition,
screenX: currentXPosition,
movementX,
})
);
await nextFrame();
}
// Release the mouse
frameRecorder.setStage('mouseup');
nodeElement.dispatchEvent(
new MouseEvent('mouseup', {
...mouseDownEvent,
clientX: currentXPosition,
screenX: currentXPosition,
})
);
await nextFrame();
// Log the results
await frameRecorder.endRecordingAsync();
console.log('Frame durations:', frameRecorder.getFrames());
console.log(
'Frame durations for Observable (copy and paste to https://observablehq.com/@iamakulov/long-frame-visualizer):',
frameRecorder.getFramesForObservable()
);
};
const dragOutsideViewport = async () => {
const randomNodeIndex = Math.floor(Math.random() * nodes.length);
const nodeElement = document.querySelector(`.react-flow__node[data-id="${nodes[randomNodeIndex].id}"]`);
if (!nodeElement) throw new Error('Node not found');
const frameRecorder = new FrameRecorder();
// Hold down the mouse
frameRecorder.setStage('mousedown');
const mouseDownEvent = generateMouseEventParamsTargetingNode(nodeElement);
nodeElement.dispatchEvent(new MouseEvent('mousedown', mouseDownEvent));
await nextFrame();
// Move the mouse to the top of the viewport (so that the viewport starts
// scrolling up). Then, wiggle the mouse up and down to keep the viewport
// scrolling.
frameRecorder.setStage('mousemove');
let currentYPosition = 50;
for (let iteration = 0; iteration < 20; ++iteration) {
const movementY = Math.random() > 0.5 ? +2 : -2;
currentYPosition += movementY;
nodeElement.dispatchEvent(
new MouseEvent('mousemove', {
...mouseDownEvent,
clientY: currentYPosition,
screenY: currentYPosition,
movementY,
})
);
await nextFrame();
}
// Release the mouse
frameRecorder.setStage('mouseup');
nodeElement.dispatchEvent(
new MouseEvent('mouseup', {
...mouseDownEvent,
clientY: currentYPosition,
screenY: currentYPosition,
})
);
await nextFrame();
// Log the results
await frameRecorder.endRecordingAsync();
console.log('Frame durations:', frameRecorder.getFrames());
console.log(
'Frame durations for Observable (copy and paste to https://observablehq.com/@iamakulov/long-frame-visualizer):',
frameRecorder.getFramesForObservable()
);
};
const selectNode = async () => {
const randomNodeIndex = Math.floor(Math.random() * nodes.length);
const nodeElement = document.querySelector(`.react-flow__node[data-id="${nodes[randomNodeIndex].id}"]`);
if (!nodeElement) throw new Error('Node not found');
const frameRecorder = new FrameRecorder();
const mouseEvent = generateMouseEventParamsTargetingNode(nodeElement);
// mousedown
frameRecorder.setStage('mousedown');
nodeElement.dispatchEvent(new MouseEvent('mousedown', mouseEvent));
await nextFrame();
// click
frameRecorder.setStage('click');
nodeElement.dispatchEvent(new MouseEvent('click', mouseEvent));
await nextFrame();
// mouseup
frameRecorder.setStage('mouseup');
nodeElement.dispatchEvent(new MouseEvent('mouseup', mouseEvent));
await nextFrame();
// Log the results
await frameRecorder.endRecordingAsync();
console.log('Frame durations:', frameRecorder.getFrames());
console.log(
'Frame durations for Observable (copy and paste to https://observablehq.com/@iamakulov/long-frame-visualizer):',
frameRecorder.getFramesForObservable()
);
};
const [key, setKey] = useState(0);
const frameRecorderRef = useRef<FrameRecorder | null>(null);
function remount() {
frameRecorderRef.current = new FrameRecorder();
setKey((k) => k + 1);
}
useEffect(() => {
const frameRecorder = frameRecorderRef.current;
if (!frameRecorder) return;
frameRecorder.endRecordingAsync().then(() => {
console.log('Frame durations:', frameRecorder.getFrames());
console.log(
'Frame durations for Observable (copy and paste to https://observablehq.com/@iamakulov/long-frame-visualizer):',
frameRecorder.getFramesForObservable()
);
frameRecorderRef.current = null;
});
}, [key]);
const updatePos = () => {
setNodes((nds) => {
return nds.map((n) => {
return {
...n,
position: {
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
},
};
});
});
};
const updateElements = () => {
const grid = Math.ceil(Math.random() * 10);
const initialElements = getNodesAndEdges(grid, grid);
setNodes(initialElements.nodes);
setEdges(initialElements.edges);
};
const onNodesChange = useCallback((changes: NodeChange[]) => {
setNodes((ns) => applyNodeChanges(changes, ns));
}, []);
const onEdgeChange = useCallback((changes: EdgeChange[]) => {
setEdges((es) => applyEdgeChanges(changes, es));
}, []);
return (
<ReactFlow
key={key}
nodes={nodes}
edges={edges}
onConnect={onConnect}
onNodesChange={onNodesChange}
onEdgesChange={onEdgeChange}
minZoom={0.2}
fitView
panOnDrag={false}
>
<Controls />
<Background />
<Panel position="top-right">
<button onClick={selectNode}>select node</button>
<button onClick={dragInViewport}>drag node within the viewport</button>
<button onClick={dragOutsideViewport}>drag node outside of the viewport</button>
<button onClick={remount}>re-mount</button>
<button onClick={updatePos}>change pos</button>
<button onClick={updateElements}>update elements</button>
</Panel>
</ReactFlow>
);
};
export default StressFlow;