v2.3 extended analysis render update

This commit is contained in:
2026-06-15 22:00:38 +02:00
parent bd44049e5e
commit ac05513b6c
30 changed files with 2341 additions and 146 deletions

View File

@@ -50,6 +50,9 @@ public class HtmlExporterCommand implements Callable<Integer> {
@Option(names = {"--open"}, description = "Automatically open the generated HTML portal in your default browser.")
private boolean openPortal;
@Option(names = {"--no-diamonds"}, description = "Disable rendering choice pseudostates as PlantUML diamonds (render them as normal state nodes instead).")
private boolean noDiamonds;
@Override
public Integer call() throws Exception {
var out = spec.commandLine().getOut();
@@ -73,7 +76,9 @@ public class HtmlExporterCommand implements Callable<Integer> {
// Force HTML and JSON formats for the interactive portal
List<String> formats = List.of("html", "json");
exportService.runExporter(inputDir, outputDir, formats, true,
boolean renderChoicesAsDiamonds = !noDiamonds;
exportService.runExporter(inputDir, outputDir, formats, renderChoicesAsDiamonds,
activeProfiles != null ? activeProfiles : java.util.Collections.emptyList(),
flowsFile, machineFilter);

View File

@@ -83,6 +83,7 @@ public class HtmlExporter implements StateMachineExporter {
public String export(String name, List<Transition> transitions, Set<String> startStates, Set<String> endStates, ExportOptions options) {
AnalysisResult mockResult = AnalysisResult.builder()
.name(name)
.states(click.kamil.springstatemachineexporter.ast.app.TransitionStateUtils.findAllStates(transitions, startStates, endStates))
.transitions(transitions)
.startStates(startStates)
.endStates(endStates)

View File

@@ -48,6 +48,46 @@
overflow-x: hidden;
resize: horizontal;
position: relative;
transition: width 0.3s ease, min-width 0.3s ease, padding 0.3s ease;
}
#sidebar.collapsed {
width: 0 !important;
min-width: 0 !important;
border-right: none;
padding: 0;
overflow: hidden;
resize: none;
}
#toggle-sidebar {
position: absolute;
top: 20px;
left: 20px;
z-index: 50;
background: #fff;
border: 1px solid var(--border);
border-radius: 8px;
padding: 8px;
cursor: pointer;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
#toggle-sidebar:hover {
background: #f8fafc;
border-color: var(--primary);
}
#toggle-sidebar svg {
width: 20px;
height: 20px;
fill: none;
stroke: var(--text-muted);
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
#sidebar::after {
@@ -244,6 +284,9 @@
</div>
<div id="main">
<button id="toggle-sidebar" aria-label="Toggle Sidebar" title="Toggle Sidebar">
<svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h16"></path></svg>
</button>
<div id="svg-container">
{{SVG_CONTENT}}
</div>
@@ -258,9 +301,22 @@
let panZoomInstance;
document.addEventListener('DOMContentLoaded', () => {
const svg = document.querySelector('svg');
const svg = document.querySelector('#svg-container svg');
panZoomInstance = svgPanZoom(svg, { zoomEnabled: true, controlIconsEnabled: true, fit: true, center: true });
document.getElementById('toggle-sidebar').addEventListener('click', () => {
const sidebar = document.getElementById('sidebar');
sidebar.classList.toggle('collapsed');
// Re-center SVG pan-zoom after transition
setTimeout(() => {
if (panZoomInstance) {
panZoomInstance.resize();
panZoomInstance.center();
}
}, 300);
});
setupTabs();
populateSidebar();
populateFlows();
@@ -353,7 +409,7 @@
}
function highlightEvents(steps) {
const svg = document.querySelector('svg');
const svg = document.querySelector('#svg-container svg');
// Dim everything first
svg.querySelectorAll('path, rect, circle, ellipse, text, polygon').forEach(el => {
@@ -427,7 +483,7 @@
}
function attachInteractivity() {
const svg = document.querySelector('svg');
const svg = document.querySelector('#svg-container svg');
const transitions = metadata.transitions || [];
svg.querySelectorAll('a').forEach(link => {
@@ -491,6 +547,21 @@
if (trans && trans.guard) {
html += `<div style="font-size:0.7rem; font-weight:800; color:#94a3b8; text-transform:uppercase; margin-bottom:4px">Guard</div>
<div style="background:#f1f5f9; padding:6px; border-radius:4px; font-family:monospace; font-size:0.8rem; margin-bottom:12px; border:1px solid #e2e8f0;">${trans.guard.expression}</div>`;
if (trans.guard.internalLogic) {
html += `<div style="font-size:0.65rem; font-weight:800; color:#cbd5e1; text-transform:uppercase; margin-bottom:4px">Internal Logic</div>
<pre style="background:#1e293b; color:#f8fafc; padding:8px; border-radius:4px; font-family:'JetBrains Mono', monospace; font-size:0.7rem; overflow-x:auto; margin-bottom:12px;"><code>${trans.guard.internalLogic.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</code></pre>`;
}
}
if (trans && trans.actions && trans.actions.length > 0) {
html += `<div style="font-size:0.7rem; font-weight:800; color:#94a3b8; text-transform:uppercase; margin-bottom:4px">Actions</div>`;
trans.actions.forEach(action => {
html += `<div style="background:#f1f5f9; padding:6px; border-radius:4px; font-family:monospace; font-size:0.8rem; margin-bottom:12px; border:1px solid #e2e8f0;">${action.expression}</div>`;
if (action.internalLogic) {
html += `<div style="font-size:0.65rem; font-weight:800; color:#cbd5e1; text-transform:uppercase; margin-bottom:4px">Internal Logic</div>
<pre style="background:#1e293b; color:#f8fafc; padding:8px; border-radius:4px; font-family:'JetBrains Mono', monospace; font-size:0.7rem; overflow-x:auto; margin-bottom:12px;"><code>${action.internalLogic.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</code></pre>`;
}
});
}
if (chains && chains.length > 0) {