feat: Jump to Error centers the failed node in the viewport
Added centerOnNodeId prop to ProcessDiagram. When set, the diagram pans to center the specified node in the viewport. Jump to Error now selects the failed processor AND centers the viewport on it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -80,8 +80,9 @@ export function ExecutionDiagram({
|
||||
// 4. Compute overlay
|
||||
const overlay = useExecutionOverlay(detail?.processors, iterationState);
|
||||
|
||||
// 5. Manage selection
|
||||
// 5. Manage selection + center-on-node
|
||||
const [selectedProcessorId, setSelectedProcessorId] = useState<string>('');
|
||||
const [centerOnNodeId, setCenterOnNodeId] = useState<string>('');
|
||||
|
||||
// 6. Resizable splitter state
|
||||
const [splitPercent, setSplitPercent] = useState(60);
|
||||
@@ -105,12 +106,15 @@ export function ExecutionDiagram({
|
||||
document.addEventListener('pointerup', onUp);
|
||||
}, []);
|
||||
|
||||
// Jump to error: find first FAILED processor and select it
|
||||
// Jump to error: find first FAILED processor, select it, and center the viewport
|
||||
const handleJumpToError = useCallback(() => {
|
||||
if (!detail?.processors) return;
|
||||
const failed = findFailedProcessor(detail.processors);
|
||||
if (failed?.processorId) {
|
||||
setSelectedProcessorId(failed.processorId);
|
||||
// Use a unique value to re-trigger centering even if the same node
|
||||
setCenterOnNodeId('');
|
||||
requestAnimationFrame(() => setCenterOnNodeId(failed.processorId));
|
||||
}
|
||||
}, [detail?.processors]);
|
||||
|
||||
@@ -187,6 +191,7 @@ export function ExecutionDiagram({
|
||||
executionOverlay={overlay}
|
||||
iterationState={iterationState}
|
||||
onIterationChange={setIteration}
|
||||
centerOnNodeId={centerOnNodeId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ export function ProcessDiagram({
|
||||
executionOverlay,
|
||||
iterationState,
|
||||
onIterationChange,
|
||||
centerOnNodeId,
|
||||
}: ProcessDiagramProps) {
|
||||
// Route stack for drill-down navigation
|
||||
const [routeStack, setRouteStack] = useState<string[]>([routeId]);
|
||||
@@ -106,6 +107,33 @@ export function ProcessDiagram({
|
||||
}
|
||||
}, [totalWidth, totalHeight, currentRouteId]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Center on a specific node when centerOnNodeId changes
|
||||
useEffect(() => {
|
||||
if (!centerOnNodeId || sections.length === 0) return;
|
||||
const node = findNodeById(sections, centerOnNodeId);
|
||||
if (!node) return;
|
||||
const container = zoom.containerRef.current;
|
||||
if (!container) return;
|
||||
// Compute the node center in diagram coordinates
|
||||
const nodeX = (node.x ?? 0) + (node.width ?? 160) / 2;
|
||||
const nodeY = (node.y ?? 0) + (node.height ?? 40) / 2;
|
||||
// Find which section the node is in to add its offsetY
|
||||
let sectionOffsetY = 0;
|
||||
for (const section of sections) {
|
||||
const found = findNodeInSection(section.nodes, centerOnNodeId);
|
||||
if (found) { sectionOffsetY = section.offsetY; break; }
|
||||
}
|
||||
const adjustedY = nodeY + sectionOffsetY;
|
||||
// Pan so the node center is at the viewport center
|
||||
const cw = container.clientWidth;
|
||||
const ch = container.clientHeight;
|
||||
const scale = zoom.state.scale;
|
||||
zoom.panTo(
|
||||
cw / 2 - nodeX * scale,
|
||||
ch / 2 - adjustedY * scale,
|
||||
);
|
||||
}, [centerOnNodeId]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Resolve execution state for a node. ENDPOINT nodes (the route's "from:")
|
||||
// don't appear in the processor execution tree, but should be marked as
|
||||
// COMPLETED when the route executed (i.e., overlay has any entries).
|
||||
@@ -415,6 +443,13 @@ function findInChildren(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function findNodeInSection(
|
||||
nodes: DiagramNodeType[],
|
||||
nodeId: string,
|
||||
): boolean {
|
||||
return !!findInChildren(nodes, nodeId) || nodes.some(n => n.id === nodeId);
|
||||
}
|
||||
|
||||
function topLevelEdge(
|
||||
edge: import('../../api/queries/diagrams').DiagramEdge,
|
||||
nodes: DiagramNodeType[],
|
||||
|
||||
@@ -35,4 +35,6 @@ export interface ProcessDiagramProps {
|
||||
iterationState?: Map<string, IterationInfo>;
|
||||
/** Called when user changes iteration on a compound stepper */
|
||||
onIterationChange?: (compoundNodeId: string, iterationIndex: number) => void;
|
||||
/** When set, the diagram pans to center this node in the viewport */
|
||||
centerOnNodeId?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user