Build and publish Eldorado documentation / build (pull_request) Successful in 39s
66 lines
4.1 KiB
JavaScript
66 lines
4.1 KiB
JavaScript
// Native Workflow v2 owns node geometry, mainPath and routing. The portal adds
|
|
// multiline typography: upstream's three single-line labels are too small for
|
|
// a five-stage reading view. Do not change the pinned vendor or graph semantics.
|
|
const escape = text => text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"');
|
|
const wrap = (text, limit = 21) => {
|
|
const lines = [];
|
|
for (const word of text.split(/\s+/)) {
|
|
if (!lines.length || `${lines.at(-1)} ${word}`.length > limit) lines.push(word);
|
|
else lines[lines.length - 1] += ` ${word}`;
|
|
}
|
|
return lines;
|
|
};
|
|
export function roadmapViewer(html, spec) {
|
|
if (spec.diagram_type !== 'workflow' || spec.schema_version !== 2 || spec.nodes.length !== 5) throw new Error('Roadmap workflow contract drift');
|
|
const boxes = [];
|
|
for (const [index, node] of spec.nodes.entries()) {
|
|
const start = html.indexOf(`<g id="node-${node.id}"`);
|
|
const next = html.indexOf('<text data-node-label=', start);
|
|
const end = html.indexOf('</g>', next) + 4;
|
|
if (start < 0 || next < start || end < next) throw new Error(`Roadmap node drift: ${node.id}`);
|
|
const original = html.slice(start, end);
|
|
const rect = /<rect x="([\d.-]+)" y="([\d.-]+)" width="([\d.]+)" height="([\d.]+)"/.exec(original);
|
|
if (!rect || (original.match(/<text /g) || []).length !== 3) throw new Error(`Roadmap text contract drift: ${node.id}`);
|
|
const [x,y,w,h] = rect.slice(1).map(Number);
|
|
boxes.push({x,y,w,h});
|
|
const text = (value, top, size, cls, attrs = 'data-detail="context"') => {
|
|
const lines = wrap(value, size === 30 ? 18 : 21);
|
|
const maxLines = top === 166 || top === 310 ? 3 : 2;
|
|
if (lines.length > maxLines || lines.some(line => line.length > 21)) throw new Error(`Roadmap copy exceeds card capacity: ${node.id}`);
|
|
return `<text x="${x+24}" y="${y+top}" font-size="${size}" class="${cls}" ${attrs}>${lines.map((line,i) => `<tspan x="${x+24}" dy="${i ? 34 : 0}">${escape(line)}</tspan>`).join('')}</text>`;
|
|
};
|
|
const [action, output] = node.sublabel.split('\nOutput: ');
|
|
if (!action || !output || !node.tag.startsWith('Exit: ')) throw new Error(`Roadmap content contract drift: ${node.id}`);
|
|
const content = text(String(index+1).padStart(2,'0'), 40, 26, 't-muted')
|
|
+ text(node.label, 88, 30, 't-primary', 'data-node-label="" font-weight="700"')
|
|
+ text(action, 166, 26, 't-primary')
|
|
+ text('OUTPUT', 274, 20, 't-muted', 'font-weight="700"')
|
|
+ text(output, 310, 26, 't-primary')
|
|
+ text(node.tag, 416, 24, 't-primary');
|
|
// Preserve native node title/metadata and rectangles; replace sigil/text only.
|
|
// Sigils are nested SVG groups. Anchor on the two native node rectangles.
|
|
const rectangles = [...original.matchAll(/<rect\b[^>]*\/>/g)];
|
|
if (rectangles.length !== 2) throw new Error('Roadmap shape contract drift');
|
|
const prefixEnd = rectangles[1].index + rectangles[1][0].length;
|
|
const replacement = original.slice(0,prefixEnd).replaceAll('rx="6"', 'rx="0"') + content + '</g>';
|
|
html = html.slice(0,start) + replacement + html.slice(end);
|
|
}
|
|
const left = Math.min(...boxes.map(b => b.x)) - 12;
|
|
const top = Math.min(...boxes.map(b => b.y)) - 12;
|
|
const width = Math.max(...boxes.map(b => b.x+b.w)) - left + 12;
|
|
const height = Math.max(...boxes.map(b => b.y+b.h)) - top + 12;
|
|
// One editorial lane; its title/frame would repeat the page's framing.
|
|
html = html.replace(/<rect\b[^>]*data-composition-frame-kind="lane"[^>]*\/>\s*<text\b[^>]*>[\s\S]*?<\/text>/g, '');
|
|
const roots = [...html.matchAll(/<svg\b[^>]*role="img"[^>]*>/g)];
|
|
if (roots.length !== 1) throw new Error('Roadmap SVG contract drift');
|
|
const root = roots[0][0].replace(/viewBox="[^"]*"/, `viewBox="${left} ${top} ${width} ${height}"`);
|
|
const styles = `<style id="eldorado-roadmap-typography">
|
|
[data-node-id] text {font-family: Arial, sans-serif;}
|
|
svg[data-preset="classic"] [data-node-id] > rect.c-backend,
|
|
svg[data-preset="editorial"] [data-node-id] > rect.c-backend {fill:var(--panel); stroke:var(--border);}
|
|
svg[data-preset="classic"] .c-grid {opacity:0;}
|
|
</style>`;
|
|
html = html.replace(roots[0][0], root + styles);
|
|
return html;
|
|
}
|