feat: add interactive ProcessDiagram SVG component (sub-project 1/3)
New interactive route diagram component with SVG rendering using server-computed ELK layout coordinates. TIBCO BW5-inspired top-bar card node style with zoom/pan, hover toolbars, config badges, and error handler sections below the main flow. Backend: add direction query parameter (LR/TB) to diagram render endpoints, defaulting to left-to-right layout. Frontend: 14-file ProcessDiagram component in ui/src/components/ with DiagramNode, CompoundNode, DiagramEdge, ConfigBadge, NodeToolbar, ErrorSection, ZoomControls, and supporting hooks. Dev test page at /dev/diagram for validation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,7 @@ public class DiagramRenderController {
|
||||
@ApiResponse(responseCode = "404", description = "Diagram not found")
|
||||
public ResponseEntity<?> renderDiagram(
|
||||
@PathVariable String contentHash,
|
||||
@RequestParam(defaultValue = "LR") String direction,
|
||||
HttpServletRequest request) {
|
||||
|
||||
Optional<RouteGraph> graphOpt = diagramStore.findByContentHash(contentHash);
|
||||
@@ -76,7 +77,7 @@ public class DiagramRenderController {
|
||||
// without also accepting everything (*/*). This means "application/json"
|
||||
// must appear and wildcards must not dominate the preference.
|
||||
if (accept != null && isJsonPreferred(accept)) {
|
||||
DiagramLayout layout = diagramRenderer.layoutJson(graph);
|
||||
DiagramLayout layout = diagramRenderer.layoutJson(graph, direction);
|
||||
return ResponseEntity.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(layout);
|
||||
@@ -96,7 +97,8 @@ public class DiagramRenderController {
|
||||
@ApiResponse(responseCode = "404", description = "No diagram found for the given application and route")
|
||||
public ResponseEntity<DiagramLayout> findByApplicationAndRoute(
|
||||
@RequestParam String application,
|
||||
@RequestParam String routeId) {
|
||||
@RequestParam String routeId,
|
||||
@RequestParam(defaultValue = "LR") String direction) {
|
||||
List<String> agentIds = registryService.findByApplication(application).stream()
|
||||
.map(AgentInfo::id)
|
||||
.toList();
|
||||
@@ -115,7 +117,7 @@ public class DiagramRenderController {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
DiagramLayout layout = diagramRenderer.layoutJson(graphOpt.get());
|
||||
DiagramLayout layout = diagramRenderer.layoutJson(graphOpt.get(), direction);
|
||||
return ResponseEntity.ok(layout);
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
|
||||
@Override
|
||||
public String renderSvg(RouteGraph graph) {
|
||||
LayoutResult result = computeLayout(graph);
|
||||
LayoutResult result = computeLayout(graph, Direction.DOWN);
|
||||
DiagramLayout layout = result.layout;
|
||||
|
||||
int svgWidth = (int) Math.ceil(layout.width()) + 2 * PADDING;
|
||||
@@ -153,21 +153,27 @@ public class ElkDiagramRenderer implements DiagramRenderer {
|
||||
|
||||
@Override
|
||||
public DiagramLayout layoutJson(RouteGraph graph) {
|
||||
return computeLayout(graph).layout;
|
||||
return computeLayout(graph, Direction.RIGHT).layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiagramLayout layoutJson(RouteGraph graph, String direction) {
|
||||
Direction dir = "TB".equalsIgnoreCase(direction) ? Direction.DOWN : Direction.RIGHT;
|
||||
return computeLayout(graph, dir).layout;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Layout computation
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
private LayoutResult computeLayout(RouteGraph graph) {
|
||||
private LayoutResult computeLayout(RouteGraph graph, Direction rootDirection) {
|
||||
ElkGraphFactory factory = ElkGraphFactory.eINSTANCE;
|
||||
|
||||
// Create root node
|
||||
ElkNode rootNode = factory.createElkNode();
|
||||
rootNode.setIdentifier("root");
|
||||
rootNode.setProperty(CoreOptions.ALGORITHM, "org.eclipse.elk.layered");
|
||||
rootNode.setProperty(CoreOptions.DIRECTION, Direction.DOWN);
|
||||
rootNode.setProperty(CoreOptions.DIRECTION, rootDirection);
|
||||
rootNode.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING);
|
||||
rootNode.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING);
|
||||
rootNode.setProperty(CoreOptions.HIERARCHY_HANDLING, HierarchyHandling.INCLUDE_CHILDREN);
|
||||
|
||||
Reference in New Issue
Block a user