feat: color minimap nodes by execution overlay state
Minimap reflects execution overlay: green for completed, red for failed, grey for skipped nodes. ENDPOINT nodes are always green when overlay is active (route entry point, same as main diagram logic). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
import type { DiagramSection } from './types';
|
import type { DiagramSection } from './types';
|
||||||
import type { DiagramNode as DiagramNodeType } from '../../api/queries/diagrams';
|
import type { DiagramNode as DiagramNodeType } from '../../api/queries/diagrams';
|
||||||
|
import type { NodeExecutionState } from '../ExecutionDiagram/types';
|
||||||
import { colorForType } from './node-colors';
|
import { colorForType } from './node-colors';
|
||||||
import styles from './ProcessDiagram.module.css';
|
import styles from './ProcessDiagram.module.css';
|
||||||
|
|
||||||
@@ -21,13 +22,15 @@ interface MinimapProps {
|
|||||||
containerHeight: number;
|
containerHeight: number;
|
||||||
/** Called when user clicks/drags on minimap to pan */
|
/** Called when user clicks/drags on minimap to pan */
|
||||||
onPan: (translateX: number, translateY: number) => void;
|
onPan: (translateX: number, translateY: number) => void;
|
||||||
|
/** Execution overlay for coloring nodes */
|
||||||
|
executionOverlay?: Map<string, NodeExecutionState>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Minimap({
|
export function Minimap({
|
||||||
sections, totalWidth, totalHeight,
|
sections, totalWidth, totalHeight,
|
||||||
scale, translateX, translateY,
|
scale, translateX, translateY,
|
||||||
containerWidth, containerHeight,
|
containerWidth, containerHeight,
|
||||||
onPan,
|
onPan, executionOverlay,
|
||||||
}: MinimapProps) {
|
}: MinimapProps) {
|
||||||
const svgRef = useRef<SVGSVGElement>(null);
|
const svgRef = useRef<SVGSVGElement>(null);
|
||||||
const dragging = useRef(false);
|
const dragging = useRef(false);
|
||||||
@@ -116,7 +119,7 @@ export function Minimap({
|
|||||||
{/* Render simplified nodes for each section */}
|
{/* Render simplified nodes for each section */}
|
||||||
{sections.map((section, si) => (
|
{sections.map((section, si) => (
|
||||||
<g key={si} transform={`translate(0, ${section.offsetY})`}>
|
<g key={si} transform={`translate(0, ${section.offsetY})`}>
|
||||||
{renderMinimapNodes(section.nodes)}
|
{renderMinimapNodes(section.nodes, executionOverlay)}
|
||||||
</g>
|
</g>
|
||||||
))}
|
))}
|
||||||
</g>
|
</g>
|
||||||
@@ -138,27 +141,43 @@ export function Minimap({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMinimapNodes(nodes: DiagramNodeType[]): React.ReactNode[] {
|
function nodeColor(
|
||||||
|
node: DiagramNodeType,
|
||||||
|
overlay?: Map<string, NodeExecutionState>,
|
||||||
|
): { fill: string; opacity: number } {
|
||||||
|
if (!overlay) return { fill: colorForType(node.type), opacity: 0.7 };
|
||||||
|
const state = node.id ? overlay.get(node.id) : undefined;
|
||||||
|
if (state?.status === 'COMPLETED') return { fill: '#3D7C47', opacity: 0.85 };
|
||||||
|
if (state?.status === 'FAILED') return { fill: '#C0392B', opacity: 0.85 };
|
||||||
|
// ENDPOINT is always traversed when overlay is active (route entry point)
|
||||||
|
if (node.type === 'ENDPOINT' && overlay.size > 0) return { fill: '#3D7C47', opacity: 0.85 };
|
||||||
|
// Skipped (overlay active but node not executed)
|
||||||
|
return { fill: '#9CA3AF', opacity: 0.35 };
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMinimapNodes(
|
||||||
|
nodes: DiagramNodeType[],
|
||||||
|
overlay?: Map<string, NodeExecutionState>,
|
||||||
|
): React.ReactNode[] {
|
||||||
const elements: React.ReactNode[] = [];
|
const elements: React.ReactNode[] = [];
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
const x = node.x ?? 0;
|
const x = node.x ?? 0;
|
||||||
const y = node.y ?? 0;
|
const y = node.y ?? 0;
|
||||||
const w = node.width ?? 160;
|
const w = node.width ?? 160;
|
||||||
const h = node.height ?? 40;
|
const h = node.height ?? 40;
|
||||||
const color = colorForType(node.type);
|
|
||||||
|
|
||||||
if (node.children && node.children.length > 0) {
|
if (node.children && node.children.length > 0) {
|
||||||
// Compound: border only
|
const { fill, opacity } = nodeColor(node, overlay);
|
||||||
elements.push(
|
elements.push(
|
||||||
<rect key={node.id} x={x} y={y} width={w} height={h}
|
<rect key={node.id} x={x} y={y} width={w} height={h}
|
||||||
fill="none" stroke={color} strokeWidth={2} rx={2} opacity={0.6} />,
|
fill="none" stroke={fill} strokeWidth={2} rx={2} opacity={opacity} />,
|
||||||
);
|
);
|
||||||
elements.push(...renderMinimapNodes(node.children));
|
elements.push(...renderMinimapNodes(node.children, overlay));
|
||||||
} else {
|
} else {
|
||||||
// Leaf: filled rectangle
|
const { fill, opacity } = nodeColor(node, overlay);
|
||||||
elements.push(
|
elements.push(
|
||||||
<rect key={node.id} x={x} y={y} width={w} height={h}
|
<rect key={node.id} x={x} y={y} width={w} height={h}
|
||||||
fill={color} rx={2} opacity={0.7} />,
|
fill={fill} rx={2} opacity={opacity} />,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -409,6 +409,7 @@ export function ProcessDiagram({
|
|||||||
containerWidth={zoom.containerRef.current?.clientWidth ?? 0}
|
containerWidth={zoom.containerRef.current?.clientWidth ?? 0}
|
||||||
containerHeight={zoom.containerRef.current?.clientHeight ?? 0}
|
containerHeight={zoom.containerRef.current?.clientHeight ?? 0}
|
||||||
onPan={zoom.panTo}
|
onPan={zoom.panTo}
|
||||||
|
executionOverlay={effectiveOverlay}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ZoomControls
|
<ZoomControls
|
||||||
|
|||||||
Reference in New Issue
Block a user