fix: scrollable headers/timeline, CodeBlock for body, ELK node alignment

- Make headers tab and timeline tab scrollable when content overflows
- Replace custom <pre> code block with design system CodeBlock component
  for body tabs (Input/Output) to match existing styleguide
- Add LINEAR_SEGMENTS node placement strategy to ELK layout to fix
  Y-offset misalignment between nodes in left-to-right diagrams
  (e.g., ENDPOINT at different Y level than subsequent processors)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-27 19:34:25 +01:00
parent 3d5d462de0
commit 3027e9b24f
3 changed files with 17 additions and 34 deletions

View File

@@ -13,6 +13,7 @@ import org.eclipse.elk.core.RecursiveGraphLayoutEngine;
import org.eclipse.elk.core.options.CoreOptions;
import org.eclipse.elk.core.options.Direction;
import org.eclipse.elk.core.options.HierarchyHandling;
import org.eclipse.elk.alg.layered.options.NodePlacementStrategy;
import org.eclipse.elk.core.util.BasicProgressMonitor;
import org.eclipse.elk.graph.ElkBendPoint;
import org.eclipse.elk.graph.ElkEdge;
@@ -181,6 +182,8 @@ public class ElkDiagramRenderer implements DiagramRenderer {
rootNode.setProperty(CoreOptions.SPACING_NODE_NODE, NODE_SPACING);
rootNode.setProperty(CoreOptions.SPACING_EDGE_NODE, EDGE_SPACING);
rootNode.setProperty(CoreOptions.HIERARCHY_HANDLING, HierarchyHandling.INCLUDE_CHILDREN);
rootNode.setProperty(org.eclipse.elk.alg.layered.options.LayeredOptions.NODE_PLACEMENT_STRATEGY,
NodePlacementStrategy.LINEAR_SEGMENTS);
// Build index of all RouteNodes (flat list from graph + recursive children)
Map<String, RouteNode> routeNodeMap = new HashMap<>();

View File

@@ -293,12 +293,13 @@
display: flex;
gap: 0;
min-height: 0;
overflow-y: auto;
max-height: 100%;
}
.headersColumn {
flex: 1;
min-width: 0;
overflow: hidden;
}
.headersColumn + .headersColumn {
@@ -457,6 +458,8 @@
display: flex;
flex-direction: column;
gap: 2px;
overflow-y: auto;
max-height: 100%;
}
.ganttRow {

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { CodeBlock } from '@cameleer/design-system';
import styles from '../ExecutionDiagram.module.css';
interface BodyTabProps {
@@ -6,22 +6,22 @@ interface BodyTabProps {
label: string;
}
function detectFormat(text: string): 'JSON' | 'XML' | 'Text' {
function detectLanguage(text: string): string {
const trimmed = text.trimStart();
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
try {
JSON.parse(text);
return 'JSON';
return 'json';
} catch {
// not valid JSON
}
}
if (trimmed.startsWith('<')) return 'XML';
return 'Text';
if (trimmed.startsWith('<')) return 'xml';
return 'text';
}
function formatBody(text: string, format: string): string {
if (format === 'JSON') {
function formatBody(text: string, language: string): string {
if (language === 'json') {
try {
return JSON.stringify(JSON.parse(text), null, 2);
} catch {
@@ -31,40 +31,17 @@ function formatBody(text: string, format: string): string {
return text;
}
function byteSize(text: string): string {
const bytes = new TextEncoder().encode(text).length;
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
export function BodyTab({ body, label }: BodyTabProps) {
const [copied, setCopied] = useState(false);
if (!body) {
return <div className={styles.emptyState}>No {label.toLowerCase()} body available</div>;
}
const format = detectFormat(body);
const formatted = formatBody(body, format);
function handleCopy() {
navigator.clipboard.writeText(body!).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 1500);
});
}
const language = detectLanguage(body);
const formatted = formatBody(body, language);
return (
<div>
<div className={styles.codeHeader}>
<span className={styles.codeFormat}>{format}</span>
<span className={styles.codeSize}>{byteSize(body)}</span>
<button className={styles.codeCopyBtn} onClick={handleCopy} type="button">
{copied ? 'Copied' : 'Copy'}
</button>
</div>
<pre className={styles.codeBlock}>{formatted}</pre>
<CodeBlock content={formatted} language={language} copyable />
</div>
);
}