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:
@@ -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<>();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user