@webpieces/nx-webpieces-rules 0.4.884 → 0.4.886
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -8
- package/package.json +7 -7
- package/src/executors/visualize-runtime/executor.js +13 -1
- package/src/executors/visualize-runtime/executor.js.map +1 -1
- package/src/lib/dot-syntax.js +26 -0
- package/src/lib/dot-syntax.js.map +1 -1
- package/src/lib/graph-color-modes.d.ts +2 -1
- package/src/lib/graph-color-modes.js +5 -2
- package/src/lib/graph-color-modes.js.map +1 -1
- package/src/lib/graph-impact.js +1 -0
- package/src/lib/graph-impact.js.map +1 -1
- package/src/lib/graph-legend.d.ts +1 -1
- package/src/lib/graph-legend.js.map +1 -1
- package/src/lib/graph-mode-state.client.d.ts +32 -0
- package/src/lib/graph-mode-state.client.js +141 -0
- package/src/lib/graph-mode-state.client.js.map +1 -0
- package/src/lib/graph-page-shell.d.ts +5 -1
- package/src/lib/graph-page-shell.js +31 -12
- package/src/lib/graph-page-shell.js.map +1 -1
- package/src/lib/graph-render-model.d.ts +7 -5
- package/src/lib/graph-render-model.js +11 -8
- package/src/lib/graph-render-model.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +11 -46
- package/src/lib/graph-visualizer.client.js +103 -160
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/graph-visualizer.js +2 -1
- package/src/lib/graph-visualizer.js.map +1 -1
- package/src/lib/runtime-details.d.ts +4 -1
- package/src/lib/runtime-details.js +102 -55
- package/src/lib/runtime-details.js.map +1 -1
- package/src/lib/runtime-html-page.d.ts +13 -40
- package/src/lib/runtime-html-page.js +96 -129
- package/src/lib/runtime-html-page.js.map +1 -1
- package/src/lib/runtime-viewer-adapter.d.ts +16 -0
- package/src/lib/runtime-viewer-adapter.js +58 -0
- package/src/lib/runtime-viewer-adapter.js.map +1 -0
- package/src/lib/runtime-visualizer.client.d.ts +9 -19
- package/src/lib/runtime-visualizer.client.js +75 -75
- package/src/lib/runtime-visualizer.client.js.map +1 -1
- package/src/lib/runtime-visualizer.d.ts +2 -16
- package/src/lib/runtime-visualizer.js +22 -44
- package/src/lib/runtime-visualizer.js.map +1 -1
- package/src/lib/runtime-viz-options.d.ts +19 -0
- package/src/lib/runtime-viz-options.js +22 -0
- package/src/lib/runtime-viz-options.js.map +1 -0
|
@@ -61,7 +61,9 @@ class Bounds {
|
|
|
61
61
|
class QueueCylinders {
|
|
62
62
|
static SVG_NS = 'http://www.w3.org/2000/svg';
|
|
63
63
|
applyTo(svg) {
|
|
64
|
-
svg.querySelectorAll('g.wp_queue').forEach((node) => {
|
|
64
|
+
svg.querySelectorAll('g.wp_queue').forEach((node) => {
|
|
65
|
+
this.reshape(node);
|
|
66
|
+
});
|
|
65
67
|
}
|
|
66
68
|
/**
|
|
67
69
|
* Replace one node's shape elements with a horizontal cylinder sized to its own bounding box.
|
|
@@ -82,18 +84,26 @@ class QueueCylinders {
|
|
|
82
84
|
const stroke = first.getAttribute('stroke') ?? 'black';
|
|
83
85
|
// Measured BEFORE the shapes go: the record separator is where the label text starts.
|
|
84
86
|
const separator = this.separatorX(shapes);
|
|
85
|
-
shapes.forEach((shape) => {
|
|
87
|
+
shapes.forEach((shape) => {
|
|
88
|
+
shape.remove();
|
|
89
|
+
});
|
|
86
90
|
const ry = box.halfHeight();
|
|
87
91
|
const rx = this.capRadius(box, ry, separator);
|
|
88
|
-
const body =
|
|
89
|
-
' L' + (box.x1 - rx) + ',' + box.y0 +
|
|
90
|
-
' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x1 - rx) + ',' + box.y1 +
|
|
91
|
-
' L' + (box.x0 + rx) + ',' + box.y1 +
|
|
92
|
-
' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x0 + rx) + ',' + box.y0 + ' Z';
|
|
92
|
+
const body = this.bodyPath(box, rx, ry);
|
|
93
93
|
// Only the NEAR end cap is drawn: that single arc is what reads as "tube" rather than
|
|
94
94
|
// "stadium", and a real cylinder hides the far one behind the body.
|
|
95
|
-
const cap = 'M' +
|
|
96
|
-
|
|
95
|
+
const cap = 'M' +
|
|
96
|
+
(box.x0 + rx) +
|
|
97
|
+
',' +
|
|
98
|
+
box.y0 +
|
|
99
|
+
' A' +
|
|
100
|
+
rx +
|
|
101
|
+
',' +
|
|
102
|
+
ry +
|
|
103
|
+
' 0 0 1 ' +
|
|
104
|
+
(box.x0 + rx) +
|
|
105
|
+
',' +
|
|
106
|
+
box.y1;
|
|
97
107
|
const anchor = node.firstChild;
|
|
98
108
|
if (anchor === null || anchor.nextSibling === null)
|
|
99
109
|
return;
|
|
@@ -104,6 +114,37 @@ class QueueCylinders {
|
|
|
104
114
|
// cap lands. Shifting it again double-counts that offset and pushes the longest line out
|
|
105
115
|
// through the far end of the cylinder.
|
|
106
116
|
}
|
|
117
|
+
bodyPath(box, rx, ry) {
|
|
118
|
+
return ('M' +
|
|
119
|
+
(box.x0 + rx) +
|
|
120
|
+
',' +
|
|
121
|
+
box.y0 +
|
|
122
|
+
' L' +
|
|
123
|
+
(box.x1 - rx) +
|
|
124
|
+
',' +
|
|
125
|
+
box.y0 +
|
|
126
|
+
' A' +
|
|
127
|
+
rx +
|
|
128
|
+
',' +
|
|
129
|
+
ry +
|
|
130
|
+
' 0 0 1 ' +
|
|
131
|
+
(box.x1 - rx) +
|
|
132
|
+
',' +
|
|
133
|
+
box.y1 +
|
|
134
|
+
' L' +
|
|
135
|
+
(box.x0 + rx) +
|
|
136
|
+
',' +
|
|
137
|
+
box.y1 +
|
|
138
|
+
' A' +
|
|
139
|
+
rx +
|
|
140
|
+
',' +
|
|
141
|
+
ry +
|
|
142
|
+
' 0 0 1 ' +
|
|
143
|
+
(box.x0 + rx) +
|
|
144
|
+
',' +
|
|
145
|
+
box.y0 +
|
|
146
|
+
' Z');
|
|
147
|
+
}
|
|
107
148
|
/**
|
|
108
149
|
* A box listing several queues is TALL, and a cap scaled off half-height alone would then reach
|
|
109
150
|
* past the empty leading field and sit on top of the first characters of the label. Graphviz sizes
|
|
@@ -160,77 +201,36 @@ class QueueCylinders {
|
|
|
160
201
|
return el;
|
|
161
202
|
}
|
|
162
203
|
}
|
|
163
|
-
/**
|
|
164
|
-
class
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
this.
|
|
170
|
-
this.lock = lock;
|
|
171
|
-
this.page = page;
|
|
172
|
-
}
|
|
173
|
-
wire() {
|
|
174
|
-
WpNodeMenu.wire(this.svg, (name, node) => {
|
|
175
|
-
const label = this.lock.isLocked(name) ? 'Unlock' : 'Lock';
|
|
176
|
-
return [new WpNodeMenuItem(label, () => { this.lock.toggle(name, node); }), this.page.filterItem(name)];
|
|
177
|
-
});
|
|
204
|
+
/** Shape adapter for the shared viewer controller; no separate runtime control state. */
|
|
205
|
+
class RuntimeImplementsPayload {
|
|
206
|
+
name;
|
|
207
|
+
node;
|
|
208
|
+
constructor(name, node) {
|
|
209
|
+
this.name = name;
|
|
210
|
+
this.node = node;
|
|
178
211
|
}
|
|
179
212
|
}
|
|
180
|
-
class
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (this.products.has(product))
|
|
190
|
-
this.products.delete(product);
|
|
191
|
-
else
|
|
192
|
-
this.products.add(product);
|
|
193
|
-
chip.setAttribute('aria-pressed', String(this.products.has(product)));
|
|
194
|
-
this.redraw();
|
|
195
|
-
});
|
|
213
|
+
class RuntimeViewerBindingImpl {
|
|
214
|
+
prepare(svg) {
|
|
215
|
+
new QueueCylinders().applyTo(svg);
|
|
216
|
+
// Keep identity titles readable by shared controllers without native SVG tooltips.
|
|
217
|
+
svg.querySelectorAll('g.node > title, g.edge > title').forEach((title) => {
|
|
218
|
+
title.parentElement?.setAttribute('aria-label', title.textContent ?? '');
|
|
219
|
+
const identity = document.createElement('title');
|
|
220
|
+
identity.textContent = title.textContent;
|
|
221
|
+
title.replaceWith(identity);
|
|
196
222
|
});
|
|
197
223
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (node.products.some((product) => this.products.has(product)))
|
|
207
|
-
kept.add(node.id);
|
|
208
|
-
}
|
|
209
|
-
return kept;
|
|
210
|
-
}
|
|
211
|
-
usesFullDot() {
|
|
212
|
-
return this.products.size === 0;
|
|
213
|
-
}
|
|
214
|
-
captureBinding(svg) {
|
|
215
|
-
const previous = this.lock;
|
|
216
|
-
return () => {
|
|
217
|
-
this.lock = previous;
|
|
218
|
-
if (svg !== null)
|
|
219
|
-
this.lock?.rebind(svg);
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
prepareSvg(svg) { new QueueCylinders().applyTo(svg); }
|
|
223
|
-
wireSvg(svg) {
|
|
224
|
-
if (this.lock === null)
|
|
225
|
-
this.lock = new WpNodeLock(svg);
|
|
226
|
-
else
|
|
227
|
-
this.lock.rebind(svg);
|
|
228
|
-
new RuntimeNodeMenu(svg, this.lock, this).wire();
|
|
229
|
-
svg.querySelectorAll('g.node').forEach(node => {
|
|
230
|
-
if (node.querySelector('title')?.textContent === this.anchor)
|
|
231
|
-
node.classList.add('wp-filter-anchor');
|
|
224
|
+
implementsItem(name, node) {
|
|
225
|
+
if (!node.querySelector('text') ||
|
|
226
|
+
!Array.from(node.querySelectorAll('text')).some((text) => text.textContent?.startsWith('Implements (')))
|
|
227
|
+
return null;
|
|
228
|
+
return new WpNodeMenuItem('Implements', () => {
|
|
229
|
+
document.dispatchEvent(new CustomEvent('wp-runtime-implements', {
|
|
230
|
+
detail: new RuntimeImplementsPayload(name, node),
|
|
231
|
+
}));
|
|
232
232
|
});
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
|
-
new
|
|
235
|
+
window.__WP_RUNTIME__ = new RuntimeViewerBindingImpl();
|
|
236
236
|
//# sourceMappingURL=runtime-visualizer.client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-visualizer.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-visualizer.client.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Browser-side script for tmp/webpieces/runtime-architecture.html.\n *\n * runtime-visualizer.ts reads this file's COMPILED output with readFileSync, substitutes the DOT\n * placeholder (see viz-client-globals.d.ts) with the JSON-encoded DOT, and inlines the result into a <script> tag. It runs in a\n * browser, never in node — but it is ordinary TypeScript, compiled in place by tsc like the package's\n * bin entry points, and linted like every other file here. (It used to be a committed .js asset\n * exempted from `no-js-files`; see graph-visualizer.client.ts for why that exemption is gone.)\n *\n * Three jobs:\n * 1. render the DOT with @viz-js/viz v3 (instance() resolves the WASM renderer, and\n * renderSVGElement is then SYNCHRONOUS — v2's returned a promise);\n * 2. upgrade every queue node into a TRUE horizontal cylinder;\n * 3. make every node clickable, opening the SHARED floating menu (graph-node-menu.ts — the same one\n * architecture/dependencies.html and every design.html use) with independent Lock/Unlock and chain filtering.\n *\n * Why (2) is post-processing rather than a shape:\n *\n * Graphviz has exactly one `cylinder` and it is upright-only. `orientation=` is documented as rotating\n * POLYGON shapes, and `cylinder` is drawn with beziers, so it silently ignores the attribute — graphviz\n * issue #2244, open since 2022 and still reproducible on Graphviz 15, the version this page's renderer\n * carries. All 54 native shapes were compared at the real label size; none is a horizontal cylinder. So\n * the DOT emits `Mrecord` (which renders sensibly on its own for anyone running `dot` over the\n * committed .dot file) and this script redraws it in the browser, where the geometry is computed from\n * each node's actual bounding box and therefore fits any label width — which a fixed image asset never\n * can.\n */\n\n// The DOT placeholder and the `Viz` global are declared once in viz-client-globals.d.ts — both\n// scripts share a global scope, so they cannot each declare them. Do NOT spell the placeholder token\n// in a comment: the inliner is a blind split/join, so every literal occurrence gets the whole DOT.\n\n/** Cap width as a fraction of the node's half-height. Below ~0.6 it reads as a rounded box. */\nconst CAP_RATIO = 0.75;\n/** A node narrower than this is not a queue box worth reshaping. */\nconst MIN_WIDTH = 40;\n\n/** The union bounding box of a node's shape elements, in user space. Data-only → a class. */\nclass Bounds {\n constructor(\n readonly x0: number,\n readonly x1: number,\n readonly y0: number,\n readonly y1: number,\n ) {}\n\n width(): number {\n return this.x1 - this.x0;\n }\n\n halfHeight(): number {\n return (this.y1 - this.y0) / 2;\n }\n}\n\n/**\n * Redraws the nodes the DOT marked `wp_queue` as cylinders lying on their side.\n *\n * Selected by CLASS, not by id prefix: a queue-kind external system shares the `system__` id space with\n * databases, and a database is an UPRIGHT cylinder that must be left alone.\n */\nclass QueueCylinders {\n private static readonly SVG_NS = 'http://www.w3.org/2000/svg';\n\n applyTo(svg: SVGSVGElement): void {\n svg.querySelectorAll('g.wp_queue').forEach((node: Element): void => { this.reshape(node); });\n }\n\n /**\n * Replace one node's shape elements with a horizontal cylinder sized to its own bounding box.\n *\n * The bbox is measured from the shape elements BEFORE they are removed, never from getBBox() on the\n * whole group — the group includes the text, which is inset, so using it would shrink the cylinder\n * inside the very label it is meant to contain.\n */\n private reshape(node: Element): void {\n const shapes = node.querySelectorAll('polygon, path, polyline');\n if (shapes.length === 0) return;\n const box = this.boundsOf(shapes);\n if (box === null || box.width() < MIN_WIDTH) return;\n\n const first = shapes[0];\n const fill = first.getAttribute('fill') ?? 'none';\n const stroke = first.getAttribute('stroke') ?? 'black';\n // Measured BEFORE the shapes go: the record separator is where the label text starts.\n const separator = this.separatorX(shapes);\n shapes.forEach((shape: Element): void => { shape.remove(); });\n\n const ry = box.halfHeight();\n const rx = this.capRadius(box, ry, separator);\n const body =\n 'M' + (box.x0 + rx) + ',' + box.y0 +\n ' L' + (box.x1 - rx) + ',' + box.y0 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x1 - rx) + ',' + box.y1 +\n ' L' + (box.x0 + rx) + ',' + box.y1 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x0 + rx) + ',' + box.y0 + ' Z';\n // Only the NEAR end cap is drawn: that single arc is what reads as \"tube\" rather than\n // \"stadium\", and a real cylinder hides the far one behind the body.\n const cap =\n 'M' + (box.x0 + rx) + ',' + box.y0 +\n ' A' + rx + ',' + ry + ' 0 0 1 ' + (box.x0 + rx) + ',' + box.y1;\n\n const anchor = node.firstChild;\n if (anchor === null || anchor.nextSibling === null) return;\n node.insertBefore(this.pathEl(fill, stroke, body), anchor.nextSibling);\n node.insertBefore(this.pathEl('none', stroke, cap), anchor.nextSibling.nextSibling);\n\n // The label is deliberately NOT nudged. QUEUE_LABEL_PREFIX already gives the node an empty\n // leading record field, so Graphviz has centred the text in the space to the RIGHT of where the\n // cap lands. Shifting it again double-counts that offset and pushes the longest line out\n // through the far end of the cylinder.\n }\n\n /**\n * A box listing several queues is TALL, and a cap scaled off half-height alone would then reach\n * past the empty leading field and sit on top of the first characters of the label. Graphviz sizes\n * that field from the font, not from the label's height, so the separator is the only honest bound\n * on how far right the cap may go.\n */\n private capRadius(box: Bounds, ry: number, separator: number | null): number {\n const fromHeight = Math.max(6, ry * CAP_RATIO);\n if (separator === null) return fromHeight;\n return Math.max(6, Math.min(fromHeight, separator - box.x0));\n }\n\n /**\n * The x of the record's field separator — the VERTICAL polyline Graphviz draws between the empty\n * leading field and the label — or null if this node has none.\n */\n private separatorX(shapes: NodeListOf<Element>): number | null {\n for (const shape of Array.from(shapes)) {\n if (shape.tagName !== 'polyline') continue;\n const nums = (shape.getAttribute('points') ?? '').match(/-?[\\d.]+/g);\n if (nums === null || nums.length < 4) continue;\n const x = parseFloat(nums[0]);\n if (Math.abs(x - parseFloat(nums[2])) < 0.5) return x;\n }\n return null;\n }\n\n /** The union bounding box of some SVG shape elements, from their raw geometry attributes. */\n private boundsOf(shapes: NodeListOf<Element>): Bounds | null {\n const xs: number[] = [];\n const ys: number[] = [];\n for (const shape of Array.from(shapes)) {\n const pts = shape.getAttribute('points');\n const source = pts ?? shape.getAttribute('d') ?? '';\n const nums = source.match(/-?[\\d.]+/g);\n if (nums === null) continue;\n for (let n = 0; n + 1 < nums.length; n += 2) {\n xs.push(parseFloat(nums[n]));\n ys.push(parseFloat(nums[n + 1]));\n }\n }\n if (xs.length === 0) return null;\n return new Bounds(Math.min(...xs), Math.max(...xs), Math.min(...ys), Math.max(...ys));\n }\n\n private pathEl(fill: string, stroke: string, d: string): SVGPathElement {\n const el = document.createElementNS(QueueCylinders.SVG_NS, 'path');\n el.setAttribute('fill', fill);\n el.setAttribute('stroke', stroke);\n el.setAttribute('d', d);\n return el;\n }\n}\n\n/** Runtime keeps the single-box Lock while Filter follows the full directed chain. */\nclass RuntimeNodeMenu {\n constructor(private readonly svg: SVGSVGElement, private readonly lock: WpNodeLock, private readonly page: RuntimePage) {}\n\n wire(): void {\n WpNodeMenu.wire(this.svg, (name: string, node: SVGGElement): WpNodeMenuItem[] => {\n const label = this.lock.isLocked(name) ? 'Unlock' : 'Lock';\n return [new WpNodeMenuItem(label, (): void => { this.lock.toggle(name, node); }), this.page.filterItem(name)];\n });\n }\n}\n\nclass RuntimePage extends WpFilterPage {\n private lock: WpNodeLock | null = null;\n /** The pressed product chips (#1179); empty keeps every box. */\n private readonly products = new Set<string>();\n\n /** Each product chip toggles itself and redraws; the narrowing happens in narrow(). */\n protected override wireControls(): void {\n document.querySelectorAll<HTMLButtonElement>('[data-wp-product]').forEach((chip: HTMLButtonElement): void => {\n chip.addEventListener('click', (): void => {\n const product = chip.dataset['wpProduct'] ?? '';\n if (this.products.has(product)) this.products.delete(product);\n else this.products.add(product);\n chip.setAttribute('aria-pressed', String(this.products.has(product)));\n this.redraw();\n });\n });\n }\n\n /** Pressed chips keep the UNION of their products' boxes, intersected with Filter Unconnected. */\n protected override narrow(retained: Set<string>): Set<string> {\n if (this.products.size === 0) return retained;\n const kept = new Set<string>();\n for (const node of this.model.nodes) {\n if (!retained.has(node.id)) continue;\n if (node.products.some((product: string): boolean => this.products.has(product))) kept.add(node.id);\n }\n return kept;\n }\n\n protected override usesFullDot(): boolean {\n return this.products.size === 0;\n }\n\n protected override captureBinding(svg: SVGSVGElement | null): () => void {\n const previous = this.lock;\n return (): void => {\n this.lock = previous;\n if (svg !== null) this.lock?.rebind(svg);\n };\n }\n\n protected override prepareSvg(svg: SVGSVGElement): void { new QueueCylinders().applyTo(svg); }\n\n protected wireSvg(svg: SVGSVGElement): void {\n if (this.lock === null) this.lock = new WpNodeLock(svg);\n else this.lock.rebind(svg);\n new RuntimeNodeMenu(svg, this.lock, this).wire();\n svg.querySelectorAll<SVGGElement>('g.node').forEach(node => {\n if (node.querySelector('title')?.textContent === this.anchor) node.classList.add('wp-filter-anchor');\n });\n }\n}\n\nnew RuntimePage(__RENDER_MODEL__).render();\n"]}
|
|
1
|
+
{"version":3,"file":"runtime-visualizer.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/runtime-visualizer.client.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Browser-side script for tmp/webpieces/runtime-architecture.html.\n *\n * runtime-visualizer.ts reads this file's COMPILED output with readFileSync, substitutes the DOT\n * placeholder (see viz-client-globals.d.ts) with the JSON-encoded DOT, and inlines the result into a <script> tag. It runs in a\n * browser, never in node — but it is ordinary TypeScript, compiled in place by tsc like the package's\n * bin entry points, and linted like every other file here. (It used to be a committed .js asset\n * exempted from `no-js-files`; see graph-visualizer.client.ts for why that exemption is gone.)\n *\n * Three jobs:\n * 1. render the DOT with @viz-js/viz v3 (instance() resolves the WASM renderer, and\n * renderSVGElement is then SYNCHRONOUS — v2's returned a promise);\n * 2. upgrade every queue node into a TRUE horizontal cylinder;\n * 3. make every node clickable, opening the SHARED floating menu (graph-node-menu.ts — the same one\n * architecture/dependencies.html and every design.html use) with independent Lock/Unlock and chain filtering.\n *\n * Why (2) is post-processing rather than a shape:\n *\n * Graphviz has exactly one `cylinder` and it is upright-only. `orientation=` is documented as rotating\n * POLYGON shapes, and `cylinder` is drawn with beziers, so it silently ignores the attribute — graphviz\n * issue #2244, open since 2022 and still reproducible on Graphviz 15, the version this page's renderer\n * carries. All 54 native shapes were compared at the real label size; none is a horizontal cylinder. So\n * the DOT emits `Mrecord` (which renders sensibly on its own for anyone running `dot` over the\n * committed .dot file) and this script redraws it in the browser, where the geometry is computed from\n * each node's actual bounding box and therefore fits any label width — which a fixed image asset never\n * can.\n */\n\n// The DOT placeholder and the `Viz` global are declared once in viz-client-globals.d.ts — both\n// scripts share a global scope, so they cannot each declare them. Do NOT spell the placeholder token\n// in a comment: the inliner is a blind split/join, so every literal occurrence gets the whole DOT.\n\n/** Cap width as a fraction of the node's half-height. Below ~0.6 it reads as a rounded box. */\nconst CAP_RATIO = 0.75;\n/** A node narrower than this is not a queue box worth reshaping. */\nconst MIN_WIDTH = 40;\n\n/** The union bounding box of a node's shape elements, in user space. Data-only → a class. */\nclass Bounds {\n constructor(\n readonly x0: number,\n readonly x1: number,\n readonly y0: number,\n readonly y1: number,\n ) {}\n\n width(): number {\n return this.x1 - this.x0;\n }\n\n halfHeight(): number {\n return (this.y1 - this.y0) / 2;\n }\n}\n\n/**\n * Redraws the nodes the DOT marked `wp_queue` as cylinders lying on their side.\n *\n * Selected by CLASS, not by id prefix: a queue-kind external system shares the `system__` id space with\n * databases, and a database is an UPRIGHT cylinder that must be left alone.\n */\nclass QueueCylinders {\n private static readonly SVG_NS = 'http://www.w3.org/2000/svg';\n\n applyTo(svg: SVGSVGElement): void {\n svg.querySelectorAll('g.wp_queue').forEach((node: Element): void => {\n this.reshape(node);\n });\n }\n\n /**\n * Replace one node's shape elements with a horizontal cylinder sized to its own bounding box.\n *\n * The bbox is measured from the shape elements BEFORE they are removed, never from getBBox() on the\n * whole group — the group includes the text, which is inset, so using it would shrink the cylinder\n * inside the very label it is meant to contain.\n */\n private reshape(node: Element): void {\n const shapes = node.querySelectorAll('polygon, path, polyline');\n if (shapes.length === 0) return;\n const box = this.boundsOf(shapes);\n if (box === null || box.width() < MIN_WIDTH) return;\n\n const first = shapes[0];\n const fill = first.getAttribute('fill') ?? 'none';\n const stroke = first.getAttribute('stroke') ?? 'black';\n // Measured BEFORE the shapes go: the record separator is where the label text starts.\n const separator = this.separatorX(shapes);\n shapes.forEach((shape: Element): void => {\n shape.remove();\n });\n\n const ry = box.halfHeight();\n const rx = this.capRadius(box, ry, separator);\n const body = this.bodyPath(box, rx, ry);\n // Only the NEAR end cap is drawn: that single arc is what reads as \"tube\" rather than\n // \"stadium\", and a real cylinder hides the far one behind the body.\n const cap =\n 'M' +\n (box.x0 + rx) +\n ',' +\n box.y0 +\n ' A' +\n rx +\n ',' +\n ry +\n ' 0 0 1 ' +\n (box.x0 + rx) +\n ',' +\n box.y1;\n\n const anchor = node.firstChild;\n if (anchor === null || anchor.nextSibling === null) return;\n node.insertBefore(this.pathEl(fill, stroke, body), anchor.nextSibling);\n node.insertBefore(this.pathEl('none', stroke, cap), anchor.nextSibling.nextSibling);\n\n // The label is deliberately NOT nudged. QUEUE_LABEL_PREFIX already gives the node an empty\n // leading record field, so Graphviz has centred the text in the space to the RIGHT of where the\n // cap lands. Shifting it again double-counts that offset and pushes the longest line out\n // through the far end of the cylinder.\n }\n\n private bodyPath(box: Bounds, rx: number, ry: number): string {\n return (\n 'M' +\n (box.x0 + rx) +\n ',' +\n box.y0 +\n ' L' +\n (box.x1 - rx) +\n ',' +\n box.y0 +\n ' A' +\n rx +\n ',' +\n ry +\n ' 0 0 1 ' +\n (box.x1 - rx) +\n ',' +\n box.y1 +\n ' L' +\n (box.x0 + rx) +\n ',' +\n box.y1 +\n ' A' +\n rx +\n ',' +\n ry +\n ' 0 0 1 ' +\n (box.x0 + rx) +\n ',' +\n box.y0 +\n ' Z'\n );\n }\n\n /**\n * A box listing several queues is TALL, and a cap scaled off half-height alone would then reach\n * past the empty leading field and sit on top of the first characters of the label. Graphviz sizes\n * that field from the font, not from the label's height, so the separator is the only honest bound\n * on how far right the cap may go.\n */\n private capRadius(box: Bounds, ry: number, separator: number | null): number {\n const fromHeight = Math.max(6, ry * CAP_RATIO);\n if (separator === null) return fromHeight;\n return Math.max(6, Math.min(fromHeight, separator - box.x0));\n }\n\n /**\n * The x of the record's field separator — the VERTICAL polyline Graphviz draws between the empty\n * leading field and the label — or null if this node has none.\n */\n private separatorX(shapes: NodeListOf<Element>): number | null {\n for (const shape of Array.from(shapes)) {\n if (shape.tagName !== 'polyline') continue;\n const nums = (shape.getAttribute('points') ?? '').match(/-?[\\d.]+/g);\n if (nums === null || nums.length < 4) continue;\n const x = parseFloat(nums[0]);\n if (Math.abs(x - parseFloat(nums[2])) < 0.5) return x;\n }\n return null;\n }\n\n /** The union bounding box of some SVG shape elements, from their raw geometry attributes. */\n private boundsOf(shapes: NodeListOf<Element>): Bounds | null {\n const xs: number[] = [];\n const ys: number[] = [];\n for (const shape of Array.from(shapes)) {\n const pts = shape.getAttribute('points');\n const source = pts ?? shape.getAttribute('d') ?? '';\n const nums = source.match(/-?[\\d.]+/g);\n if (nums === null) continue;\n for (let n = 0; n + 1 < nums.length; n += 2) {\n xs.push(parseFloat(nums[n]));\n ys.push(parseFloat(nums[n + 1]));\n }\n }\n if (xs.length === 0) return null;\n return new Bounds(Math.min(...xs), Math.max(...xs), Math.min(...ys), Math.max(...ys));\n }\n\n private pathEl(fill: string, stroke: string, d: string): SVGPathElement {\n const el = document.createElementNS(QueueCylinders.SVG_NS, 'path');\n el.setAttribute('fill', fill);\n el.setAttribute('stroke', stroke);\n el.setAttribute('d', d);\n return el;\n }\n}\n\n/** Shape adapter for the shared viewer controller; no separate runtime control state. */\nclass RuntimeImplementsPayload {\n constructor(\n readonly name: string,\n readonly node: SVGGElement,\n ) {}\n}\n\nclass RuntimeViewerBindingImpl implements RuntimeViewerBinding {\n prepare(svg: SVGSVGElement): void {\n new QueueCylinders().applyTo(svg);\n // Keep identity titles readable by shared controllers without native SVG tooltips.\n svg.querySelectorAll('g.node > title, g.edge > title').forEach((title) => {\n title.parentElement?.setAttribute('aria-label', title.textContent ?? '');\n const identity = document.createElement('title');\n identity.textContent = title.textContent;\n title.replaceWith(identity);\n });\n }\n implementsItem(name: string, node: SVGGElement): WpNodeMenuItem | null {\n if (\n !node.querySelector('text') ||\n !Array.from(node.querySelectorAll('text')).some((text) =>\n text.textContent?.startsWith('Implements ('),\n )\n )\n return null;\n return new WpNodeMenuItem('Implements', (): void => {\n document.dispatchEvent(\n new CustomEvent('wp-runtime-implements', {\n detail: new RuntimeImplementsPayload(name, node),\n }),\n );\n });\n }\n}\nwindow.__WP_RUNTIME__ = new RuntimeViewerBindingImpl();\n"]}
|
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
import type { RuntimeGraph } from './runtime-graph';
|
|
2
2
|
import { GraphRenderModel } from './graph-render-model';
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
/**
|
|
6
|
-
* Draw the dashed terminal nodes for contracts nothing in-repo implements. On by default;
|
|
7
|
-
* a repo whose external surface is noisy can turn them off in the declared Nx owner file's
|
|
8
|
-
* direct policy-ID map (runtime-architecture.showExternalNodes).
|
|
9
|
-
*/
|
|
10
|
-
readonly showExternalNodes: boolean;
|
|
11
|
-
constructor(
|
|
12
|
-
/**
|
|
13
|
-
* Draw the dashed terminal nodes for contracts nothing in-repo implements. On by default;
|
|
14
|
-
* a repo whose external surface is noisy can turn them off in the declared Nx owner file's
|
|
15
|
-
* direct policy-ID map (runtime-architecture.showExternalNodes).
|
|
16
|
-
*/
|
|
17
|
-
showExternalNodes?: boolean);
|
|
18
|
-
}
|
|
3
|
+
import { RuntimeVizOptions } from './runtime-viz-options';
|
|
4
|
+
export { RuntimeVizOptions } from './runtime-viz-options';
|
|
19
5
|
/** Build the Graphviz DOT for the runtime service graph. */
|
|
20
6
|
export declare function generateRuntimeDot(graph: RuntimeGraph, title?: string, options?: RuntimeVizOptions): string;
|
|
21
7
|
/** Capture synthetic drawable nodes and edges at their original emission sites. */
|
|
@@ -5,7 +5,7 @@ exports.generateRuntimeDot = generateRuntimeDot;
|
|
|
5
5
|
exports.generateRuntimeRenderModel = generateRuntimeRenderModel;
|
|
6
6
|
exports.writeRuntimeVisualization = writeRuntimeVisualization;
|
|
7
7
|
const tslib_1 = require("tslib");
|
|
8
|
-
const
|
|
8
|
+
const graph_level_bands_1 = require("./graph-level-bands");
|
|
9
9
|
/**
|
|
10
10
|
* Runtime Visualizer
|
|
11
11
|
*
|
|
@@ -45,8 +45,7 @@ const runtime_details_1 = require("./runtime-details");
|
|
|
45
45
|
*
|
|
46
46
|
* EVERY node on the rendered page is clickable and opens the SHARED floating menu
|
|
47
47
|
* (graph-node-menu.ts — one implementation, also used by architecture/dependencies.html
|
|
48
|
-
* and every project's design.html). Lock/Unlock
|
|
49
|
-
* other node and every edge and lights the locked box alone. There is no "View Design"
|
|
48
|
+
* and every project's design.html). Lock/Unlock pins separate complete incoming/outgoing chains, with hover adding a temporary chain. There is no "View Design"
|
|
50
49
|
* item here — a node is a running service, queue, datastore or third-party system, not
|
|
51
50
|
* an nx project, so no design.html exists to point at and the item is absent rather than
|
|
52
51
|
* dead. Filter Unconnected follows separate incoming/outgoing chains and compacts the rendered
|
|
@@ -56,26 +55,16 @@ const fs = tslib_1.__importStar(require("fs"));
|
|
|
56
55
|
const path = tslib_1.__importStar(require("path"));
|
|
57
56
|
const dot_syntax_1 = require("./dot-syntax");
|
|
58
57
|
const graph_render_model_1 = require("./graph-render-model");
|
|
58
|
+
const runtime_viewer_adapter_1 = require("./runtime-viewer-adapter");
|
|
59
59
|
const runtime_html_page_1 = require("./runtime-html-page");
|
|
60
60
|
const runtime_viz_theme_1 = require("./runtime-viz-theme");
|
|
61
61
|
/** Apis per line inside a node label — beyond this the box grows wider than it is readable. */
|
|
62
62
|
const APIS_PER_LABEL_LINE = 3;
|
|
63
63
|
/** Separator for the (service, external-library) grouping key; illegal in both project names. */
|
|
64
64
|
const PAIR_SEP = '|';
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
constructor(
|
|
69
|
-
/**
|
|
70
|
-
* Draw the dashed terminal nodes for contracts nothing in-repo implements. On by default;
|
|
71
|
-
* a repo whose external surface is noisy can turn them off in the declared Nx owner file's
|
|
72
|
-
* direct policy-ID map (runtime-architecture.showExternalNodes).
|
|
73
|
-
*/
|
|
74
|
-
showExternalNodes = true) {
|
|
75
|
-
this.showExternalNodes = showExternalNodes;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
exports.RuntimeVizOptions = RuntimeVizOptions;
|
|
65
|
+
const runtime_viz_options_1 = require("./runtime-viz-options");
|
|
66
|
+
var runtime_viz_options_2 = require("./runtime-viz-options");
|
|
67
|
+
Object.defineProperty(exports, "RuntimeVizOptions", { enumerable: true, get: function () { return runtime_viz_options_2.RuntimeVizOptions; } });
|
|
79
68
|
/**
|
|
80
69
|
* The DISPLAY name of a project/service: scope stripped. LABELS ONLY.
|
|
81
70
|
*
|
|
@@ -101,20 +90,6 @@ function labelList(entries) {
|
|
|
101
90
|
}
|
|
102
91
|
return lines.join('\\n');
|
|
103
92
|
}
|
|
104
|
-
/** Compact affordances carry full details in the HTML, including hidden external uses. */
|
|
105
|
-
// webpieces-disable no-function-outside-class -- DOT label builder, matching getShortName in this file
|
|
106
|
-
function nodeLabel(name, svc, usesCount) {
|
|
107
|
-
// The DECLARED role when the graph carries one; the old inference only as a fallback for a
|
|
108
|
-
// runtime-dependencies.json committed before `role` existed. Inferring it labelled every server
|
|
109
|
-
// with no implements as a "client", which is exactly wrong for a queue-driven service.
|
|
110
|
-
const role = svc.role ?? (svc.implements.length > 0 ? 'server' : 'client');
|
|
111
|
-
// The declared name is quoted for the reader — those quotes MUST be DOT-escaped, or they end
|
|
112
|
-
// the label string and the whole graph stops parsing.
|
|
113
|
-
const declared = svc.serviceName === undefined ? '' : `, \\"${(0, dot_syntax_1.dotValue)(svc.serviceName)}\\"`;
|
|
114
|
-
let label = `${(0, dot_syntax_1.dotValue)(getShortName(name))}\\n(${role}, L${svc.level}${declared})`;
|
|
115
|
-
label += `\\nImplements (${svc.implements.length})\\nUses (${usesCount})`;
|
|
116
|
-
return label;
|
|
117
|
-
}
|
|
118
93
|
/**
|
|
119
94
|
* DOT for ONE NON-QUEUED runtime edge: a direct labeled SOLID arrow (producer calls consumer, the
|
|
120
95
|
* response comes back). Queued (pubsub) hops are drawn by {@link queuesDot} instead, because they
|
|
@@ -176,16 +151,16 @@ function queueLine(key, queue) {
|
|
|
176
151
|
}
|
|
177
152
|
/** The node statement + enqueue/deliver arrows for one queue box. Service identities remain raw; only emitted DOT is escaped. */
|
|
178
153
|
// webpieces-disable no-function-outside-class -- DOT string builder, matching getShortName in this file
|
|
179
|
-
function queueBoxDot(id, body, producers, consumers, model) {
|
|
154
|
+
function queueBoxDot(id, body, producers, consumers, model, contractCount, queueKeys) {
|
|
180
155
|
// Record-mode label: the text must clear recordValue(), and QUEUE_LABEL_PREFIX supplies the
|
|
181
156
|
// empty leading field that draws the cylinder's end cap. Drop it and the node silently
|
|
182
157
|
// degrades to a plain box.
|
|
183
158
|
let dot = model.node(id, ` "${id}" [shape=${runtime_viz_theme_1.QUEUE_SHAPE}, style="filled", fillcolor="${runtime_viz_theme_1.QUEUE_FILL}", ` +
|
|
184
|
-
`class="${runtime_viz_theme_1.QUEUE_CLASS}", label="${runtime_viz_theme_1.QUEUE_LABEL_PREFIX}${body}"];\n
|
|
159
|
+
`class="${runtime_viz_theme_1.QUEUE_CLASS}", label="${runtime_viz_theme_1.QUEUE_LABEL_PREFIX}${body}"];\n`, [], queueKeys);
|
|
185
160
|
for (const producer of producers)
|
|
186
|
-
dot += model.edge(producer, id, ` "${serviceNodeId(producer)}" -> "${id}" [label="
|
|
161
|
+
dot += model.edge(producer, id, ` "${serviceNodeId(producer)}" -> "${id}" [label="Uses (${contractCount}) ▾\\nenqueue", style=dashed];\n`);
|
|
187
162
|
for (const consumer of consumers)
|
|
188
|
-
dot += model.edge(id, consumer, ` "${id}" -> "${serviceNodeId(consumer)}" [label="
|
|
163
|
+
dot += model.edge(id, consumer, ` "${id}" -> "${serviceNodeId(consumer)}" [label="Uses (${contractCount}) ▾\\ndeliver", style=dashed];\n`);
|
|
189
164
|
return dot;
|
|
190
165
|
}
|
|
191
166
|
/**
|
|
@@ -244,7 +219,7 @@ function queuesDot(graph, hidden, model) {
|
|
|
244
219
|
const viaRaw = edge.via.length > 2
|
|
245
220
|
? `Uses (${edge.via.length}) ▾`
|
|
246
221
|
: edge.via.map((v) => getShortName(v)).join(', ');
|
|
247
|
-
dot += queueBoxDot(`queue__${dotId(edge.from)}__${dotId(edge.to)}`, `${(0, dot_syntax_1.recordValue)(viaRaw)}\\nqueue`, [from], [to], model);
|
|
222
|
+
dot += queueBoxDot(`queue__${dotId(edge.from)}__${dotId(edge.to)}`, `${(0, dot_syntax_1.recordValue)(viaRaw)}\\nqueue`, [from], [to], model, edge.via.length, [`${from}->${to}`]);
|
|
248
223
|
continue;
|
|
249
224
|
}
|
|
250
225
|
if (!byQueue.has(edge.queue))
|
|
@@ -256,7 +231,7 @@ function queuesDot(graph, hidden, model) {
|
|
|
256
231
|
const body = group.members
|
|
257
232
|
.map((key) => queueLine(key, graph.queues[key]))
|
|
258
233
|
.join('\\n');
|
|
259
|
-
dot += queueBoxDot(`queue__${dotId(group.members[0])}`, body, group.producers, group.consumers, model);
|
|
234
|
+
dot += queueBoxDot(`queue__${dotId(group.members[0])}`, body, group.producers, group.consumers, model, 1, group.members);
|
|
260
235
|
}
|
|
261
236
|
return dot;
|
|
262
237
|
}
|
|
@@ -438,14 +413,15 @@ function externalDot(graph, hidden, model) {
|
|
|
438
413
|
}
|
|
439
414
|
/** Build the Graphviz DOT for the runtime service graph. */
|
|
440
415
|
// webpieces-disable no-function-outside-class -- module entry point, matching the sibling builders here
|
|
441
|
-
function generateRuntimeDot(graph, title = 'WebPieces Runtime Architecture', options = new RuntimeVizOptions()) {
|
|
416
|
+
function generateRuntimeDot(graph, title = 'WebPieces Runtime Architecture', options = new runtime_viz_options_1.RuntimeVizOptions()) {
|
|
442
417
|
return generateRuntimeRenderModel(graph, title, options).fullDot;
|
|
443
418
|
}
|
|
444
419
|
/** Capture synthetic drawable nodes and edges at their original emission sites. */
|
|
445
420
|
// webpieces-disable no-function-outside-class -- renderer entry point matching generateRuntimeDot
|
|
446
|
-
function generateRuntimeRenderModel(graph, title = 'WebPieces Runtime Architecture', options = new RuntimeVizOptions()) {
|
|
421
|
+
function generateRuntimeRenderModel(graph, title = 'WebPieces Runtime Architecture', options = new runtime_viz_options_1.RuntimeVizOptions()) {
|
|
447
422
|
const model = new graph_render_model_1.GraphRenderModel();
|
|
448
|
-
|
|
423
|
+
model.viewer = 'runtime';
|
|
424
|
+
const adapter = new runtime_viewer_adapter_1.RuntimeViewerAdapter(graph, options.projects);
|
|
449
425
|
let dot = 'digraph RuntimeArchitecture {\n';
|
|
450
426
|
dot += ' rankdir=TB;\n';
|
|
451
427
|
dot += ' node [shape=box, style="filled,rounded", fontname="Arial"];\n';
|
|
@@ -458,9 +434,10 @@ function generateRuntimeRenderModel(graph, title = 'WebPieces Runtime Architectu
|
|
|
458
434
|
if (hidden.has(name))
|
|
459
435
|
continue;
|
|
460
436
|
const svc = graph.services[name];
|
|
461
|
-
|
|
462
|
-
dot += model.node(name, ` "${serviceNodeId(name)}" [fillcolor="${color}", label="${nodeLabel(name, svc, details.nodes[name].used.length)}"];\n`, svc.products ?? []);
|
|
437
|
+
dot += adapter.service(model, name, svc);
|
|
463
438
|
}
|
|
439
|
+
model.bands = adapter.levelBands(model);
|
|
440
|
+
dot += new graph_level_bands_1.LevelBandLayout().dot(model.bands);
|
|
464
441
|
dot += '\n';
|
|
465
442
|
for (const edge of graph.runtimeEdges) {
|
|
466
443
|
if (hidden.has(edge.from) || hidden.has(edge.to))
|
|
@@ -488,11 +465,12 @@ function generateRuntimeRenderModel(graph, title = 'WebPieces Runtime Architectu
|
|
|
488
465
|
model.completeEndpoints();
|
|
489
466
|
// #1179: a queue, trigger or external system belongs to the products of the services it touches.
|
|
490
467
|
model.attachProducts((id) => graph.services[id] !== undefined);
|
|
468
|
+
adapter.contextModes(model);
|
|
491
469
|
model.fullDot = dot;
|
|
492
470
|
return model;
|
|
493
471
|
}
|
|
494
472
|
/** Write the DOT + HTML renderings to tmp/webpieces/. */
|
|
495
|
-
function writeRuntimeVisualization(graph, workspaceRoot, title = 'WebPieces Runtime Architecture', options = new RuntimeVizOptions()) {
|
|
473
|
+
function writeRuntimeVisualization(graph, workspaceRoot, title = 'WebPieces Runtime Architecture', options = new runtime_viz_options_1.RuntimeVizOptions()) {
|
|
496
474
|
const outputDir = path.join(workspaceRoot, 'tmp', 'webpieces');
|
|
497
475
|
if (!fs.existsSync(outputDir))
|
|
498
476
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
@@ -501,7 +479,7 @@ function writeRuntimeVisualization(graph, workspaceRoot, title = 'WebPieces Runt
|
|
|
501
479
|
const dotPath = path.join(outputDir, 'runtime-architecture.dot');
|
|
502
480
|
fs.writeFileSync(dotPath, dot, 'utf-8');
|
|
503
481
|
const htmlPath = path.join(outputDir, 'runtime-architecture.html');
|
|
504
|
-
fs.writeFileSync(htmlPath, new runtime_html_page_1.RuntimeHtmlPage().render(model, title, graph, options
|
|
482
|
+
fs.writeFileSync(htmlPath, new runtime_html_page_1.RuntimeHtmlPage().render(model, title, graph, options), 'utf-8');
|
|
505
483
|
return { dotPath, htmlPath };
|
|
506
484
|
}
|
|
507
485
|
//# sourceMappingURL=runtime-visualizer.js.map
|