@webpieces/nx-webpieces-rules 0.4.883 → 0.4.885
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
|
@@ -66,9 +66,13 @@ class ShellParts {
|
|
|
66
66
|
snapshot;
|
|
67
67
|
responsibilities;
|
|
68
68
|
products;
|
|
69
|
+
runtime;
|
|
70
|
+
frameworks;
|
|
71
|
+
roles;
|
|
72
|
+
emptyState;
|
|
69
73
|
constructor(title, lockControl, legend, edgeKey, filterStatus, snapshot, responsibilities,
|
|
70
74
|
/** Every declared product, sorted (#1179); empty hides the Filter's Product group. */
|
|
71
|
-
products) {
|
|
75
|
+
products, runtime = false, frameworks = graph_color_modes_1.FRAMEWORK_ORDER, roles = graph_color_modes_1.ROLE_STYLES.map((style) => style.role), emptyState = '') {
|
|
72
76
|
this.title = title;
|
|
73
77
|
this.lockControl = lockControl;
|
|
74
78
|
this.legend = legend;
|
|
@@ -77,6 +81,10 @@ class ShellParts {
|
|
|
77
81
|
this.snapshot = snapshot;
|
|
78
82
|
this.responsibilities = responsibilities;
|
|
79
83
|
this.products = products;
|
|
84
|
+
this.runtime = runtime;
|
|
85
|
+
this.frameworks = frameworks;
|
|
86
|
+
this.roles = roles;
|
|
87
|
+
this.emptyState = emptyState;
|
|
80
88
|
}
|
|
81
89
|
}
|
|
82
90
|
exports.ShellParts = ShellParts;
|
|
@@ -106,6 +114,7 @@ class GraphPageShell {
|
|
|
106
114
|
<div class="wp-group">
|
|
107
115
|
<label class="wp-label" for="wp-lock">Focus</label>
|
|
108
116
|
${parts.lockControl}
|
|
117
|
+
<button type="button" class="wp-toggle" id="wp-lock-toggle" aria-pressed="false">Lock <span class="wp-sw" aria-hidden="true"></span></button>
|
|
109
118
|
<button type="button" class="wp-toggle" id="wp-filter-toggle" aria-pressed="false">Hide unconnected <span class="wp-sw" aria-hidden="true"></span></button>
|
|
110
119
|
</div>
|
|
111
120
|
<div class="wp-group">
|
|
@@ -148,21 +157,21 @@ class GraphPageShell {
|
|
|
148
157
|
* (when any is selected) AND belongs to any selected product (when any is selected). Nothing
|
|
149
158
|
* applies until "Show N projects".
|
|
150
159
|
*/
|
|
151
|
-
filterPopover(
|
|
160
|
+
filterPopover(parts) {
|
|
152
161
|
const scopes = exports.SCOPE_OPTIONS.map((option) => `<label class="wp-scope"><input type="radio" name="wp-scope" value="${option.scope}"${option.scope === ChangeScope.EVERYTHING ? ' checked' : ''}>` +
|
|
153
162
|
`<span class="wp-scope-text"><span class="wp-scope-name" data-wp-scope-label="${option.scope}">${option.label}</span>` +
|
|
154
|
-
`<span class="wp-scope-sub">${option.subtitle}</span></span>` +
|
|
163
|
+
`<span class="wp-scope-sub">${parts.runtime && option.scope === ChangeScope.EVERYTHING ? 'every service' : option.subtitle}</span></span>` +
|
|
155
164
|
`<span class="wp-count" data-wp-scope-count="${option.scope}"></span></label>`).join('');
|
|
156
165
|
const chips = (group, values) => values
|
|
157
166
|
.map((value) => `<button type="button" class="wp-chip" data-wp-chip-group="${group}" data-wp-chip="${value}" aria-pressed="false">${value}</button>`)
|
|
158
167
|
.join('');
|
|
159
|
-
return `<div class="wp-pop wp-filter-pop" id="wp-filter-pop" role="dialog" aria-label="Filter projects" hidden>
|
|
168
|
+
return `<div class="wp-pop wp-filter-pop" id="wp-filter-pop" role="dialog" aria-label="Filter ${parts.runtime ? 'services' : 'projects'}" hidden>
|
|
160
169
|
<header><span>Filter</span><button type="button" class="wp-icon wp-icon-mini" id="wp-filter-close" aria-label="Close the filter">✕</button></header>
|
|
161
170
|
<fieldset class="wp-fgroup" id="wp-scope-group"><legend id="wp-scope-legend">Changes on this branch</legend>${this.impactKinds('filter')}${scopes}<p class="wp-scope-reason" id="wp-scope-reason" hidden></p></fieldset>
|
|
162
|
-
<fieldset class="wp-fgroup"><legend>Runtime <small>any match</small></legend><div class="wp-chips">${chips('runtime',
|
|
163
|
-
<fieldset class="wp-fgroup"><legend>Role <small>any match</small></legend><div class="wp-chips">${chips('role',
|
|
164
|
-
${this.productGroup(products)}
|
|
165
|
-
<footer><button type="button" class="wp-text-btn" id="wp-filter-clear">Clear all</button><button type="button" class="wp-primary" id="wp-filter-apply">Show all projects</button></footer>
|
|
171
|
+
<fieldset class="wp-fgroup"><legend>Runtime <small>any match</small></legend><div class="wp-chips">${chips('runtime', parts.frameworks)}</div>${parts.runtime && parts.frameworks.length === 0 ? '<p>No declared framework metadata in the saved project graph. Refresh explicitly with architecture:generate.</p>' : ''}</fieldset>
|
|
172
|
+
<fieldset class="wp-fgroup"><legend>Role <small>any match</small></legend><div class="wp-chips">${chips('role', parts.roles)}</div></fieldset>
|
|
173
|
+
${this.productGroup(parts.products)}
|
|
174
|
+
<footer><button type="button" class="wp-text-btn" id="wp-filter-clear">Clear all</button><button type="button" class="wp-primary" id="wp-filter-apply">Show all ${parts.runtime ? 'services' : 'projects'}</button></footer>
|
|
166
175
|
</div>`;
|
|
167
176
|
}
|
|
168
177
|
/**
|
|
@@ -201,12 +210,12 @@ class GraphPageShell {
|
|
|
201
210
|
</div>`;
|
|
202
211
|
}
|
|
203
212
|
overlays(parts) {
|
|
204
|
-
return
|
|
213
|
+
return `${parts.emptyState}<div class="wp-pop wp-legend-popout" id="wp-legend-popout" hidden>
|
|
205
214
|
<header><span id="wp-legend-pop-title">Legend · Runtime</span><button type="button" class="wp-icon wp-icon-mini" id="wp-legend-close" aria-label="Close the legend">✕</button></header>
|
|
206
215
|
${parts.legend}
|
|
207
216
|
</div>
|
|
208
|
-
${this.help()}
|
|
209
|
-
${this.filterPopover(parts
|
|
217
|
+
${this.help(parts.runtime)}
|
|
218
|
+
${this.filterPopover(parts)}
|
|
210
219
|
<div class="wp-pop wp-snapshot" id="wp-snapshot" hidden>${parts.snapshot}</div>
|
|
211
220
|
<aside class="wp-panel" id="wp-resp-panel" aria-label="Responsibilities" hidden>
|
|
212
221
|
<header><span>Responsibilities</span><button type="button" class="wp-icon wp-icon-mini" id="wp-resp-close" aria-label="Close responsibilities">✕</button></header>
|
|
@@ -214,7 +223,17 @@ class GraphPageShell {
|
|
|
214
223
|
</aside>`;
|
|
215
224
|
}
|
|
216
225
|
/** The two hint paragraphs that used to sit above the graph, plus the keyboard shortcuts. */
|
|
217
|
-
help() {
|
|
226
|
+
help(runtime) {
|
|
227
|
+
if (runtime)
|
|
228
|
+
return `<div class="wp-pop wp-help" id="wp-help" role="dialog" aria-label="How to read this graph" hidden>
|
|
229
|
+
<header><span>Reading the runtime graph</span><button type="button" class="wp-icon wp-icon-mini" id="wp-help-close" aria-label="Close help">✕</button></header>
|
|
230
|
+
<p>Rows preserve saved runtime call-depth levels. Boxes are services; horizontal cylinders are queues, upright cylinders datastores, and clocks scheduled triggers. Solid arrows are RPC calls, dashed arrows queued events; red arrows admit legacy cycles.</p>
|
|
231
|
+
<p>Click a box for Lock, Filter Unconnected, or Implements. Implements in the box is a plain summary. Click Uses on a relationship line for its complete details. Hover traces chains without opening details.</p>
|
|
232
|
+
<p>Lock pins separate incoming and outgoing chains; hover adds another chain temporarily. Hide unconnected removes unrelated boxes independently. Clear all clears facets, leaving Lock and chain filtering intact.</p>
|
|
233
|
+
<p>Filters intersect declared runtime, role, product closure and Nx build scope. Counts describe services; attached contextual nodes are counted separately. Impact uses compile-project sets, never runtime reachability. Queues and vendors are context, never source owners or CI projects.</p>
|
|
234
|
+
<p>Runtime uses declared framework colors, Architecture declared roles; contextual nodes keep their kind colors. Product colors include inherited membership. Unknown metadata is neutral. Missing saved metadata requires explicit architecture:generate.</p>
|
|
235
|
+
<p><kbd>/</kbd> Focus search · <kbd>1</kbd>–<kbd>4</kbd> modes · <kbd>Esc</kbd> dismiss or unlock</p>
|
|
236
|
+
</div>`;
|
|
218
237
|
return `<div class="wp-pop wp-help" id="wp-help" role="dialog" aria-label="How to read this graph" hidden>
|
|
219
238
|
<header><span>Reading the graph</span><button type="button" class="wp-icon wp-icon-mini" id="wp-help-close" aria-label="Close help">✕</button></header>
|
|
220
239
|
<p>Every row is one dependency level: the HIGHEST level is the top row and L0, the foundation libraries, is always the bottom row. Servers, clients and apps always form the top row (a bundle sits above the apps it aggregates). Transitive dependencies are allowed but not drawn.</p>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-page-shell.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-page-shell.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The architecture page's shell (#1155): a collapsible dark drawer on the left holding every control,\n * and the graph filling the rest of the viewport.\n *\n * Before this, two hint paragraphs, a ~650px three-column legend, a lock dropdown and a zoom bar all\n * sat ABOVE the graph, so a laptop's first screen showed no graph at all. Now the hints are a \"?\"\n * popover, the legend lives in the drawer (and can pop out over the graph), zoom floats bottom-right\n * (graph-navigation.ts) and the responsibilities open in a right-hand panel.\n *\n * Markup only. Behaviour is graph-visualizer.client.ts; styling is graph-page-styles.ts. No element\n * carries a `style=` attribute — every look is a class.\n */\n\nimport { FRAMEWORK_ORDER, GRAPH_MODES, GraphMode, ModeInfo, ROLE_STYLES, RoleStyle } from './graph-color-modes';\nimport { ProductColor, ProductPalette } from './graph-products';\n\n/**\n * The Filter popover's \"Changes on this branch\" choices. The page computes each set from the Impact\n * sidecar (graph-impact.ts): CHANGED = touched; DEPENDENTS = touched + affected (nx affected: their\n * tests re-run); DEPENDENCIES = touched + every transitive dependency of it; BUILD = touched +\n * affected + every build input — the dependencies of the DEPENDENTS too, which is why it is more than\n * the union of the two rows above it.\n *\n * \"Dependents\" are what USE a project and \"dependencies\" what it uses (#1179). \"Dependee\" would mean\n * the thing depended ON — the opposite of the dependents row — so it is never used.\n */\nexport enum ChangeScope {\n EVERYTHING = 'everything',\n CHANGED = 'changed',\n DEPENDENTS = 'dependents',\n DEPENDENCIES = 'dependencies',\n BUILD = 'build',\n}\n\n/**\n * One change scope: its name and the grey second line under it, like the Color-by menu. These are the\n * ONE copy of the labels — the page script reads each name back from its `data-wp-scope-label` (the\n * top-bar pill), so the two cannot drift apart.\n */\nexport class ScopeOption {\n constructor(\n public readonly scope: ChangeScope,\n public readonly label: string,\n public readonly subtitle: string,\n ) {}\n}\n\nexport const SCOPE_OPTIONS: readonly ScopeOption[] = [\n new ScopeOption(ChangeScope.EVERYTHING, 'All', 'every project'),\n new ScopeOption(ChangeScope.CHANGED, 'Changed', 'owns a changed file'),\n new ScopeOption(ChangeScope.DEPENDENTS, 'Changed + dependents', '+ everything that uses them (their tests re-run)'),\n new ScopeOption(ChangeScope.DEPENDENCIES, 'Changed + dependencies', '+ everything they use'),\n new ScopeOption(ChangeScope.BUILD, 'Changed + dependents + dependencies', 'everything CI builds for this branch'),\n];\n\n/** Everything the shell frames, already rendered by its owner. */\nexport class ShellParts {\n constructor(\n public readonly title: string,\n public readonly lockControl: string,\n public readonly legend: string,\n public readonly edgeKey: string,\n public readonly filterStatus: string,\n public readonly snapshot: string,\n public readonly responsibilities: string,\n /** Every declared product, sorted (#1179); empty hides the Filter's Product group. */\n public readonly products: string[],\n ) {}\n}\n\nexport class GraphPageShell {\n body(parts: ShellParts): string {\n return `<div class=\"wp-shell\" id=\"wp-shell\">\n ${this.drawer(parts)}\n <main class=\"wp-main\">\n ${this.topbar(parts)}\n <div id=\"graph\"></div>\n ${this.overlays(parts)}\n </main>\n</div>`;\n }\n\n private drawer(parts: ShellParts): string {\n return `<aside class=\"wp-side\" id=\"wp-side\" aria-label=\"Graph controls\">\n <div class=\"wp-brand\">${this.brandIcon()}<span>${parts.title}</span></div>\n <div class=\"wp-group\" role=\"group\" aria-label=\"Color mode\">\n <div class=\"wp-label\">Color by</div>\n ${this.modePulldown()}\n <p class=\"wp-mode-reason\" id=\"wp-impact-reason\" hidden></p>\n ${this.impactKinds('drawer')}\n </div>\n <div class=\"wp-group\">\n <button type=\"button\" class=\"wp-filter-btn\" id=\"wp-filter-open\" aria-controls=\"wp-filter-pop\" aria-expanded=\"false\"><span>Filter</span><span class=\"wp-badge\" id=\"wp-filter-badge\" hidden>0</span></button>\n </div>\n <div class=\"wp-group\">\n <label class=\"wp-label\" for=\"wp-lock\">Focus</label>\n ${parts.lockControl}\n <button type=\"button\" class=\"wp-toggle\" id=\"wp-filter-toggle\" aria-pressed=\"false\">Hide unconnected <span class=\"wp-sw\" aria-hidden=\"true\"></span></button>\n </div>\n <div class=\"wp-group\">\n <div class=\"wp-label wp-label-row\">Legend <button type=\"button\" class=\"wp-icon wp-icon-mini wp-icon-dark\" id=\"wp-legend-pop\" title=\"Pop the legend out over the graph\" aria-label=\"Pop the legend out over the graph\">⇱</button></div>\n ${parts.legend}\n </div>\n <div class=\"wp-group\">\n <div class=\"wp-label\">Edges</div>\n ${parts.edgeKey}\n </div>\n <div class=\"wp-foot\">\n <button type=\"button\" class=\"wp-link\" id=\"wp-resp-open\" aria-expanded=\"false\">Responsibilities</button>\n <button type=\"button\" class=\"wp-link\" id=\"wp-snapshot-open\" aria-expanded=\"false\">Saved snapshot</button>\n </div>\n </aside>`;\n }\n\n /**\n * \"Color by\": ONE pill-shaped trigger showing the current mode (name + subtitle) that opens a\n * menu of the four. Keys 1/2/3/4 and the #runtime / #architecture / #impact / #product links still\n * switch.\n */\n private modePulldown(): string {\n const first = GRAPH_MODES[0];\n return `<div class=\"wp-pulldown\">\n <button type=\"button\" class=\"wp-pill-trigger\" id=\"wp-mode-trigger\" aria-haspopup=\"menu\" aria-controls=\"wp-mode-menu\" aria-expanded=\"false\">\n <span class=\"wp-pill-text\"><span class=\"wp-mode-name\" id=\"wp-mode-current\">${first.name}</span><span class=\"wp-mode-sub\" id=\"wp-mode-current-sub\">${first.subtitle}</span></span><span class=\"wp-caret\" aria-hidden=\"true\">▾</span>\n </button>\n <div class=\"wp-menu\" id=\"wp-mode-menu\" role=\"menu\" aria-label=\"Color by\" hidden>${GRAPH_MODES.map((info: ModeInfo, index: number): string => this.modeItem(info, index)).join('')}</div>\n </div>`;\n }\n\n /** One menu option, two lines: the mode's name, then its grey subtitle. */\n private modeItem(info: ModeInfo, index: number): string {\n const checked = info.mode === GraphMode.RUNTIME ? 'true' : 'false';\n return (\n `<button type=\"button\" class=\"wp-mode\" role=\"menuitemradio\" data-wp-mode=\"${info.mode}\" aria-checked=\"${checked}\" title=\"Key ${index + 1}\">` +\n `<span class=\"wp-mode-name\">${info.name}</span><span class=\"wp-mode-sub\">${info.subtitle}</span></button>`\n );\n }\n\n /**\n * The Filter popover. Its four groups INTERSECT: a box is shown when it is in the chosen change\n * scope AND carries any selected runtime chip (when any is selected) AND any selected role chip\n * (when any is selected) AND belongs to any selected product (when any is selected). Nothing\n * applies until \"Show N projects\".\n */\n private filterPopover(products: string[]): string {\n const scopes = SCOPE_OPTIONS.map(\n (option: ScopeOption): string =>\n `<label class=\"wp-scope\"><input type=\"radio\" name=\"wp-scope\" value=\"${option.scope}\"${option.scope === ChangeScope.EVERYTHING ? ' checked' : ''}>` +\n `<span class=\"wp-scope-text\"><span class=\"wp-scope-name\" data-wp-scope-label=\"${option.scope}\">${option.label}</span>` +\n `<span class=\"wp-scope-sub\">${option.subtitle}</span></span>` +\n `<span class=\"wp-count\" data-wp-scope-count=\"${option.scope}\"></span></label>`,\n ).join('');\n const chips = (group: string, values: readonly string[]): string =>\n values\n .map(\n (value: string): string =>\n `<button type=\"button\" class=\"wp-chip\" data-wp-chip-group=\"${group}\" data-wp-chip=\"${value}\" aria-pressed=\"false\">${value}</button>`,\n )\n .join('');\n return `<div class=\"wp-pop wp-filter-pop\" id=\"wp-filter-pop\" role=\"dialog\" aria-label=\"Filter projects\" hidden>\n <header><span>Filter</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-filter-close\" aria-label=\"Close the filter\">✕</button></header>\n <fieldset class=\"wp-fgroup\" id=\"wp-scope-group\"><legend id=\"wp-scope-legend\">Changes on this branch</legend>${this.impactKinds('filter')}${scopes}<p class=\"wp-scope-reason\" id=\"wp-scope-reason\" hidden></p></fieldset>\n <fieldset class=\"wp-fgroup\"><legend>Runtime <small>any match</small></legend><div class=\"wp-chips\">${chips('runtime', FRAMEWORK_ORDER)}</div></fieldset>\n <fieldset class=\"wp-fgroup\"><legend>Role <small>any match</small></legend><div class=\"wp-chips\">${chips('role', ROLE_STYLES.map((style: RoleStyle): string => style.role))}</div></fieldset>\n ${this.productGroup(products)}\n <footer><button type=\"button\" class=\"wp-text-btn\" id=\"wp-filter-clear\">Clear all</button><button type=\"button\" class=\"wp-primary\" id=\"wp-filter-apply\">Show all projects</button></footer>\n </div>`;\n }\n\n /**\n * The Product group (#1179): one chip per declared product, each with its palette swatch. Several\n * chips select the UNION of their closures; the group intersects with the others. Omitted when the\n * workspace declares no product.\n */\n private productGroup(products: string[]): string {\n if (products.length === 0) return '';\n const palette = new ProductPalette(products);\n const chips = palette.colors\n .map(\n (color: ProductColor): string =>\n `<button type=\"button\" class=\"wp-chip\" data-wp-chip-group=\"product\" data-wp-chip=\"${color.product}\" aria-pressed=\"false\">` +\n `<svg class=\"wp-chip-swatch\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"4.5\" fill=\"${color.color}\"/></svg>` +\n `${color.product}</button>`,\n )\n .join('');\n return `<fieldset class=\"wp-fgroup\" id=\"wp-product-group\"><legend>Product <small>any match</small></legend><div class=\"wp-chips\">${chips}</div></fieldset>`;\n }\n\n /**\n * Which Impact scan the page shows (#1163): \"Changed on this branch\" (fork point → working tree)\n * or \"Last commit\" (HEAD^ → working tree). Hidden until the page finds TWO scans in the sidecar;\n * `where` tells the drawer's copy (shown only in Impact mode) from the Filter popover's.\n */\n private impactKinds(where: string): string {\n return (\n `<div class=\"wp-chips wp-impact-kinds\" data-wp-impact-kinds=\"${where}\" role=\"group\" aria-label=\"Compare\" hidden>` +\n '<button type=\"button\" class=\"wp-chip\" data-wp-impact-kind=\"branch\" aria-pressed=\"false\">Changed on this branch</button>' +\n '<button type=\"button\" class=\"wp-chip\" data-wp-impact-kind=\"commit\" aria-pressed=\"false\">Last commit</button></div>'\n );\n }\n\n private topbar(parts: ShellParts): string {\n return `<div class=\"wp-topbar\">\n <button type=\"button\" class=\"wp-icon\" id=\"wp-collapse\" aria-label=\"Toggle the controls drawer\" aria-controls=\"wp-side\" aria-expanded=\"true\">☰</button>\n <div class=\"wp-crumb\" id=\"wp-crumb\">Mode: <b>Runtime</b></div>\n <div class=\"wp-pills\" id=\"wp-filter-pills\" aria-label=\"Active filters\"></div>\n ${parts.filterStatus}\n <button type=\"button\" class=\"wp-icon\" id=\"wp-help-btn\" aria-label=\"How to read this graph\" aria-controls=\"wp-help\" aria-expanded=\"false\">?</button>\n </div>`;\n }\n\n private overlays(parts: ShellParts): string {\n return `<div class=\"wp-pop wp-legend-popout\" id=\"wp-legend-popout\" hidden>\n <header><span id=\"wp-legend-pop-title\">Legend · Runtime</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-legend-close\" aria-label=\"Close the legend\">✕</button></header>\n ${parts.legend}\n </div>\n ${this.help()}\n ${this.filterPopover(parts.products)}\n <div class=\"wp-pop wp-snapshot\" id=\"wp-snapshot\" hidden>${parts.snapshot}</div>\n <aside class=\"wp-panel\" id=\"wp-resp-panel\" aria-label=\"Responsibilities\" hidden>\n <header><span>Responsibilities</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-resp-close\" aria-label=\"Close responsibilities\">✕</button></header>\n ${parts.responsibilities}\n </aside>`;\n }\n\n /** The two hint paragraphs that used to sit above the graph, plus the keyboard shortcuts. */\n private help(): string {\n return `<div class=\"wp-pop wp-help\" id=\"wp-help\" role=\"dialog\" aria-label=\"How to read this graph\" hidden>\n <header><span>Reading the graph</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-help-close\" aria-label=\"Close help\">✕</button></header>\n <p>Every row is one dependency level: the HIGHEST level is the top row and L0, the foundation libraries, is always the bottom row. Servers, clients and apps always form the top row (a bundle sits above the apps it aggregates). Transitive dependencies are allowed but not drawn.</p>\n <p>💡 <strong>Click any box</strong> for its menu: <strong>View Design</strong> (only where the project has a generated <strong>design.html</strong>, i.e. what the AI sees inside it), <strong>Lock/Unlock</strong> (the same lock as the Focus field), <strong>Filter Unconnected</strong> (removes unrelated boxes, keeping each L-number row).</p>\n <p><strong>Filter</strong> narrows the graph by what changed on this branch or in the last commit, by runtime, by role and by product; the groups combine. A product keeps its servers, clients and apps plus everything they depend on, so <strong>Product</strong> + <strong>Changed + dependents + dependencies</strong> is exactly what this branch builds that can affect that product. Every box keeps its own L-row, and a level left empty shows as a thin labeled band.</p>\n <p>🔦 <strong>Hover any box</strong> to trace its <em>entire</em> dependency chain — every ancestor above it and every dependency below it, with the lines between — while the rest of the graph dims.</p>\n <p><strong>Runtime</strong>: a box may depend on a box carrying every color it has. <strong>Architecture</strong>: what each box is in the system. <strong>Impact</strong>: what changed on this branch or in the last commit (amber) and its dependents (amber outline); with both available, a toggle picks which. <strong>Product</strong>: which products each box belongs to — one color per product, stripes for several, grey for every product, a dashed white box for none.</p>\n <p><kbd>/</kbd> focus the lock search · <kbd>1</kbd> <kbd>2</kbd> <kbd>3</kbd> <kbd>4</kbd> switch mode · <kbd>Esc</kbd> unlock</p>\n </div>`;\n }\n\n private brandIcon(): string {\n return (\n '<svg width=\"22\" height=\"22\" viewBox=\"0 0 22 22\" aria-hidden=\"true\">' +\n '<rect x=\"1\" y=\"1\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#a46bf7\" stroke-width=\"1.6\"/>' +\n '<rect x=\"13\" y=\"1\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<rect x=\"7\" y=\"15\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<path d=\"M5 7v4h12V7M11 11v4\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/></svg>'\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph-page-shell.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-page-shell.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The architecture page's shell (#1155): a collapsible dark drawer on the left holding every control,\n * and the graph filling the rest of the viewport.\n *\n * Before this, two hint paragraphs, a ~650px three-column legend, a lock dropdown and a zoom bar all\n * sat ABOVE the graph, so a laptop's first screen showed no graph at all. Now the hints are a \"?\"\n * popover, the legend lives in the drawer (and can pop out over the graph), zoom floats bottom-right\n * (graph-navigation.ts) and the responsibilities open in a right-hand panel.\n *\n * Markup only. Behaviour is graph-visualizer.client.ts; styling is graph-page-styles.ts. No element\n * carries a `style=` attribute — every look is a class.\n */\n\nimport { FRAMEWORK_ORDER, GRAPH_MODES, GraphMode, ModeInfo, ROLE_STYLES, RoleStyle } from './graph-color-modes';\nimport { ProductColor, ProductPalette } from './graph-products';\n\n/**\n * The Filter popover's \"Changes on this branch\" choices. The page computes each set from the Impact\n * sidecar (graph-impact.ts): CHANGED = touched; DEPENDENTS = touched + affected (nx affected: their\n * tests re-run); DEPENDENCIES = touched + every transitive dependency of it; BUILD = touched +\n * affected + every build input — the dependencies of the DEPENDENTS too, which is why it is more than\n * the union of the two rows above it.\n *\n * \"Dependents\" are what USE a project and \"dependencies\" what it uses (#1179). \"Dependee\" would mean\n * the thing depended ON — the opposite of the dependents row — so it is never used.\n */\nexport enum ChangeScope {\n EVERYTHING = 'everything',\n CHANGED = 'changed',\n DEPENDENTS = 'dependents',\n DEPENDENCIES = 'dependencies',\n BUILD = 'build',\n}\n\n/**\n * One change scope: its name and the grey second line under it, like the Color-by menu. These are the\n * ONE copy of the labels — the page script reads each name back from its `data-wp-scope-label` (the\n * top-bar pill), so the two cannot drift apart.\n */\nexport class ScopeOption {\n constructor(\n public readonly scope: ChangeScope,\n public readonly label: string,\n public readonly subtitle: string,\n ) {}\n}\n\nexport const SCOPE_OPTIONS: readonly ScopeOption[] = [\n new ScopeOption(ChangeScope.EVERYTHING, 'All', 'every project'),\n new ScopeOption(ChangeScope.CHANGED, 'Changed', 'owns a changed file'),\n new ScopeOption(ChangeScope.DEPENDENTS, 'Changed + dependents', '+ everything that uses them (their tests re-run)'),\n new ScopeOption(ChangeScope.DEPENDENCIES, 'Changed + dependencies', '+ everything they use'),\n new ScopeOption(ChangeScope.BUILD, 'Changed + dependents + dependencies', 'everything CI builds for this branch'),\n];\n\n/** Everything the shell frames, already rendered by its owner. */\nexport class ShellParts {\n constructor(\n public readonly title: string,\n public readonly lockControl: string,\n public readonly legend: string,\n public readonly edgeKey: string,\n public readonly filterStatus: string,\n public readonly snapshot: string,\n public readonly responsibilities: string,\n /** Every declared product, sorted (#1179); empty hides the Filter's Product group. */\n public readonly products: string[],\n public readonly runtime: boolean = false,\n public readonly frameworks: readonly string[] = FRAMEWORK_ORDER,\n public readonly roles: readonly string[] = ROLE_STYLES.map((style: RoleStyle): string => style.role),\n public readonly emptyState: string = '',\n ) {}\n}\n\nexport class GraphPageShell {\n body(parts: ShellParts): string {\n return `<div class=\"wp-shell\" id=\"wp-shell\">\n ${this.drawer(parts)}\n <main class=\"wp-main\">\n ${this.topbar(parts)}\n <div id=\"graph\"></div>\n ${this.overlays(parts)}\n </main>\n</div>`;\n }\n\n private drawer(parts: ShellParts): string {\n return `<aside class=\"wp-side\" id=\"wp-side\" aria-label=\"Graph controls\">\n <div class=\"wp-brand\">${this.brandIcon()}<span>${parts.title}</span></div>\n <div class=\"wp-group\" role=\"group\" aria-label=\"Color mode\">\n <div class=\"wp-label\">Color by</div>\n ${this.modePulldown()}\n <p class=\"wp-mode-reason\" id=\"wp-impact-reason\" hidden></p>\n ${this.impactKinds('drawer')}\n </div>\n <div class=\"wp-group\">\n <button type=\"button\" class=\"wp-filter-btn\" id=\"wp-filter-open\" aria-controls=\"wp-filter-pop\" aria-expanded=\"false\"><span>Filter</span><span class=\"wp-badge\" id=\"wp-filter-badge\" hidden>0</span></button>\n </div>\n <div class=\"wp-group\">\n <label class=\"wp-label\" for=\"wp-lock\">Focus</label>\n ${parts.lockControl}\n <button type=\"button\" class=\"wp-toggle\" id=\"wp-lock-toggle\" aria-pressed=\"false\">Lock <span class=\"wp-sw\" aria-hidden=\"true\"></span></button>\n <button type=\"button\" class=\"wp-toggle\" id=\"wp-filter-toggle\" aria-pressed=\"false\">Hide unconnected <span class=\"wp-sw\" aria-hidden=\"true\"></span></button>\n </div>\n <div class=\"wp-group\">\n <div class=\"wp-label wp-label-row\">Legend <button type=\"button\" class=\"wp-icon wp-icon-mini wp-icon-dark\" id=\"wp-legend-pop\" title=\"Pop the legend out over the graph\" aria-label=\"Pop the legend out over the graph\">⇱</button></div>\n ${parts.legend}\n </div>\n <div class=\"wp-group\">\n <div class=\"wp-label\">Edges</div>\n ${parts.edgeKey}\n </div>\n <div class=\"wp-foot\">\n <button type=\"button\" class=\"wp-link\" id=\"wp-resp-open\" aria-expanded=\"false\">Responsibilities</button>\n <button type=\"button\" class=\"wp-link\" id=\"wp-snapshot-open\" aria-expanded=\"false\">Saved snapshot</button>\n </div>\n </aside>`;\n }\n\n /**\n * \"Color by\": ONE pill-shaped trigger showing the current mode (name + subtitle) that opens a\n * menu of the four. Keys 1/2/3/4 and the #runtime / #architecture / #impact / #product links still\n * switch.\n */\n private modePulldown(): string {\n const first = GRAPH_MODES[0];\n return `<div class=\"wp-pulldown\">\n <button type=\"button\" class=\"wp-pill-trigger\" id=\"wp-mode-trigger\" aria-haspopup=\"menu\" aria-controls=\"wp-mode-menu\" aria-expanded=\"false\">\n <span class=\"wp-pill-text\"><span class=\"wp-mode-name\" id=\"wp-mode-current\">${first.name}</span><span class=\"wp-mode-sub\" id=\"wp-mode-current-sub\">${first.subtitle}</span></span><span class=\"wp-caret\" aria-hidden=\"true\">▾</span>\n </button>\n <div class=\"wp-menu\" id=\"wp-mode-menu\" role=\"menu\" aria-label=\"Color by\" hidden>${GRAPH_MODES.map((info: ModeInfo, index: number): string => this.modeItem(info, index)).join('')}</div>\n </div>`;\n }\n\n /** One menu option, two lines: the mode's name, then its grey subtitle. */\n private modeItem(info: ModeInfo, index: number): string {\n const checked = info.mode === GraphMode.RUNTIME ? 'true' : 'false';\n return (\n `<button type=\"button\" class=\"wp-mode\" role=\"menuitemradio\" data-wp-mode=\"${info.mode}\" aria-checked=\"${checked}\" title=\"Key ${index + 1}\">` +\n `<span class=\"wp-mode-name\">${info.name}</span><span class=\"wp-mode-sub\">${info.subtitle}</span></button>`\n );\n }\n\n /**\n * The Filter popover. Its four groups INTERSECT: a box is shown when it is in the chosen change\n * scope AND carries any selected runtime chip (when any is selected) AND any selected role chip\n * (when any is selected) AND belongs to any selected product (when any is selected). Nothing\n * applies until \"Show N projects\".\n */\n private filterPopover(parts: ShellParts): string {\n const scopes = SCOPE_OPTIONS.map(\n (option: ScopeOption): string =>\n `<label class=\"wp-scope\"><input type=\"radio\" name=\"wp-scope\" value=\"${option.scope}\"${option.scope === ChangeScope.EVERYTHING ? ' checked' : ''}>` +\n `<span class=\"wp-scope-text\"><span class=\"wp-scope-name\" data-wp-scope-label=\"${option.scope}\">${option.label}</span>` +\n `<span class=\"wp-scope-sub\">${parts.runtime && option.scope === ChangeScope.EVERYTHING ? 'every service' : option.subtitle}</span></span>` +\n `<span class=\"wp-count\" data-wp-scope-count=\"${option.scope}\"></span></label>`,\n ).join('');\n const chips = (group: string, values: readonly string[]): string =>\n values\n .map(\n (value: string): string =>\n `<button type=\"button\" class=\"wp-chip\" data-wp-chip-group=\"${group}\" data-wp-chip=\"${value}\" aria-pressed=\"false\">${value}</button>`,\n )\n .join('');\n return `<div class=\"wp-pop wp-filter-pop\" id=\"wp-filter-pop\" role=\"dialog\" aria-label=\"Filter ${parts.runtime ? 'services' : 'projects'}\" hidden>\n <header><span>Filter</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-filter-close\" aria-label=\"Close the filter\">✕</button></header>\n <fieldset class=\"wp-fgroup\" id=\"wp-scope-group\"><legend id=\"wp-scope-legend\">Changes on this branch</legend>${this.impactKinds('filter')}${scopes}<p class=\"wp-scope-reason\" id=\"wp-scope-reason\" hidden></p></fieldset>\n <fieldset class=\"wp-fgroup\"><legend>Runtime <small>any match</small></legend><div class=\"wp-chips\">${chips('runtime', parts.frameworks)}</div>${parts.runtime && parts.frameworks.length === 0 ? '<p>No declared framework metadata in the saved project graph. Refresh explicitly with architecture:generate.</p>' : ''}</fieldset>\n <fieldset class=\"wp-fgroup\"><legend>Role <small>any match</small></legend><div class=\"wp-chips\">${chips('role', parts.roles)}</div></fieldset>\n ${this.productGroup(parts.products)}\n <footer><button type=\"button\" class=\"wp-text-btn\" id=\"wp-filter-clear\">Clear all</button><button type=\"button\" class=\"wp-primary\" id=\"wp-filter-apply\">Show all ${parts.runtime ? 'services' : 'projects'}</button></footer>\n </div>`;\n }\n\n /**\n * The Product group (#1179): one chip per declared product, each with its palette swatch. Several\n * chips select the UNION of their closures; the group intersects with the others. Omitted when the\n * workspace declares no product.\n */\n private productGroup(products: string[]): string {\n if (products.length === 0) return '';\n const palette = new ProductPalette(products);\n const chips = palette.colors\n .map(\n (color: ProductColor): string =>\n `<button type=\"button\" class=\"wp-chip\" data-wp-chip-group=\"product\" data-wp-chip=\"${color.product}\" aria-pressed=\"false\">` +\n `<svg class=\"wp-chip-swatch\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\"><circle cx=\"5\" cy=\"5\" r=\"4.5\" fill=\"${color.color}\"/></svg>` +\n `${color.product}</button>`,\n )\n .join('');\n return `<fieldset class=\"wp-fgroup\" id=\"wp-product-group\"><legend>Product <small>any match</small></legend><div class=\"wp-chips\">${chips}</div></fieldset>`;\n }\n\n /**\n * Which Impact scan the page shows (#1163): \"Changed on this branch\" (fork point → working tree)\n * or \"Last commit\" (HEAD^ → working tree). Hidden until the page finds TWO scans in the sidecar;\n * `where` tells the drawer's copy (shown only in Impact mode) from the Filter popover's.\n */\n private impactKinds(where: string): string {\n return (\n `<div class=\"wp-chips wp-impact-kinds\" data-wp-impact-kinds=\"${where}\" role=\"group\" aria-label=\"Compare\" hidden>` +\n '<button type=\"button\" class=\"wp-chip\" data-wp-impact-kind=\"branch\" aria-pressed=\"false\">Changed on this branch</button>' +\n '<button type=\"button\" class=\"wp-chip\" data-wp-impact-kind=\"commit\" aria-pressed=\"false\">Last commit</button></div>'\n );\n }\n\n private topbar(parts: ShellParts): string {\n return `<div class=\"wp-topbar\">\n <button type=\"button\" class=\"wp-icon\" id=\"wp-collapse\" aria-label=\"Toggle the controls drawer\" aria-controls=\"wp-side\" aria-expanded=\"true\">☰</button>\n <div class=\"wp-crumb\" id=\"wp-crumb\">Mode: <b>Runtime</b></div>\n <div class=\"wp-pills\" id=\"wp-filter-pills\" aria-label=\"Active filters\"></div>\n ${parts.filterStatus}\n <button type=\"button\" class=\"wp-icon\" id=\"wp-help-btn\" aria-label=\"How to read this graph\" aria-controls=\"wp-help\" aria-expanded=\"false\">?</button>\n </div>`;\n }\n\n private overlays(parts: ShellParts): string {\n return `${parts.emptyState}<div class=\"wp-pop wp-legend-popout\" id=\"wp-legend-popout\" hidden>\n <header><span id=\"wp-legend-pop-title\">Legend · Runtime</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-legend-close\" aria-label=\"Close the legend\">✕</button></header>\n ${parts.legend}\n </div>\n ${this.help(parts.runtime)}\n ${this.filterPopover(parts)}\n <div class=\"wp-pop wp-snapshot\" id=\"wp-snapshot\" hidden>${parts.snapshot}</div>\n <aside class=\"wp-panel\" id=\"wp-resp-panel\" aria-label=\"Responsibilities\" hidden>\n <header><span>Responsibilities</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-resp-close\" aria-label=\"Close responsibilities\">✕</button></header>\n ${parts.responsibilities}\n </aside>`;\n }\n\n /** The two hint paragraphs that used to sit above the graph, plus the keyboard shortcuts. */\n private help(runtime: boolean): string {\n if (runtime) return `<div class=\"wp-pop wp-help\" id=\"wp-help\" role=\"dialog\" aria-label=\"How to read this graph\" hidden>\n <header><span>Reading the runtime graph</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-help-close\" aria-label=\"Close help\">✕</button></header>\n <p>Rows preserve saved runtime call-depth levels. Boxes are services; horizontal cylinders are queues, upright cylinders datastores, and clocks scheduled triggers. Solid arrows are RPC calls, dashed arrows queued events; red arrows admit legacy cycles.</p>\n <p>Click a box for Lock, Filter Unconnected, or Implements. Implements in the box is a plain summary. Click Uses on a relationship line for its complete details. Hover traces chains without opening details.</p>\n <p>Lock pins separate incoming and outgoing chains; hover adds another chain temporarily. Hide unconnected removes unrelated boxes independently. Clear all clears facets, leaving Lock and chain filtering intact.</p>\n <p>Filters intersect declared runtime, role, product closure and Nx build scope. Counts describe services; attached contextual nodes are counted separately. Impact uses compile-project sets, never runtime reachability. Queues and vendors are context, never source owners or CI projects.</p>\n <p>Runtime uses declared framework colors, Architecture declared roles; contextual nodes keep their kind colors. Product colors include inherited membership. Unknown metadata is neutral. Missing saved metadata requires explicit architecture:generate.</p>\n <p><kbd>/</kbd> Focus search · <kbd>1</kbd>–<kbd>4</kbd> modes · <kbd>Esc</kbd> dismiss or unlock</p>\n </div>`;\n return `<div class=\"wp-pop wp-help\" id=\"wp-help\" role=\"dialog\" aria-label=\"How to read this graph\" hidden>\n <header><span>Reading the graph</span><button type=\"button\" class=\"wp-icon wp-icon-mini\" id=\"wp-help-close\" aria-label=\"Close help\">✕</button></header>\n <p>Every row is one dependency level: the HIGHEST level is the top row and L0, the foundation libraries, is always the bottom row. Servers, clients and apps always form the top row (a bundle sits above the apps it aggregates). Transitive dependencies are allowed but not drawn.</p>\n <p>💡 <strong>Click any box</strong> for its menu: <strong>View Design</strong> (only where the project has a generated <strong>design.html</strong>, i.e. what the AI sees inside it), <strong>Lock/Unlock</strong> (the same lock as the Focus field), <strong>Filter Unconnected</strong> (removes unrelated boxes, keeping each L-number row).</p>\n <p><strong>Filter</strong> narrows the graph by what changed on this branch or in the last commit, by runtime, by role and by product; the groups combine. A product keeps its servers, clients and apps plus everything they depend on, so <strong>Product</strong> + <strong>Changed + dependents + dependencies</strong> is exactly what this branch builds that can affect that product. Every box keeps its own L-row, and a level left empty shows as a thin labeled band.</p>\n <p>🔦 <strong>Hover any box</strong> to trace its <em>entire</em> dependency chain — every ancestor above it and every dependency below it, with the lines between — while the rest of the graph dims.</p>\n <p><strong>Runtime</strong>: a box may depend on a box carrying every color it has. <strong>Architecture</strong>: what each box is in the system. <strong>Impact</strong>: what changed on this branch or in the last commit (amber) and its dependents (amber outline); with both available, a toggle picks which. <strong>Product</strong>: which products each box belongs to — one color per product, stripes for several, grey for every product, a dashed white box for none.</p>\n <p><kbd>/</kbd> focus the lock search · <kbd>1</kbd> <kbd>2</kbd> <kbd>3</kbd> <kbd>4</kbd> switch mode · <kbd>Esc</kbd> unlock</p>\n </div>`;\n }\n\n private brandIcon(): string {\n return (\n '<svg width=\"22\" height=\"22\" viewBox=\"0 0 22 22\" aria-hidden=\"true\">' +\n '<rect x=\"1\" y=\"1\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#a46bf7\" stroke-width=\"1.6\"/>' +\n '<rect x=\"13\" y=\"1\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<rect x=\"7\" y=\"15\" width=\"8\" height=\"6\" rx=\"1.5\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/>' +\n '<path d=\"M5 7v4h12V7M11 11v4\" fill=\"none\" stroke=\"#e9eaf0\" stroke-width=\"1.6\"/></svg>'\n );\n }\n}\n"]}
|
|
@@ -3,9 +3,9 @@ import { LegendFacts, NodeFacts, NodeModeDots } from './graph-color-modes';
|
|
|
3
3
|
/**
|
|
4
4
|
* Renderer-owned records, captured while emitting DOT, never recovered by parsing labels.
|
|
5
5
|
*
|
|
6
|
-
* `dot` is the statement `fullDot` carries. `modes`
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* `dot` is the statement `fullDot` carries. `modes` carries per-mode statements in both viewers; `tags` carries declared service/project
|
|
7
|
+
* metadata. Runtime auxiliary entities leave tags null so facets count services and retain context
|
|
8
|
+
* separately.
|
|
9
9
|
*
|
|
10
10
|
* `products` is set on BOTH graphs (#1179): the products a box belongs to, which each page's product
|
|
11
11
|
* filter matches on. Empty for a box in no product.
|
|
@@ -16,7 +16,8 @@ export declare class RenderNode {
|
|
|
16
16
|
readonly modes: NodeModeDots | null;
|
|
17
17
|
readonly tags: NodeTags | null;
|
|
18
18
|
readonly products: string[];
|
|
19
|
-
|
|
19
|
+
readonly queueKeys: string[];
|
|
20
|
+
constructor(id: string, dot: string, modes: NodeModeDots | null, tags: NodeTags | null, products: string[], queueKeys?: string[]);
|
|
20
21
|
}
|
|
21
22
|
/** What the architecture page's Filter matches a box on: its level, role and framework tags. */
|
|
22
23
|
export declare class NodeTags {
|
|
@@ -34,6 +35,7 @@ export declare class RenderEdge {
|
|
|
34
35
|
}
|
|
35
36
|
/** The immutable drawable snapshot carried by a generated page. */
|
|
36
37
|
export declare class GraphRenderModel {
|
|
38
|
+
viewer: 'architecture' | 'runtime';
|
|
37
39
|
readonly nodes: RenderNode[];
|
|
38
40
|
readonly edges: RenderEdge[];
|
|
39
41
|
bands: LevelBand[];
|
|
@@ -42,7 +44,7 @@ export declare class GraphRenderModel {
|
|
|
42
44
|
footer: string;
|
|
43
45
|
/** The colors and roles in use, so the page's legend lists exactly those (architecture graph). */
|
|
44
46
|
legend: LegendFacts;
|
|
45
|
-
node(id: string, dot: string, products?: string[]): string;
|
|
47
|
+
node(id: string, dot: string, products?: string[], queueKeys?: string[]): string;
|
|
46
48
|
/**
|
|
47
49
|
* Every product a declared box (a queue, a cron trigger, an external system) inherits from the
|
|
48
50
|
* services its edges touch (#1179): it belongs to whichever product's services it is attached to.
|
|
@@ -6,9 +6,9 @@ const graph_color_modes_1 = require("./graph-color-modes");
|
|
|
6
6
|
/**
|
|
7
7
|
* Renderer-owned records, captured while emitting DOT, never recovered by parsing labels.
|
|
8
8
|
*
|
|
9
|
-
* `dot` is the statement `fullDot` carries. `modes`
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* `dot` is the statement `fullDot` carries. `modes` carries per-mode statements in both viewers; `tags` carries declared service/project
|
|
10
|
+
* metadata. Runtime auxiliary entities leave tags null so facets count services and retain context
|
|
11
|
+
* separately.
|
|
12
12
|
*
|
|
13
13
|
* `products` is set on BOTH graphs (#1179): the products a box belongs to, which each page's product
|
|
14
14
|
* filter matches on. Empty for a box in no product.
|
|
@@ -19,12 +19,14 @@ class RenderNode {
|
|
|
19
19
|
modes;
|
|
20
20
|
tags;
|
|
21
21
|
products;
|
|
22
|
-
|
|
22
|
+
queueKeys;
|
|
23
|
+
constructor(id, dot, modes, tags, products, queueKeys = []) {
|
|
23
24
|
this.id = id;
|
|
24
25
|
this.dot = dot;
|
|
25
26
|
this.modes = modes;
|
|
26
27
|
this.tags = tags;
|
|
27
28
|
this.products = products;
|
|
29
|
+
this.queueKeys = queueKeys;
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
exports.RenderNode = RenderNode;
|
|
@@ -55,6 +57,7 @@ class RenderEdge {
|
|
|
55
57
|
exports.RenderEdge = RenderEdge;
|
|
56
58
|
/** The immutable drawable snapshot carried by a generated page. */
|
|
57
59
|
class GraphRenderModel {
|
|
60
|
+
viewer = 'architecture';
|
|
58
61
|
nodes = [];
|
|
59
62
|
edges = [];
|
|
60
63
|
bands = [];
|
|
@@ -63,8 +66,8 @@ class GraphRenderModel {
|
|
|
63
66
|
footer = '';
|
|
64
67
|
/** The colors and roles in use, so the page's legend lists exactly those (architecture graph). */
|
|
65
68
|
legend = new graph_color_modes_1.LegendFacts();
|
|
66
|
-
node(id, dot, products = []) {
|
|
67
|
-
this.nodes.push(new RenderNode(id, dot, null, null, products));
|
|
69
|
+
node(id, dot, products = [], queueKeys = []) {
|
|
70
|
+
this.nodes.push(new RenderNode(id, dot, null, null, products, queueKeys));
|
|
68
71
|
return dot;
|
|
69
72
|
}
|
|
70
73
|
/**
|
|
@@ -91,7 +94,7 @@ class GraphRenderModel {
|
|
|
91
94
|
}
|
|
92
95
|
if (inherited.size === 0)
|
|
93
96
|
continue;
|
|
94
|
-
this.nodes[index] = new RenderNode(node.id, node.dot, node.modes, node.tags, [...inherited].sort());
|
|
97
|
+
this.nodes[index] = new RenderNode(node.id, node.dot, node.modes, node.tags, [...inherited].sort(), node.queueKeys);
|
|
95
98
|
}
|
|
96
99
|
}
|
|
97
100
|
/** A box drawn differently per color mode; `fullDot` carries its RUNTIME statement. */
|
|
@@ -131,7 +134,7 @@ class GraphFilterAssets {
|
|
|
131
134
|
styles() {
|
|
132
135
|
return ('#wp-filter-status { text-align:center; } #wp-graph-error { color:#b71c1c; } ' +
|
|
133
136
|
'#graph .wp-filter-anchor polygon, #graph .wp-filter-anchor path, ' +
|
|
134
|
-
'#graph .wp-filter-anchor ellipse { stroke:#
|
|
137
|
+
'#graph .wp-filter-anchor ellipse { stroke:#8b3cf0; stroke-width:3; }');
|
|
135
138
|
}
|
|
136
139
|
}
|
|
137
140
|
exports.GraphFilterAssets = GraphFilterAssets;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-render-model.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-render-model.ts"],"names":[],"mappings":";;;AAAA,6CAAwC;AAExC,2DAA2E;AAE3E;;;;;;;;;GASG;AACH,MAAa,UAAU;IAEC;IACA;IACA;IACA;IACA;
|
|
1
|
+
{"version":3,"file":"graph-render-model.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-render-model.ts"],"names":[],"mappings":"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","sourcesContent":["import { dotValue } from './dot-syntax';\nimport { LevelBand } from './graph-level-bands';\nimport { LegendFacts, NodeFacts, NodeModeDots } from './graph-color-modes';\n\n/**\n * Renderer-owned records, captured while emitting DOT, never recovered by parsing labels.\n *\n * `dot` is the statement `fullDot` carries. `modes` carries per-mode statements in both viewers; `tags` carries declared service/project\n * metadata. Runtime auxiliary entities leave tags null so facets count services and retain context\n * separately.\n *\n * `products` is set on BOTH graphs (#1179): the products a box belongs to, which each page's product\n * filter matches on. Empty for a box in no product.\n */\nexport class RenderNode {\n constructor(\n public readonly id: string,\n public readonly dot: string,\n public readonly modes: NodeModeDots | null,\n public readonly tags: NodeTags | null,\n public readonly products: string[],\n public readonly queueKeys: string[] = [],\n ) {}\n}\n\n/** What the architecture page's Filter matches a box on: its level, role and framework tags. */\nexport class NodeTags {\n constructor(\n public readonly level: number,\n public readonly role: string,\n public readonly frameworks: string[],\n ) {}\n}\n\nexport class RenderEdge {\n constructor(\n public readonly from: string,\n public readonly to: string,\n public readonly dot: string,\n public readonly id: string,\n ) {}\n}\n\n/** The immutable drawable snapshot carried by a generated page. */\nexport class GraphRenderModel {\n viewer: 'architecture' | 'runtime' = 'architecture';\n readonly nodes: RenderNode[] = [];\n readonly edges: RenderEdge[] = [];\n bands: LevelBand[] = [];\n fullDot = '';\n header = '';\n footer = '';\n /** The colors and roles in use, so the page's legend lists exactly those (architecture graph). */\n legend = new LegendFacts();\n\n node(id: string, dot: string, products: string[] = [], queueKeys: string[] = []): string {\n this.nodes.push(new RenderNode(id, dot, null, null, products, queueKeys));\n return dot;\n }\n\n /**\n * Every product a declared box (a queue, a cron trigger, an external system) inherits from the\n * services its edges touch (#1179): it belongs to whichever product's services it is attached to.\n * Called by the runtime graph once every edge is emitted; RenderNode is immutable, so a node gains\n * its products by being replaced.\n */\n attachProducts(isService: (id: string) => boolean): void {\n const byId = new Map<string, RenderNode>();\n for (const node of this.nodes) byId.set(node.id, node);\n for (let index = 0; index < this.nodes.length; index++) {\n const node = this.nodes[index];\n if (isService(node.id)) continue;\n const inherited = new Set<string>();\n for (const edge of this.edges) {\n const other =\n edge.from === node.id ? edge.to : edge.to === node.id ? edge.from : null;\n if (other === null || !isService(other)) continue;\n for (const product of byId.get(other)?.products ?? []) inherited.add(product);\n }\n if (inherited.size === 0) continue;\n this.nodes[index] = new RenderNode(\n node.id,\n node.dot,\n node.modes,\n node.tags,\n [...inherited].sort(),\n node.queueKeys,\n );\n }\n }\n\n /** A box drawn differently per color mode; `fullDot` carries its RUNTIME statement. */\n styledNode(facts: NodeFacts, modes: NodeModeDots): string {\n this.nodes.push(\n new RenderNode(\n facts.nodeId,\n modes.runtime,\n modes,\n new NodeTags(facts.level, facts.role, facts.frameworks),\n facts.products,\n ),\n );\n return modes.runtime;\n }\n\n completeEndpoints(): void {\n const ids = new Set(this.nodes.map((node) => node.id));\n for (const edge of this.edges) {\n for (const id of [edge.from, edge.to]) {\n if (ids.has(id)) continue;\n ids.add(id);\n this.node(id, ` \"${dotValue(id)}\";\\n`);\n }\n }\n }\n\n edge(from: string, to: string, dot: string): string {\n const id = `wp-real-edge-${this.edges.length}`;\n const statement = `${dot.slice(0, -2)} [id=\"${id}\"];\\n`;\n this.edges.push(new RenderEdge(from, to, statement, id));\n return statement;\n }\n}\n\n/** Shared filter chrome and script-safe JSON for pages opened directly from disk. */\nexport class GraphFilterAssets {\n json<T>(value: T): string {\n return JSON.stringify(value).replace(/</g, '\\\\u003c');\n }\n\n html(): string {\n return (\n '<p id=\"wp-filter-status\" hidden>Filtering: <strong id=\"wp-filter-anchor\"></strong> ' +\n '<button id=\"wp-filter-off\" type=\"button\">Turn off Filter</button></p>' +\n '<p id=\"wp-graph-error\" role=\"alert\" hidden></p>'\n );\n }\n\n styles(): string {\n return (\n '#wp-filter-status { text-align:center; } #wp-graph-error { color:#b71c1c; } ' +\n '#graph .wp-filter-anchor polygon, #graph .wp-filter-anchor path, ' +\n '#graph .wp-filter-anchor ellipse { stroke:#8b3cf0; stroke-width:3; }'\n );\n }\n}\n"]}
|
|
@@ -1,49 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The four color modes (graph-color-modes.ts), the viewer's remembered choice, and Impact's data.
|
|
3
|
-
*
|
|
4
|
-
* The mode is deep-linkable (`#runtime`, `#architecture`, `#impact`, `#product`, which win over the
|
|
5
|
-
* remembered one) and remembered per viewer in localStorage. Product is unavailable on a page whose
|
|
6
|
-
* workspace declares no `product:` tag. Storage can be missing or throw (a private window,
|
|
7
|
-
* blocked site data, a file:// page in some browsers), so every access is wrapped and the page works
|
|
8
|
-
* without it.
|
|
9
|
-
*
|
|
10
|
-
* Impact can carry two scans (#1163): "Changed on this branch" (kind `branch`) and "Last commit"
|
|
11
|
-
* (kind `commit`). The ACTIVE one starts as the sidecar's `defaultKind` (or the viewer's remembered
|
|
12
|
-
* choice, when that scan exists and is available) and is what every Impact color, note and Filter
|
|
13
|
-
* set reads.
|
|
14
|
-
*/
|
|
15
|
-
declare class WpModeState {
|
|
16
|
-
private readonly hasProducts;
|
|
17
|
-
static readonly MODES: string[];
|
|
18
|
-
static readonly NAMES: string[];
|
|
19
|
-
static readonly SUBTITLES: string[];
|
|
20
|
-
static readonly STORAGE_KEY = "wp-architecture-graph-mode";
|
|
21
|
-
static readonly KIND_STORAGE_KEY = "wp-architecture-graph-impact-kind";
|
|
22
|
-
readonly scans: ImpactJson[];
|
|
23
|
-
kind: string;
|
|
24
|
-
mode: string;
|
|
25
|
-
/** @param hasProducts some box belongs to a product, so the Product mode has something to show */
|
|
26
|
-
constructor(hasProducts: boolean);
|
|
27
|
-
/** '' when Product can be shown, else why not. */
|
|
28
|
-
productReason(): string;
|
|
29
|
-
/** The active scan; null when the page has no Impact data. */
|
|
30
|
-
get impact(): ImpactJson | null;
|
|
31
|
-
/** '' when Impact can be shown, else the one line the drawer and the Filter popover say instead. */
|
|
32
|
-
impactReason(): string;
|
|
33
|
-
/** Switch the active scan; false (and nothing changes) for a missing or unavailable one. */
|
|
34
|
-
setKind(kind: string): boolean;
|
|
35
|
-
kindAvailable(kind: string): boolean;
|
|
36
|
-
private initialKind;
|
|
37
|
-
available(mode: string): boolean;
|
|
38
|
-
name(mode: string): string;
|
|
39
|
-
subtitle(mode: string): string;
|
|
40
|
-
/** False (and nothing changes) for an unknown or unavailable mode. */
|
|
41
|
-
set(mode: string): boolean;
|
|
42
|
-
/** Which impact shade a box gets, from the ACTIVE scan. */
|
|
43
|
-
status(id: string): 'touched' | 'affected' | 'buildInput' | 'untouched';
|
|
44
|
-
private initial;
|
|
45
|
-
private read;
|
|
46
|
-
}
|
|
47
1
|
/**
|
|
48
2
|
* One state of the Filter popover: a change scope plus the selected Runtime, Role and Product chips.
|
|
49
3
|
* The page holds the APPLIED one; the popover edits a copy until "Show N projects".
|
|
@@ -74,6 +28,7 @@ declare class WpProjectFilter {
|
|
|
74
28
|
scopeCount(scope: string): number;
|
|
75
29
|
/** `base` (everything, or the Hide-unconnected chain) narrowed by `choice`. */
|
|
76
30
|
apply(base: Set<string>, choice: WpFilterChoice): Set<string>;
|
|
31
|
+
isService(node: RenderNodeJson): boolean;
|
|
77
32
|
/** An empty chip group matches every box; otherwise the box must carry one of the chips. */
|
|
78
33
|
private anyOf;
|
|
79
34
|
}
|
|
@@ -117,6 +72,7 @@ declare class GraphFilterPopover {
|
|
|
117
72
|
declare class GraphDrawer {
|
|
118
73
|
private readonly page;
|
|
119
74
|
readonly popover: GraphFilterPopover;
|
|
75
|
+
private readonly openers;
|
|
120
76
|
constructor(page: GraphPage);
|
|
121
77
|
wire(): void;
|
|
122
78
|
/** "Color by": the pill trigger opens the menu of four; picking one switches and closes it. */
|
|
@@ -136,6 +92,8 @@ declare class GraphDrawer {
|
|
|
136
92
|
private onKey;
|
|
137
93
|
/** `button` shows/hides `target`; for the drawer itself it toggles the collapsed class instead. */
|
|
138
94
|
private toggles;
|
|
95
|
+
private dismissOverlay;
|
|
96
|
+
private dismissOutside;
|
|
139
97
|
/** Repaint every control from the page's state. */
|
|
140
98
|
sync(modes: WpModeState, locked: string | null, filtering: boolean, filter: WpFilterChoice): void;
|
|
141
99
|
private syncFilterToggle;
|
|
@@ -160,6 +118,7 @@ declare class GraphDrawer {
|
|
|
160
118
|
}
|
|
161
119
|
declare class GraphPage extends WpFilterPage {
|
|
162
120
|
private highlighter;
|
|
121
|
+
private selected;
|
|
163
122
|
private readonly modes;
|
|
164
123
|
private readonly projects;
|
|
165
124
|
private applied;
|
|
@@ -171,13 +130,18 @@ declare class GraphPage extends WpFilterPage {
|
|
|
171
130
|
protected narrow(retained: Set<string>): Set<string>;
|
|
172
131
|
/** Filtering keeps every box in its own L-row: an emptied level stays as a thin labeled band. */
|
|
173
132
|
protected keepsEmptyLevels(): boolean;
|
|
133
|
+
protected prepareSvg(svg: SVGSVGElement): void;
|
|
174
134
|
protected wireSvg(svg: SVGSVGElement): void;
|
|
175
135
|
lockSelection(): string | null;
|
|
176
136
|
hasNode(id: string): boolean;
|
|
137
|
+
noun(): string;
|
|
177
138
|
projectCount(): number;
|
|
139
|
+
focusSelection(): string | null;
|
|
140
|
+
toggleLock(): void;
|
|
178
141
|
/** An exact id, else the one id whose short name (after any scope) is `text`; null if none or several. */
|
|
179
142
|
resolveName(text: string): string | null;
|
|
180
143
|
setLock(name: string | null): void;
|
|
144
|
+
clearSelection(): void;
|
|
181
145
|
setMode(mode: string): void;
|
|
182
146
|
/** Switch the active Impact scan: Impact recolours and an active change-scope filter re-applies. */
|
|
183
147
|
setImpactKind(kind: string): void;
|
|
@@ -193,5 +157,6 @@ declare class GraphPage extends WpFilterPage {
|
|
|
193
157
|
/** A top-bar pill's ✕. */
|
|
194
158
|
removeFilter(group: string, value: string): void;
|
|
195
159
|
private syncControls;
|
|
160
|
+
private syncLegend;
|
|
196
161
|
private filterCards;
|
|
197
162
|
}
|