@webpieces/nx-webpieces-rules 0.4.978 → 0.4.979
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 +32 -21
- package/package.json +7 -7
- package/src/lib/graph-filter.client.d.ts +25 -11
- package/src/lib/graph-filter.client.js +65 -29
- package/src/lib/graph-filter.client.js.map +1 -1
- package/src/lib/graph-multi-select.client.d.ts +57 -0
- package/src/lib/graph-multi-select.client.js +222 -0
- package/src/lib/graph-multi-select.client.js.map +1 -0
- package/src/lib/graph-node-menu.d.ts +1 -1
- package/src/lib/graph-node-menu.js +1 -1
- package/src/lib/graph-node-menu.js.map +1 -1
- package/src/lib/graph-page-shell.d.ts +23 -2
- package/src/lib/graph-page-shell.js +63 -13
- package/src/lib/graph-page-shell.js.map +1 -1
- package/src/lib/graph-page-styles.d.ts +6 -0
- package/src/lib/graph-page-styles.js +38 -10
- package/src/lib/graph-page-styles.js.map +1 -1
- package/src/lib/graph-render-model.js +2 -2
- package/src/lib/graph-render-model.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +39 -19
- package/src/lib/graph-visualizer.client.js +96 -110
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/graph-visualizer.d.ts +7 -11
- package/src/lib/graph-visualizer.js +14 -32
- package/src/lib/graph-visualizer.js.map +1 -1
- package/src/lib/runtime-html-page.d.ts +6 -1
- package/src/lib/runtime-html-page.js +13 -6
- package/src/lib/runtime-html-page.js.map +1 -1
- package/src/lib/runtime-visualizer.client.js +3 -1
- package/src/lib/runtime-visualizer.client.js.map +1 -1
- package/src/lib/runtime-visualizer.js +5 -3
- package/src/lib/runtime-visualizer.js.map +1 -1
package/README.md
CHANGED
|
@@ -120,8 +120,9 @@ the goal's derived levels first. Details, comparison policy and the edit workflo
|
|
|
120
120
|
|
|
121
121
|
`architecture/dependencies.html` opens with the graph filling the viewport and every control
|
|
122
122
|
in a collapsible charcoal drawer on the left (violet accent): the **Color by** pulldown, the
|
|
123
|
-
**Filter** button,
|
|
124
|
-
|
|
123
|
+
**Filter** button, two multi-select pulldowns with search, **Keep connected to** and **Focus**
|
|
124
|
+
(`/` opens Focus's search; `Esc` closes an open pulldown, else clears Focus), the legend (with a
|
|
125
|
+
pop-out button) and the edge key. Help is the **?**
|
|
125
126
|
popover, zoom floats bottom-right, and **Responsibilities** opens a right-hand panel from the
|
|
126
127
|
drawer footer.
|
|
127
128
|
|
|
@@ -222,20 +223,30 @@ validator checks.
|
|
|
222
223
|
|
|
223
224
|
### Compact graph filtering
|
|
224
225
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
226
|
+
Both viewers' drawers hold two independent multi-select pulldowns (#1385), one shared client
|
|
227
|
+
component (`graph-multi-select.client.ts`): checkbox rows in alphabetical order, a search box that
|
|
228
|
+
matches anywhere in a name (case-insensitive, the match highlighted), ↑/↓ and Enter (Space while
|
|
229
|
+
the query is empty) to toggle with the query kept for the next pick, the picks as removable chips
|
|
230
|
+
on the closed field (`None (off)` when empty), and an `N selected` footer with **Clear all**.
|
|
231
|
+
|
|
232
|
+
The page applies them in order, each working on what the previous stage left:
|
|
233
|
+
|
|
234
|
+
1. **Filter** (change scope, runtime, role, product).
|
|
235
|
+
2. **Keep connected to** keeps the UNION of every picked box plus its complete transitive incoming
|
|
236
|
+
and outgoing chains, then reruns Graphviz. Adding a pick only ever grows the graph. Architecture
|
|
237
|
+
L-number rows keep their original identities; runtime chains include rendered queues, clocks,
|
|
238
|
+
external APIs and systems. Node menus offer **Keep connected** / **Stop keeping connected** for
|
|
239
|
+
that one box; the top-bar indicator names the picks and clears them all.
|
|
240
|
+
3. **Focus** pins the union of every picked box's chains in the foreground and dims the rest,
|
|
241
|
+
without a relayout. Every focused box keeps the violet outline, and hover adds a temporary chain.
|
|
242
|
+
Focus offers only the boxes drawn after stages 1–2; a focused box a later stage hides stays as a
|
|
243
|
+
chip marked "hidden" and relights when drawn again. Node menus offer **Add to Focus** /
|
|
244
|
+
**Remove from Focus**. Responsibilities follow the drawn set narrowed to the focused chains.
|
|
245
|
+
|
|
246
|
+
Clear all in a pulldown turns that stage off; Clear all in Filter leaves both alone. Hover and
|
|
247
|
+
menu dismissal change neither, and nothing is persisted across reloads. Saved graph facts
|
|
237
248
|
and visibility options remain untouched. See
|
|
238
|
-
[dependency graphs](../../../docs/architecture/dependency-graphs.md#
|
|
249
|
+
[dependency graphs](../../../docs/architecture/dependency-graphs.md#keep-connected-to-and-focus)
|
|
239
250
|
for traversal examples and browser verification.
|
|
240
251
|
|
|
241
252
|
The runtime viewer uses the same drawer, staged Filter popover, legend pop-out, help,
|
|
@@ -247,7 +258,7 @@ are explicitly unknown. Missing project facts disable Impact, leaving the runtim
|
|
|
247
258
|
|
|
248
259
|
Filters combine OR within Runtime/Role/Product groups and AND between groups and change scope.
|
|
249
260
|
Draft changes apply only with **Show N services**; closing cancels them. Clear all resets facets,
|
|
250
|
-
leaving
|
|
261
|
+
leaving Keep connected to and Focus alone. Counts describe services, with contextual nodes reported
|
|
251
262
|
separately. Products preserve saved runtime closure across calls and queues. Attached queues,
|
|
252
263
|
clocks and external systems remain as context without resurrecting excluded services.
|
|
253
264
|
|
|
@@ -264,11 +275,11 @@ light grey for only-OFF membership, white for no product, neutral only while eve
|
|
|
264
275
|
and the node carries them all. Runtime services use the
|
|
265
276
|
established nested/striped framework palette and Architecture uses declared roles.
|
|
266
277
|
|
|
267
|
-
|
|
268
|
-
chains; hover temporarily adds its own chain.
|
|
269
|
-
removes
|
|
270
|
-
|
|
271
|
-
|
|
278
|
+
Focus (the pulldown or a box menu's **Add to Focus**) pins each picked box's complete
|
|
279
|
+
incoming/outgoing chains; hover temporarily adds its own chain. Focus does not change layout.
|
|
280
|
+
Keep connected to removes every node off its picks' chains and compacts the graph while preserving
|
|
281
|
+
original runtime L-identities. Clear all in either pulldown turns it off. A focused box that is
|
|
282
|
+
not drawn is suspended, shown as a "hidden" chip, and restored when visible.
|
|
272
283
|
|
|
273
284
|
**API details are click-only.** Every service shows plain **Implements (N)** summary text.
|
|
274
285
|
Click anywhere in the box for its menu, then **Implements** for full contracts/provenance and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/nx-webpieces-rules",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.979",
|
|
4
4
|
"description": "Nx-specific webpieces validation rules, graph tooling, executors, and inference plugin.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@webpieces/api-doc-model": "0.4.
|
|
24
|
-
"@webpieces/core-util": "0.4.
|
|
25
|
-
"@webpieces/eslint-rules": "0.4.
|
|
26
|
-
"@webpieces/rules-config": "0.4.
|
|
23
|
+
"@webpieces/api-doc-model": "0.4.979",
|
|
24
|
+
"@webpieces/core-util": "0.4.979",
|
|
25
|
+
"@webpieces/eslint-rules": "0.4.979",
|
|
26
|
+
"@webpieces/rules-config": "0.4.979",
|
|
27
27
|
"madge": "8.0.0",
|
|
28
|
-
"@webpieces/rules-sdk": "0.4.
|
|
28
|
+
"@webpieces/rules-sdk": "0.4.979"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"@nx/devkit": ">=18.0.0"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@webpieces/tooling-testkit": "0.4.
|
|
44
|
+
"@webpieces/tooling-testkit": "0.4.979"
|
|
45
45
|
},
|
|
46
46
|
"types": "./src/index.d.ts",
|
|
47
47
|
"bin": {
|
|
@@ -26,20 +26,26 @@ declare class WpFilteredDot {
|
|
|
26
26
|
render(retained: Set<string>, nodeDot?: (node: RenderNodeJson) => string, keepEmptyLevels?: boolean, edgeDot?: (edge: RenderEdgeJson) => string): string;
|
|
27
27
|
private bandDot;
|
|
28
28
|
}
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Page-owned state survives replacements; rendering failures leave the usable SVG intact.
|
|
31
|
+
*
|
|
32
|
+
* Two independent multi-selects (#1385): `keep` ("Keep connected to") decides which boxes are DRAWN —
|
|
33
|
+
* the union of each pick's complete incoming and outgoing chains, so adding a pick never shrinks the
|
|
34
|
+
* graph — and `focused` ("Focus") decides which drawn boxes are LIT, without a relayout.
|
|
35
|
+
*/
|
|
30
36
|
declare abstract class WpFilterPage {
|
|
31
37
|
protected model: RenderModelJson;
|
|
32
38
|
protected chain: WpGraphChain;
|
|
33
|
-
protected
|
|
34
|
-
protected
|
|
39
|
+
protected readonly focused: Set<string>;
|
|
40
|
+
protected keep: ReadonlySet<string>;
|
|
35
41
|
protected retained: Set<string>;
|
|
36
42
|
private viz;
|
|
37
43
|
constructor(model: RenderModelJson);
|
|
38
44
|
/**
|
|
39
45
|
* Swap the drawn topology — the runtime page's Goal vs Current overlay and its own graph (#1239) —
|
|
40
46
|
* so a mode switch genuinely changes which boxes and arrows exist, and switching back leaves none of
|
|
41
|
-
* the overlay's removed-only boxes behind. A
|
|
42
|
-
*
|
|
47
|
+
* the overlay's removed-only boxes behind. A Keep-connected pick the new topology lacks is dropped
|
|
48
|
+
* rather than filtering everything away; a Focus pick survives and relights when its box exists.
|
|
43
49
|
*/
|
|
44
50
|
protected useModel(model: RenderModelJson): void;
|
|
45
51
|
render(): void;
|
|
@@ -54,19 +60,27 @@ declare abstract class WpFilterPage {
|
|
|
54
60
|
/** Whether an unfiltered draw may use the model's precomputed `fullDot` verbatim. */
|
|
55
61
|
protected usesFullDot(): boolean;
|
|
56
62
|
/**
|
|
57
|
-
* The page's own filter on top of "
|
|
63
|
+
* The page's own filter on top of "Keep connected to": the architecture page intersects the set
|
|
58
64
|
* with its Filter popover (changes, runtime, role). Everything, by default.
|
|
59
65
|
*/
|
|
60
66
|
protected narrow(retained: Set<string>): Set<string>;
|
|
61
67
|
/** Whether a level emptied by filtering stays as a thin labeled band (see WpFilteredDot.render). */
|
|
62
68
|
protected keepsEmptyLevels(): boolean;
|
|
69
|
+
/** What "Keep connected to" keeps: the UNION of every pick's complete chains; everything when empty. */
|
|
70
|
+
protected kept(keep: ReadonlySet<string>): Set<string>;
|
|
71
|
+
/** Draw again with the current picks — after a color-mode or filter change. */
|
|
72
|
+
redraw(): void;
|
|
63
73
|
/**
|
|
64
|
-
* Draw
|
|
65
|
-
*
|
|
74
|
+
* Draw with a "Keep connected to" set. A control outside the graph (a drawer button, a pulldown's
|
|
75
|
+
* search box) keeps keyboard focus; the redraw would otherwise move it into the graph.
|
|
66
76
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
protected drawKeeping(keep: ReadonlySet<string>): void;
|
|
78
|
+
keepSelection(): ReadonlySet<string>;
|
|
79
|
+
/** A box's menu item: "Keep connected" adds the box to Keep connected to; the other removes it. */
|
|
80
|
+
keepItem(name: string): WpNodeMenuItem;
|
|
81
|
+
toggleKeep(id: string): void;
|
|
82
|
+
clearKeep(): void;
|
|
83
|
+
draw(keep: ReadonlySet<string>): void;
|
|
70
84
|
private indicator;
|
|
71
85
|
private error;
|
|
72
86
|
}
|
|
@@ -114,12 +114,18 @@ class WpFilteredDot {
|
|
|
114
114
|
return dot;
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* Page-owned state survives replacements; rendering failures leave the usable SVG intact.
|
|
119
|
+
*
|
|
120
|
+
* Two independent multi-selects (#1385): `keep` ("Keep connected to") decides which boxes are DRAWN —
|
|
121
|
+
* the union of each pick's complete incoming and outgoing chains, so adding a pick never shrinks the
|
|
122
|
+
* graph — and `focused` ("Focus") decides which drawn boxes are LIT, without a relayout.
|
|
123
|
+
*/
|
|
118
124
|
class WpFilterPage {
|
|
119
125
|
model;
|
|
120
126
|
chain;
|
|
121
|
-
|
|
122
|
-
|
|
127
|
+
focused = new Set();
|
|
128
|
+
keep = new Set();
|
|
123
129
|
retained;
|
|
124
130
|
viz = null;
|
|
125
131
|
constructor(model) {
|
|
@@ -130,27 +136,27 @@ class WpFilterPage {
|
|
|
130
136
|
/**
|
|
131
137
|
* Swap the drawn topology — the runtime page's Goal vs Current overlay and its own graph (#1239) —
|
|
132
138
|
* so a mode switch genuinely changes which boxes and arrows exist, and switching back leaves none of
|
|
133
|
-
* the overlay's removed-only boxes behind. A
|
|
134
|
-
*
|
|
139
|
+
* the overlay's removed-only boxes behind. A Keep-connected pick the new topology lacks is dropped
|
|
140
|
+
* rather than filtering everything away; a Focus pick survives and relights when its box exists.
|
|
135
141
|
*/
|
|
136
142
|
useModel(model) {
|
|
137
143
|
if (model === this.model)
|
|
138
144
|
return;
|
|
139
145
|
this.model = model;
|
|
140
146
|
this.chain = new WpGraphChain(model);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
const ids = new Set(model.nodes.map((node) => node.id));
|
|
148
|
+
this.retained = new Set(ids);
|
|
149
|
+
this.keep = new Set([...this.keep].filter((id) => ids.has(id)));
|
|
144
150
|
}
|
|
145
151
|
render() {
|
|
146
152
|
document
|
|
147
153
|
.getElementById('wp-filter-off')
|
|
148
|
-
?.addEventListener('click', () => this.
|
|
154
|
+
?.addEventListener('click', () => this.draw(new Set()));
|
|
149
155
|
this.wireControls();
|
|
150
156
|
Viz.instance()
|
|
151
157
|
.then((viz) => {
|
|
152
158
|
this.viz = viz;
|
|
153
|
-
this.
|
|
159
|
+
this.draw(new Set());
|
|
154
160
|
})
|
|
155
161
|
// webpieces-disable no-any-unknown -- promise rejections may carry any JavaScript value
|
|
156
162
|
.catch((err) => this.error(err instanceof Error ? err : new Error(String(err))));
|
|
@@ -173,7 +179,7 @@ class WpFilterPage {
|
|
|
173
179
|
return true;
|
|
174
180
|
}
|
|
175
181
|
/**
|
|
176
|
-
* The page's own filter on top of "
|
|
182
|
+
* The page's own filter on top of "Keep connected to": the architecture page intersects the set
|
|
177
183
|
* with its Filter popover (changes, runtime, role). Everything, by default.
|
|
178
184
|
*/
|
|
179
185
|
narrow(retained) {
|
|
@@ -183,44 +189,74 @@ class WpFilterPage {
|
|
|
183
189
|
keepsEmptyLevels() {
|
|
184
190
|
return false;
|
|
185
191
|
}
|
|
192
|
+
/** What "Keep connected to" keeps: the UNION of every pick's complete chains; everything when empty. */
|
|
193
|
+
kept(keep) {
|
|
194
|
+
if (keep.size === 0)
|
|
195
|
+
return new Set(this.model.nodes.map((node) => node.id));
|
|
196
|
+
const kept = new Set();
|
|
197
|
+
for (const id of keep) {
|
|
198
|
+
for (const node of this.chain.nodes(id))
|
|
199
|
+
kept.add(node);
|
|
200
|
+
}
|
|
201
|
+
return kept;
|
|
202
|
+
}
|
|
203
|
+
/** Draw again with the current picks — after a color-mode or filter change. */
|
|
204
|
+
redraw() {
|
|
205
|
+
this.drawKeeping(this.keep);
|
|
206
|
+
}
|
|
186
207
|
/**
|
|
187
|
-
* Draw
|
|
188
|
-
*
|
|
208
|
+
* Draw with a "Keep connected to" set. A control outside the graph (a drawer button, a pulldown's
|
|
209
|
+
* search box) keeps keyboard focus; the redraw would otherwise move it into the graph.
|
|
189
210
|
*/
|
|
190
|
-
|
|
211
|
+
drawKeeping(keep) {
|
|
191
212
|
const active = document.activeElement;
|
|
192
213
|
const host = document.getElementById('graph');
|
|
193
214
|
const outside = active instanceof HTMLElement && host !== null && !host.contains(active);
|
|
194
|
-
this.
|
|
215
|
+
this.draw(keep);
|
|
195
216
|
if (outside && active.isConnected)
|
|
196
217
|
active.focus({ preventScroll: true });
|
|
197
218
|
}
|
|
198
|
-
|
|
199
|
-
return
|
|
219
|
+
keepSelection() {
|
|
220
|
+
return this.keep;
|
|
221
|
+
}
|
|
222
|
+
/** A box's menu item: "Keep connected" adds the box to Keep connected to; the other removes it. */
|
|
223
|
+
keepItem(name) {
|
|
224
|
+
const kept = this.keep.has(name);
|
|
225
|
+
return new WpNodeMenuItem(kept ? 'Stop keeping connected' : 'Keep connected', () => this.toggleKeep(name));
|
|
226
|
+
}
|
|
227
|
+
toggleKeep(id) {
|
|
228
|
+
const next = new Set(this.keep);
|
|
229
|
+
if (next.has(id))
|
|
230
|
+
next.delete(id);
|
|
231
|
+
else
|
|
232
|
+
next.add(id);
|
|
233
|
+
this.drawKeeping(next);
|
|
234
|
+
}
|
|
235
|
+
clearKeep() {
|
|
236
|
+
if (this.keep.size > 0)
|
|
237
|
+
this.drawKeeping(new Set());
|
|
200
238
|
}
|
|
201
|
-
|
|
239
|
+
draw(keep) {
|
|
202
240
|
if (this.viz === null)
|
|
203
241
|
return;
|
|
204
242
|
const host = document.getElementById('graph');
|
|
205
243
|
if (host === null)
|
|
206
244
|
return;
|
|
207
|
-
const retained = this.narrow(
|
|
208
|
-
|
|
209
|
-
: this.chain.nodes(anchor));
|
|
210
|
-
const previousAnchor = this.anchor;
|
|
245
|
+
const retained = this.narrow(this.kept(keep));
|
|
246
|
+
const previousKeep = this.keep;
|
|
211
247
|
const previousRetained = this.retained;
|
|
212
248
|
const previousSvg = host.querySelector('svg');
|
|
213
249
|
const focusName = WpNodeMenu.focusedName(previousSvg);
|
|
214
250
|
const rollback = this.captureBinding(previousSvg);
|
|
215
251
|
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- browser transaction restores the previous usable graph on failure
|
|
216
252
|
try {
|
|
217
|
-
const dot =
|
|
253
|
+
const dot = keep.size === 0 && this.usesFullDot()
|
|
218
254
|
? this.model.fullDot
|
|
219
255
|
: new WpFilteredDot(this.model).render(retained, (node) => this.nodeDot(node), this.keepsEmptyLevels(), (edge) => this.edgeDot(edge));
|
|
220
256
|
const svg = this.viz.renderSVGElement(dot);
|
|
221
257
|
this.prepareSvg(svg);
|
|
222
258
|
WpNodeMenu.close();
|
|
223
|
-
this.
|
|
259
|
+
this.keep = keep;
|
|
224
260
|
this.retained = retained;
|
|
225
261
|
host.replaceChildren(svg);
|
|
226
262
|
this.wireSvg(svg);
|
|
@@ -231,7 +267,7 @@ class WpFilterPage {
|
|
|
231
267
|
}
|
|
232
268
|
catch (err) {
|
|
233
269
|
//const error = toError(err);
|
|
234
|
-
this.
|
|
270
|
+
this.keep = previousKeep;
|
|
235
271
|
this.retained = previousRetained;
|
|
236
272
|
if (previousSvg !== null)
|
|
237
273
|
host.replaceChildren(previousSvg);
|
|
@@ -247,10 +283,10 @@ class WpFilterPage {
|
|
|
247
283
|
const status = document.getElementById('wp-filter-status');
|
|
248
284
|
const label = document.getElementById('wp-filter-anchor');
|
|
249
285
|
if (status !== null)
|
|
250
|
-
status.hidden = this.
|
|
286
|
+
status.hidden = this.keep.size === 0;
|
|
251
287
|
if (label !== null)
|
|
252
|
-
label.textContent = this.
|
|
253
|
-
if (this.
|
|
288
|
+
label.textContent = [...this.keep].sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())).join(', ');
|
|
289
|
+
if (this.keep.size > 0)
|
|
254
290
|
document.getElementById('wp-filter-off')?.focus();
|
|
255
291
|
else
|
|
256
292
|
document.querySelector('#graph g.wp-node-clickable')?.focus();
|
|
@@ -263,7 +299,7 @@ class WpFilterPage {
|
|
|
263
299
|
el.textContent =
|
|
264
300
|
err === null
|
|
265
301
|
? ''
|
|
266
|
-
: `Graph rendering failed: ${err.message}. Try
|
|
302
|
+
: `Graph rendering failed: ${err.message}. Try Clear all in Keep connected to, or reload the page.`;
|
|
267
303
|
}
|
|
268
304
|
}
|
|
269
305
|
//# sourceMappingURL=graph-filter.client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-filter.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-filter.client.ts"],"names":[],"mappings":"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","sourcesContent":["/** Plain browser helpers shared by architecture and runtime; no CommonJS imports. */\nclass WpGraphChain {\n readonly inNodes = new Map<string, Set<string>>();\n readonly outNodes = new Map<string, Set<string>>();\n\n constructor(private readonly model: RenderModelJson) {\n const ids = new Set(model.nodes.map((node) => node.id));\n for (const edge of model.edges) {\n if (!ids.has(edge.from) || !ids.has(edge.to)) continue;\n this.add(this.outNodes, edge.from, edge.to);\n this.add(this.inNodes, edge.to, edge.from);\n }\n }\n\n private add(map: Map<string, Set<string>>, from: string, to: string): void {\n if (!map.has(from)) map.set(from, new Set());\n map.get(from)?.add(to);\n }\n\n nodes(anchor: string): Set<string> {\n const retained = new Set<string>();\n if (!this.model.nodes.some((node) => node.id === anchor)) return retained;\n for (const direction of [this.inNodes, this.outNodes]) {\n const visited = new Set([anchor]);\n const stack = [anchor];\n while (stack.length > 0) {\n const id = stack.pop() as string;\n retained.add(id);\n for (const next of direction.get(id) ?? []) {\n if (visited.has(next)) continue;\n visited.add(next);\n stack.push(next);\n }\n }\n }\n return retained;\n }\n}\n\nclass WpRenderBand {\n constructor(\n readonly level: number,\n readonly nodeNames: string[],\n readonly selfQueueNames: string[],\n ) {}\n}\n\n/** Original records plus fresh ranks: removed nodes cannot reappear via a rank/edge. */\nclass WpFilteredDot {\n constructor(private readonly model: RenderModelJson) {}\n\n /**\n * `nodeDot` picks each box's statement — the architecture page passes its current color mode's —\n * and `edgeDot` each edge's (the Product mode dashes a cross-product dependency).\n * `keepEmptyLevels` keeps a level whose boxes were all filtered out as a thin labeled band, so\n * every remaining box stays in its own L-row (the architecture page); otherwise it is dropped.\n */\n render(\n retained: Set<string>,\n nodeDot: (node: RenderNodeJson) => string = (node: RenderNodeJson): string => node.dot,\n keepEmptyLevels = false,\n edgeDot: (edge: RenderEdgeJson) => string = (edge: RenderEdgeJson): string => edge.dot,\n ): string {\n const nodes = this.model.nodes.filter((node: RenderNodeJson): boolean =>\n retained.has(node.id),\n );\n const edges = this.model.edges.filter(\n (edge: RenderEdgeJson): boolean => retained.has(edge.from) && retained.has(edge.to),\n );\n const bands = this.model.bands\n .map(\n (band: RenderBandJson): WpRenderBand =>\n new WpRenderBand(\n band.level,\n band.nodeNames.filter((id: string): boolean => retained.has(id)),\n band.selfQueueNames.filter((id: string): boolean => retained.has(id)),\n ),\n )\n .filter((band: WpRenderBand): boolean => keepEmptyLevels || band.nodeNames.length > 0);\n return (\n this.model.header +\n nodes.map((node: RenderNodeJson): string => nodeDot(node)).join('') +\n this.bandDot(bands) +\n edges.map((edge: RenderEdgeJson): string => edgeDot(edge)).join('') +\n this.model.footer\n );\n }\n\n private bandDot(bands: WpRenderBand[]): string {\n const nodeAttrs =\n '[style=invis, shape=point, width=0.01, height=0.01, label=\"\", class=\"wp-layout\"]';\n const edgeAttrs = '[style=invis, class=\"wp-layout\"]';\n let dot = '';\n for (const band of bands) {\n const anchor = `__wp_layout_L${band.level}`;\n // The same bytes as LevelBandLayout.emptyBandAttrs (graph-level-bands.ts).\n const attrs =\n band.nodeNames.length > 0\n ? nodeAttrs\n : '[shape=plaintext, style=\"\", width=0.01, height=0.15, margin=0, fontsize=9, fontcolor=\"#9a9eab\", ' +\n `label=\"L${band.level} · no matching projects\", class=\"wp-layout wp-empty-level\"]`;\n dot += ` \"${anchor}\" ${attrs};\\n { rank=same; \"${anchor}\"; `;\n dot +=\n band.nodeNames\n .filter((id) => !band.selfQueueNames.includes(id))\n .map((id) => `${JSON.stringify(id)}; `)\n .join('') + '}\\n';\n if (band.selfQueueNames.length > 0) {\n const subrow = `${anchor}_selfqueues`;\n dot += ` \"${subrow}\" ${nodeAttrs};\\n { rank=same; \"${subrow}\"; `;\n dot += band.selfQueueNames.map((id) => `${JSON.stringify(id)}; `).join('') + '}\\n';\n dot += ` \"${anchor}\" -> \"${subrow}\" ${edgeAttrs};\\n`;\n }\n }\n for (let i = 0; i + 1 < bands.length; i++) {\n const upper = bands[i];\n const lower = bands[i + 1];\n const from = `__wp_layout_L${upper.level}${upper.selfQueueNames.length === 0 ? '' : '_selfqueues'}`;\n const to = `__wp_layout_L${lower.level}`;\n if (upper.nodeNames.length <= 10 && lower.nodeNames.length <= 10) {\n dot += ` \"${from}\" -> \"${to}\" ${edgeAttrs};\\n`;\n continue;\n }\n const spacer = `__wp_layout_spacer_L${upper.level}_L${lower.level}`;\n dot += ` \"${spacer}\" ${nodeAttrs};\\n { rank=same; \"${spacer}\"; }\\n`;\n dot += ` \"${from}\" -> \"${spacer}\" ${edgeAttrs};\\n \"${spacer}\" -> \"${to}\" ${edgeAttrs};\\n`;\n }\n return dot;\n }\n}\n\n/** Page-owned state survives replacements; rendering failures leave the usable SVG intact. */\nabstract class WpFilterPage {\n protected chain: WpGraphChain;\n protected locked: string | null = null;\n protected anchor: string | null = null;\n protected retained: Set<string>;\n private viz: VizInstance | null = null;\n\n constructor(protected model: RenderModelJson) {\n this.chain = new WpGraphChain(model);\n this.retained = new Set(model.nodes.map((node) => node.id));\n }\n\n /**\n * Swap the drawn topology — the runtime page's Goal vs Current overlay and its own graph (#1239) —\n * so a mode switch genuinely changes which boxes and arrows exist, and switching back leaves none of\n * the overlay's removed-only boxes behind. A Hide-unconnected anchor the new topology lacks is\n * dropped rather than filtering everything away; a Lock survives and relights when its box exists.\n */\n protected useModel(model: RenderModelJson): void {\n if (model === this.model) return;\n this.model = model;\n this.chain = new WpGraphChain(model);\n this.retained = new Set(model.nodes.map((node: RenderNodeJson): string => node.id));\n if (this.anchor !== null && !model.nodes.some((node: RenderNodeJson): boolean => node.id === this.anchor)) this.anchor = null;\n }\n\n render(): void {\n document\n .getElementById('wp-filter-off')\n ?.addEventListener('click', () => this.filter(null));\n this.wireControls();\n Viz.instance()\n .then((viz) => {\n this.viz = viz;\n this.filter(null);\n })\n // webpieces-disable no-any-unknown -- promise rejections may carry any JavaScript value\n .catch((err: unknown): void =>\n this.error(err instanceof Error ? err : new Error(String(err))),\n );\n }\n\n protected captureBinding(_svg: SVGSVGElement | null): () => void {\n return (): void => {};\n }\n protected wireControls(): void {}\n protected abstract wireSvg(svg: SVGSVGElement): void;\n protected prepareSvg(_svg: SVGSVGElement): void {}\n /** The statement drawn for one box. The architecture page answers with its color mode's. */\n protected nodeDot(node: RenderNodeJson): string {\n return node.dot;\n }\n /** The statement drawn for one edge. The architecture page answers with its color mode's. */\n protected edgeDot(edge: RenderEdgeJson): string {\n return edge.dot;\n }\n /** Whether an unfiltered draw may use the model's precomputed `fullDot` verbatim. */\n protected usesFullDot(): boolean {\n return true;\n }\n /**\n * The page's own filter on top of \"Hide unconnected\": the architecture page intersects the set\n * with its Filter popover (changes, runtime, role). Everything, by default.\n */\n protected narrow(retained: Set<string>): Set<string> {\n return retained;\n }\n /** Whether a level emptied by filtering stays as a thin labeled band (see WpFilteredDot.render). */\n protected keepsEmptyLevels(): boolean {\n return false;\n }\n\n /**\n * Draw again with the current filter — after a color-mode switch. A control outside the graph\n * (a drawer button) keeps keyboard focus; the redraw would otherwise move it into the graph.\n */\n redraw(): void {\n const active = document.activeElement;\n const host = document.getElementById('graph');\n const outside = active instanceof HTMLElement && host !== null && !host.contains(active);\n this.filter(this.anchor);\n if (outside && active.isConnected) active.focus({ preventScroll: true });\n }\n\n filterItem(name: string): WpNodeMenuItem {\n return new WpNodeMenuItem(\n this.anchor === null ? 'Filter Unconnected' : 'Turn off Filter',\n () => this.filter(this.anchor === null ? name : null),\n );\n }\n\n filter(anchor: string | null): void {\n if (this.viz === null) return;\n const host = document.getElementById('graph');\n if (host === null) return;\n const retained = this.narrow(\n anchor === null\n ? new Set(this.model.nodes.map((node: RenderNodeJson): string => node.id))\n : this.chain.nodes(anchor),\n );\n const previousAnchor = this.anchor;\n const previousRetained = this.retained;\n const previousSvg = host.querySelector('svg');\n const focusName = WpNodeMenu.focusedName(previousSvg);\n const rollback = this.captureBinding(previousSvg);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- browser transaction restores the previous usable graph on failure\n try {\n const dot =\n anchor === null && this.usesFullDot()\n ? this.model.fullDot\n : new WpFilteredDot(this.model).render(\n retained,\n (node: RenderNodeJson): string => this.nodeDot(node),\n this.keepsEmptyLevels(),\n (edge: RenderEdgeJson): string => this.edgeDot(edge),\n );\n const svg = this.viz.renderSVGElement(dot);\n this.prepareSvg(svg);\n WpNodeMenu.close();\n this.anchor = anchor;\n this.retained = retained;\n host.replaceChildren(svg);\n this.wireSvg(svg);\n this.indicator();\n WpNodeMenu.restoreFocus(svg, focusName);\n this.error(null);\n // webpieces-disable no-any-unknown -- JavaScript may throw any value at this browser boundary\n } catch (err: unknown) {\n //const error = toError(err);\n this.anchor = previousAnchor;\n this.retained = previousRetained;\n if (previousSvg !== null) host.replaceChildren(previousSvg);\n else host.replaceChildren();\n rollback();\n if (previousSvg !== null) WpNodeMenu.restoreFocus(previousSvg, focusName);\n this.error(err instanceof Error ? err : new Error(String(err)));\n }\n }\n\n private indicator(): void {\n const status = document.getElementById('wp-filter-status');\n const label = document.getElementById('wp-filter-anchor');\n if (status !== null) status.hidden = this.anchor === null;\n if (label !== null) label.textContent = this.anchor;\n if (this.anchor !== null) document.getElementById('wp-filter-off')?.focus();\n else document.querySelector<SVGGElement>('#graph g.wp-node-clickable')?.focus();\n }\n\n private error(err: Error | null): void {\n const el = document.getElementById('wp-graph-error');\n if (el === null) return;\n el.hidden = err === null;\n el.textContent =\n err === null\n ? ''\n : `Graph rendering failed: ${err.message}. Try Turn off Filter or reload the page.`;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph-filter.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-filter.client.ts"],"names":[],"mappings":";AAAA,qFAAqF;AACrF,MAAM,YAAY;IAIe;IAHpB,OAAO,GAAG,IAAI,GAAG,EAAuB,CAAC;IACzC,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;IAEnD,YAA6B,KAAsB;QAAtB,UAAK,GAAL,KAAK,CAAiB;QAC/C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACxD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAC7B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAE,SAAS;YACvD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YAC5C,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/C,CAAC;IACL,CAAC;IAEO,GAAG,CAAC,GAA6B,EAAE,IAAY,EAAE,EAAU;QAC/D,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;QAC7C,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,MAAc;QAChB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC;YAAE,OAAO,QAAQ,CAAC;QAC1E,KAAK,MAAM,SAAS,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;YAClC,MAAM,KAAK,GAAG,CAAC,MAAM,CAAC,CAAC;YACvB,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,EAAY,CAAC;gBACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACjB,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;oBACzC,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;wBAAE,SAAS;oBAChC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBAClB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACrB,CAAC;YACL,CAAC;QACL,CAAC;QACD,OAAO,QAAQ,CAAC;IACpB,CAAC;CACJ;AAED,MAAM,YAAY;IAED;IACA;IACA;IAHb,YACa,KAAa,EACb,SAAmB,EACnB,cAAwB;QAFxB,UAAK,GAAL,KAAK,CAAQ;QACb,cAAS,GAAT,SAAS,CAAU;QACnB,mBAAc,GAAd,cAAc,CAAU;IAClC,CAAC;CACP;AAED,wFAAwF;AACxF,MAAM,aAAa;IACc;IAA7B,YAA6B,KAAsB;QAAtB,UAAK,GAAL,KAAK,CAAiB;IAAG,CAAC;IAEvD;;;;;OAKG;IACH,MAAM,CACF,QAAqB,EACrB,UAA4C,CAAC,IAAoB,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG,EACtF,eAAe,GAAG,KAAK,EACvB,UAA4C,CAAC,IAAoB,EAAU,EAAE,CAAC,IAAI,CAAC,GAAG;QAEtF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAoB,EAAW,EAAE,CACpE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CACxB,CAAC;QACF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CACjC,CAAC,IAAoB,EAAW,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CACtF,CAAC;QACF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK;aACzB,GAAG,CACA,CAAC,IAAoB,EAAgB,EAAE,CACnC,IAAI,YAAY,CACZ,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAU,EAAW,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAChE,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,EAAU,EAAW,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CACxE,CACR;aACA,MAAM,CAAC,CAAC,IAAkB,EAAW,EAAE,CAAC,eAAe,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC3F,OAAO,CACH,IAAI,CAAC,KAAK,CAAC,MAAM;YACjB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAoB,EAAU,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YACnE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;YACnB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAoB,EAAU,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YACnE,IAAI,CAAC,KAAK,CAAC,MAAM,CACpB,CAAC;IACN,CAAC;IAEO,OAAO,CAAC,KAAqB;QACjC,MAAM,SAAS,GACX,kFAAkF,CAAC;QACvF,MAAM,SAAS,GAAG,kCAAkC,CAAC;QACrD,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,gBAAgB,IAAI,CAAC,KAAK,EAAE,CAAC;YAC5C,2EAA2E;YAC3E,MAAM,KAAK,GACP,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;gBACrB,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,kGAAkG;oBAClG,WAAW,IAAI,CAAC,KAAK,6DAA6D,CAAC;YAC7F,GAAG,IAAI,MAAM,MAAM,KAAK,KAAK,sBAAsB,MAAM,KAAK,CAAC;YAC/D,GAAG;gBACC,IAAI,CAAC,SAAS;qBACT,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;qBACjD,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC;qBACtC,IAAI,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC;YAC1B,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACjC,MAAM,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC;gBACtC,GAAG,IAAI,MAAM,MAAM,KAAK,SAAS,sBAAsB,MAAM,KAAK,CAAC;gBACnE,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC;gBACnF,GAAG,IAAI,MAAM,MAAM,SAAS,MAAM,KAAK,SAAS,KAAK,CAAC;YAC1D,CAAC;QACL,CAAC;QACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACvB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3B,MAAM,IAAI,GAAG,gBAAgB,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;YACpG,MAAM,EAAE,GAAG,gBAAgB,KAAK,CAAC,KAAK,EAAE,CAAC;YACzC,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,IAAI,EAAE,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;gBAC/D,GAAG,IAAI,MAAM,IAAI,SAAS,EAAE,KAAK,SAAS,KAAK,CAAC;gBAChD,SAAS;YACb,CAAC;YACD,MAAM,MAAM,GAAG,uBAAuB,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC;YACpE,GAAG,IAAI,MAAM,MAAM,KAAK,SAAS,sBAAsB,MAAM,QAAQ,CAAC;YACtE,GAAG,IAAI,MAAM,IAAI,SAAS,MAAM,KAAK,SAAS,SAAS,MAAM,SAAS,EAAE,KAAK,SAAS,KAAK,CAAC;QAChG,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;CACJ;AAED;;;;;;GAMG;AACH,MAAe,YAAY;IAOD;IANZ,KAAK,CAAe;IACX,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,IAAI,GAAwB,IAAI,GAAG,EAAU,CAAC;IAC9C,QAAQ,CAAc;IACxB,GAAG,GAAuB,IAAI,CAAC;IAEvC,YAAsB,KAAsB;QAAtB,UAAK,GAAL,KAAK,CAAiB;QACxC,IAAI,CAAC,KAAK,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;QACrC,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAChE,CAAC;IAED;;;;;OAKG;IACO,QAAQ,CAAC,KAAsB;QACrC,IAAI,KAAK,KAAK,IAAI,CAAC,KAAK;YAAE,OAAO;QACjC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,KAAK,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAoB,EAAU,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAChF,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,EAAU,EAAW,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACrF,CAAC;IAED,MAAM;QACF,QAAQ;aACH,cAAc,CAAC,eAAe,CAAC;YAChC,EAAE,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,EAAU,CAAC,CAAC,CAAC;QACpE,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,GAAG,CAAC,QAAQ,EAAE;aACT,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YACV,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;YACf,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,EAAU,CAAC,CAAC;QACjC,CAAC,CAAC;YACF,wFAAwF;aACvF,KAAK,CAAC,CAAC,GAAY,EAAQ,EAAE,CAC1B,IAAI,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAClE,CAAC;IACV,CAAC;IAES,cAAc,CAAC,IAA0B;QAC/C,OAAO,GAAS,EAAE,GAAE,CAAC,CAAC;IAC1B,CAAC;IACS,YAAY,KAAU,CAAC;IAEvB,UAAU,CAAC,IAAmB,IAAS,CAAC;IAClD,4FAA4F;IAClF,OAAO,CAAC,IAAoB;QAClC,OAAO,IAAI,CAAC,GAAG,CAAC;IACpB,CAAC;IACD,6FAA6F;IACnF,OAAO,CAAC,IAAoB;QAClC,OAAO,IAAI,CAAC,GAAG,CAAC;IACpB,CAAC;IACD,qFAAqF;IAC3E,WAAW;QACjB,OAAO,IAAI,CAAC;IAChB,CAAC;IACD;;;OAGG;IACO,MAAM,CAAC,QAAqB;QAClC,OAAO,QAAQ,CAAC;IACpB,CAAC;IACD,oGAAoG;IAC1F,gBAAgB;QACtB,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,wGAAwG;IAC9F,IAAI,CAAC,IAAyB;QACpC,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAoB,EAAU,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACrG,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;YACpB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,+EAA+E;IAC/E,MAAM;QACF,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAED;;;OAGG;IACO,WAAW,CAAC,IAAyB;QAC3C,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC;QACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,MAAM,YAAY,WAAW,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzF,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,IAAI,OAAO,IAAI,MAAM,CAAC,WAAW;YAAE,MAAM,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,aAAa;QACT,OAAO,IAAI,CAAC,IAAI,CAAC;IACrB,CAAC;IAED,mGAAmG;IACnG,QAAQ,CAAC,IAAY;QACjB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjC,OAAO,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,gBAAgB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/G,CAAC;IAED,UAAU,CAAC,EAAU;QACjB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;;YAC7B,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED,SAAS;QACL,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC;YAAE,IAAI,CAAC,WAAW,CAAC,IAAI,GAAG,EAAU,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,CAAC,IAAyB;QAC1B,IAAI,IAAI,CAAC,GAAG,KAAK,IAAI;YAAE,OAAO;QAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QAC9C,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO;QAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9C,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC;QAC/B,MAAM,gBAAgB,GAAG,IAAI,CAAC,QAAQ,CAAC;QACvC,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9C,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QAClD,mIAAmI;QACnI,IAAI,CAAC;YACD,MAAM,GAAG,GACL,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE;gBACjC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO;gBACpB,CAAC,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAChC,QAAQ,EACR,CAAC,IAAoB,EAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EACpD,IAAI,CAAC,gBAAgB,EAAE,EACvB,CAAC,IAAoB,EAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CACvD,CAAC;YACZ,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;YAC3C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YACrB,UAAU,CAAC,KAAK,EAAE,CAAC;YACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;YACjB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;YACzB,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;YAC1B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAClB,IAAI,CAAC,SAAS,EAAE,CAAC;YACjB,UAAU,CAAC,YAAY,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YACxC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACjB,8FAA8F;QAClG,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,6BAA6B;YAC7B,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;YACzB,IAAI,CAAC,QAAQ,GAAG,gBAAgB,CAAC;YACjC,IAAI,WAAW,KAAK,IAAI;gBAAE,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;;gBACvD,IAAI,CAAC,eAAe,EAAE,CAAC;YAC5B,QAAQ,EAAE,CAAC;YACX,IAAI,WAAW,KAAK,IAAI;gBAAE,UAAU,CAAC,YAAY,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;YAC1E,IAAI,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACpE,CAAC;IACL,CAAC;IAEO,SAAS;QACb,MAAM,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,QAAQ,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAC;QAC1D,IAAI,MAAM,KAAK,IAAI;YAAE,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC;QAC1D,IAAI,KAAK,KAAK,IAAI;YAAE,KAAK,CAAC,WAAW,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzJ,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC;YAAE,QAAQ,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,CAAC;;YACrE,QAAQ,CAAC,aAAa,CAAc,4BAA4B,CAAC,EAAE,KAAK,EAAE,CAAC;IACpF,CAAC;IAEO,KAAK,CAAC,GAAiB;QAC3B,MAAM,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;QACrD,IAAI,EAAE,KAAK,IAAI;YAAE,OAAO;QACxB,EAAE,CAAC,MAAM,GAAG,GAAG,KAAK,IAAI,CAAC;QACzB,EAAE,CAAC,WAAW;YACV,GAAG,KAAK,IAAI;gBACR,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,2BAA2B,GAAG,CAAC,OAAO,2DAA2D,CAAC;IAChH,CAAC;CACJ","sourcesContent":["/** Plain browser helpers shared by architecture and runtime; no CommonJS imports. */\nclass WpGraphChain {\n readonly inNodes = new Map<string, Set<string>>();\n readonly outNodes = new Map<string, Set<string>>();\n\n constructor(private readonly model: RenderModelJson) {\n const ids = new Set(model.nodes.map((node) => node.id));\n for (const edge of model.edges) {\n if (!ids.has(edge.from) || !ids.has(edge.to)) continue;\n this.add(this.outNodes, edge.from, edge.to);\n this.add(this.inNodes, edge.to, edge.from);\n }\n }\n\n private add(map: Map<string, Set<string>>, from: string, to: string): void {\n if (!map.has(from)) map.set(from, new Set());\n map.get(from)?.add(to);\n }\n\n nodes(anchor: string): Set<string> {\n const retained = new Set<string>();\n if (!this.model.nodes.some((node) => node.id === anchor)) return retained;\n for (const direction of [this.inNodes, this.outNodes]) {\n const visited = new Set([anchor]);\n const stack = [anchor];\n while (stack.length > 0) {\n const id = stack.pop() as string;\n retained.add(id);\n for (const next of direction.get(id) ?? []) {\n if (visited.has(next)) continue;\n visited.add(next);\n stack.push(next);\n }\n }\n }\n return retained;\n }\n}\n\nclass WpRenderBand {\n constructor(\n readonly level: number,\n readonly nodeNames: string[],\n readonly selfQueueNames: string[],\n ) {}\n}\n\n/** Original records plus fresh ranks: removed nodes cannot reappear via a rank/edge. */\nclass WpFilteredDot {\n constructor(private readonly model: RenderModelJson) {}\n\n /**\n * `nodeDot` picks each box's statement — the architecture page passes its current color mode's —\n * and `edgeDot` each edge's (the Product mode dashes a cross-product dependency).\n * `keepEmptyLevels` keeps a level whose boxes were all filtered out as a thin labeled band, so\n * every remaining box stays in its own L-row (the architecture page); otherwise it is dropped.\n */\n render(\n retained: Set<string>,\n nodeDot: (node: RenderNodeJson) => string = (node: RenderNodeJson): string => node.dot,\n keepEmptyLevels = false,\n edgeDot: (edge: RenderEdgeJson) => string = (edge: RenderEdgeJson): string => edge.dot,\n ): string {\n const nodes = this.model.nodes.filter((node: RenderNodeJson): boolean =>\n retained.has(node.id),\n );\n const edges = this.model.edges.filter(\n (edge: RenderEdgeJson): boolean => retained.has(edge.from) && retained.has(edge.to),\n );\n const bands = this.model.bands\n .map(\n (band: RenderBandJson): WpRenderBand =>\n new WpRenderBand(\n band.level,\n band.nodeNames.filter((id: string): boolean => retained.has(id)),\n band.selfQueueNames.filter((id: string): boolean => retained.has(id)),\n ),\n )\n .filter((band: WpRenderBand): boolean => keepEmptyLevels || band.nodeNames.length > 0);\n return (\n this.model.header +\n nodes.map((node: RenderNodeJson): string => nodeDot(node)).join('') +\n this.bandDot(bands) +\n edges.map((edge: RenderEdgeJson): string => edgeDot(edge)).join('') +\n this.model.footer\n );\n }\n\n private bandDot(bands: WpRenderBand[]): string {\n const nodeAttrs =\n '[style=invis, shape=point, width=0.01, height=0.01, label=\"\", class=\"wp-layout\"]';\n const edgeAttrs = '[style=invis, class=\"wp-layout\"]';\n let dot = '';\n for (const band of bands) {\n const anchor = `__wp_layout_L${band.level}`;\n // The same bytes as LevelBandLayout.emptyBandAttrs (graph-level-bands.ts).\n const attrs =\n band.nodeNames.length > 0\n ? nodeAttrs\n : '[shape=plaintext, style=\"\", width=0.01, height=0.15, margin=0, fontsize=9, fontcolor=\"#9a9eab\", ' +\n `label=\"L${band.level} · no matching projects\", class=\"wp-layout wp-empty-level\"]`;\n dot += ` \"${anchor}\" ${attrs};\\n { rank=same; \"${anchor}\"; `;\n dot +=\n band.nodeNames\n .filter((id) => !band.selfQueueNames.includes(id))\n .map((id) => `${JSON.stringify(id)}; `)\n .join('') + '}\\n';\n if (band.selfQueueNames.length > 0) {\n const subrow = `${anchor}_selfqueues`;\n dot += ` \"${subrow}\" ${nodeAttrs};\\n { rank=same; \"${subrow}\"; `;\n dot += band.selfQueueNames.map((id) => `${JSON.stringify(id)}; `).join('') + '}\\n';\n dot += ` \"${anchor}\" -> \"${subrow}\" ${edgeAttrs};\\n`;\n }\n }\n for (let i = 0; i + 1 < bands.length; i++) {\n const upper = bands[i];\n const lower = bands[i + 1];\n const from = `__wp_layout_L${upper.level}${upper.selfQueueNames.length === 0 ? '' : '_selfqueues'}`;\n const to = `__wp_layout_L${lower.level}`;\n if (upper.nodeNames.length <= 10 && lower.nodeNames.length <= 10) {\n dot += ` \"${from}\" -> \"${to}\" ${edgeAttrs};\\n`;\n continue;\n }\n const spacer = `__wp_layout_spacer_L${upper.level}_L${lower.level}`;\n dot += ` \"${spacer}\" ${nodeAttrs};\\n { rank=same; \"${spacer}\"; }\\n`;\n dot += ` \"${from}\" -> \"${spacer}\" ${edgeAttrs};\\n \"${spacer}\" -> \"${to}\" ${edgeAttrs};\\n`;\n }\n return dot;\n }\n}\n\n/**\n * Page-owned state survives replacements; rendering failures leave the usable SVG intact.\n *\n * Two independent multi-selects (#1385): `keep` (\"Keep connected to\") decides which boxes are DRAWN —\n * the union of each pick's complete incoming and outgoing chains, so adding a pick never shrinks the\n * graph — and `focused` (\"Focus\") decides which drawn boxes are LIT, without a relayout.\n */\nabstract class WpFilterPage {\n protected chain: WpGraphChain;\n protected readonly focused = new Set<string>();\n protected keep: ReadonlySet<string> = new Set<string>();\n protected retained: Set<string>;\n private viz: VizInstance | null = null;\n\n constructor(protected model: RenderModelJson) {\n this.chain = new WpGraphChain(model);\n this.retained = new Set(model.nodes.map((node) => node.id));\n }\n\n /**\n * Swap the drawn topology — the runtime page's Goal vs Current overlay and its own graph (#1239) —\n * so a mode switch genuinely changes which boxes and arrows exist, and switching back leaves none of\n * the overlay's removed-only boxes behind. A Keep-connected pick the new topology lacks is dropped\n * rather than filtering everything away; a Focus pick survives and relights when its box exists.\n */\n protected useModel(model: RenderModelJson): void {\n if (model === this.model) return;\n this.model = model;\n this.chain = new WpGraphChain(model);\n const ids = new Set(model.nodes.map((node: RenderNodeJson): string => node.id));\n this.retained = new Set(ids);\n this.keep = new Set([...this.keep].filter((id: string): boolean => ids.has(id)));\n }\n\n render(): void {\n document\n .getElementById('wp-filter-off')\n ?.addEventListener('click', () => this.draw(new Set<string>()));\n this.wireControls();\n Viz.instance()\n .then((viz) => {\n this.viz = viz;\n this.draw(new Set<string>());\n })\n // webpieces-disable no-any-unknown -- promise rejections may carry any JavaScript value\n .catch((err: unknown): void =>\n this.error(err instanceof Error ? err : new Error(String(err))),\n );\n }\n\n protected captureBinding(_svg: SVGSVGElement | null): () => void {\n return (): void => {};\n }\n protected wireControls(): void {}\n protected abstract wireSvg(svg: SVGSVGElement): void;\n protected prepareSvg(_svg: SVGSVGElement): void {}\n /** The statement drawn for one box. The architecture page answers with its color mode's. */\n protected nodeDot(node: RenderNodeJson): string {\n return node.dot;\n }\n /** The statement drawn for one edge. The architecture page answers with its color mode's. */\n protected edgeDot(edge: RenderEdgeJson): string {\n return edge.dot;\n }\n /** Whether an unfiltered draw may use the model's precomputed `fullDot` verbatim. */\n protected usesFullDot(): boolean {\n return true;\n }\n /**\n * The page's own filter on top of \"Keep connected to\": the architecture page intersects the set\n * with its Filter popover (changes, runtime, role). Everything, by default.\n */\n protected narrow(retained: Set<string>): Set<string> {\n return retained;\n }\n /** Whether a level emptied by filtering stays as a thin labeled band (see WpFilteredDot.render). */\n protected keepsEmptyLevels(): boolean {\n return false;\n }\n\n /** What \"Keep connected to\" keeps: the UNION of every pick's complete chains; everything when empty. */\n protected kept(keep: ReadonlySet<string>): Set<string> {\n if (keep.size === 0) return new Set(this.model.nodes.map((node: RenderNodeJson): string => node.id));\n const kept = new Set<string>();\n for (const id of keep) {\n for (const node of this.chain.nodes(id)) kept.add(node);\n }\n return kept;\n }\n\n /** Draw again with the current picks — after a color-mode or filter change. */\n redraw(): void {\n this.drawKeeping(this.keep);\n }\n\n /**\n * Draw with a \"Keep connected to\" set. A control outside the graph (a drawer button, a pulldown's\n * search box) keeps keyboard focus; the redraw would otherwise move it into the graph.\n */\n protected drawKeeping(keep: ReadonlySet<string>): void {\n const active = document.activeElement;\n const host = document.getElementById('graph');\n const outside = active instanceof HTMLElement && host !== null && !host.contains(active);\n this.draw(keep);\n if (outside && active.isConnected) active.focus({ preventScroll: true });\n }\n\n keepSelection(): ReadonlySet<string> {\n return this.keep;\n }\n\n /** A box's menu item: \"Keep connected\" adds the box to Keep connected to; the other removes it. */\n keepItem(name: string): WpNodeMenuItem {\n const kept = this.keep.has(name);\n return new WpNodeMenuItem(kept ? 'Stop keeping connected' : 'Keep connected', () => this.toggleKeep(name));\n }\n\n toggleKeep(id: string): void {\n const next = new Set(this.keep);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n this.drawKeeping(next);\n }\n\n clearKeep(): void {\n if (this.keep.size > 0) this.drawKeeping(new Set<string>());\n }\n\n draw(keep: ReadonlySet<string>): void {\n if (this.viz === null) return;\n const host = document.getElementById('graph');\n if (host === null) return;\n const retained = this.narrow(this.kept(keep));\n const previousKeep = this.keep;\n const previousRetained = this.retained;\n const previousSvg = host.querySelector('svg');\n const focusName = WpNodeMenu.focusedName(previousSvg);\n const rollback = this.captureBinding(previousSvg);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- browser transaction restores the previous usable graph on failure\n try {\n const dot =\n keep.size === 0 && this.usesFullDot()\n ? this.model.fullDot\n : new WpFilteredDot(this.model).render(\n retained,\n (node: RenderNodeJson): string => this.nodeDot(node),\n this.keepsEmptyLevels(),\n (edge: RenderEdgeJson): string => this.edgeDot(edge),\n );\n const svg = this.viz.renderSVGElement(dot);\n this.prepareSvg(svg);\n WpNodeMenu.close();\n this.keep = keep;\n this.retained = retained;\n host.replaceChildren(svg);\n this.wireSvg(svg);\n this.indicator();\n WpNodeMenu.restoreFocus(svg, focusName);\n this.error(null);\n // webpieces-disable no-any-unknown -- JavaScript may throw any value at this browser boundary\n } catch (err: unknown) {\n //const error = toError(err);\n this.keep = previousKeep;\n this.retained = previousRetained;\n if (previousSvg !== null) host.replaceChildren(previousSvg);\n else host.replaceChildren();\n rollback();\n if (previousSvg !== null) WpNodeMenu.restoreFocus(previousSvg, focusName);\n this.error(err instanceof Error ? err : new Error(String(err)));\n }\n }\n\n private indicator(): void {\n const status = document.getElementById('wp-filter-status');\n const label = document.getElementById('wp-filter-anchor');\n if (status !== null) status.hidden = this.keep.size === 0;\n if (label !== null) label.textContent = [...this.keep].sort((a: string, b: string): number => a.toLowerCase().localeCompare(b.toLowerCase())).join(', ');\n if (this.keep.size > 0) document.getElementById('wp-filter-off')?.focus();\n else document.querySelector<SVGGElement>('#graph g.wp-node-clickable')?.focus();\n }\n\n private error(err: Error | null): void {\n const el = document.getElementById('wp-graph-error');\n if (el === null) return;\n el.hidden = err === null;\n el.textContent =\n err === null\n ? ''\n : `Graph rendering failed: ${err.message}. Try Clear all in Keep connected to, or reload the page.`;\n }\n}\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What one multi-select pulldown reads and changes on the page: its picks, which options it may offer
|
|
3
|
+
* right now, and how a toggle or Clear all lands. Behaviour, so an interface.
|
|
4
|
+
*/
|
|
5
|
+
interface WpMultiSource {
|
|
6
|
+
selected(): ReadonlySet<string>;
|
|
7
|
+
/** Whether `id` is drawn right now: an unavailable option is not offered, and its chip says "hidden". */
|
|
8
|
+
available(id: string): boolean;
|
|
9
|
+
toggle(id: string): void;
|
|
10
|
+
clear(): void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* One multi-select pulldown with search (#1385) — ONE implementation, two instances: "Keep connected
|
|
14
|
+
* to" and "Focus". Its markup is graph-page-shell.ts `multiSelect()`; this owns the behaviour:
|
|
15
|
+
* - one checkbox row per option, alphabetical (the shell's order);
|
|
16
|
+
* - the search matches ANYWHERE in the name, case-insensitively, and highlights the match;
|
|
17
|
+
* - ↑/↓ move, Enter toggles (Space too while the query is empty), and the query stays after a toggle
|
|
18
|
+
* so several matches can be picked in a row;
|
|
19
|
+
* - the closed field shows the picks as removable chips, or "None (off)";
|
|
20
|
+
* - the footer says "N selected", and Clear all turns the pulldown off.
|
|
21
|
+
* It holds no picks of its own: every change goes to the page, and `sync()` repaints from it.
|
|
22
|
+
*/
|
|
23
|
+
declare class WpMultiSelect {
|
|
24
|
+
private readonly key;
|
|
25
|
+
private readonly source;
|
|
26
|
+
private activeId;
|
|
27
|
+
constructor(key: string, source: WpMultiSource);
|
|
28
|
+
wire(): void;
|
|
29
|
+
isOpen(): boolean;
|
|
30
|
+
/** Open with the cursor in an EMPTY search box: the query lasts while it is open, not across opens. */
|
|
31
|
+
open(): void;
|
|
32
|
+
/** Close; `returnFocus` puts the keyboard back on the pulldown's own button. */
|
|
33
|
+
close(returnFocus: boolean): void;
|
|
34
|
+
/**
|
|
35
|
+
* Whether a click happened inside this pulldown: an outside click closes it. Read from the event's
|
|
36
|
+
* PATH, not from the target's ancestors — a pick repaints the row it was clicked on, so by the time
|
|
37
|
+
* the click reaches the document its target (the old `<mark>`) is no longer in the page.
|
|
38
|
+
*/
|
|
39
|
+
clicked(ev: Event): boolean;
|
|
40
|
+
/** Repaint the chips, the footer and (while open) the rows from the page's picks. */
|
|
41
|
+
sync(): void;
|
|
42
|
+
private onKey;
|
|
43
|
+
private move;
|
|
44
|
+
/** Show the rows the query and the page allow, highlight the match, and tick the picks. */
|
|
45
|
+
private paint;
|
|
46
|
+
/** The name with its matched part in a `<mark>`, built from text nodes so no name is parsed as HTML. */
|
|
47
|
+
private highlight;
|
|
48
|
+
/** One removable chip per pick, alphabetical; a pick that is not drawn right now says "hidden". */
|
|
49
|
+
private chips;
|
|
50
|
+
private chip;
|
|
51
|
+
private shownRows;
|
|
52
|
+
private rows;
|
|
53
|
+
private idOf;
|
|
54
|
+
private search;
|
|
55
|
+
private pop;
|
|
56
|
+
private byId;
|
|
57
|
+
}
|