@webpieces/nx-webpieces-rules 0.4.946 → 0.4.948
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 +20 -11
- package/package.json +7 -7
- package/src/executors/validate-no-file-import-cycles/executor.js +7 -4
- package/src/executors/validate-no-file-import-cycles/executor.js.map +1 -1
- package/src/index.d.ts +1 -1
- package/src/index.js +3 -1
- package/src/index.js.map +1 -1
- package/src/lib/graph-color-modes.d.ts +18 -12
- package/src/lib/graph-color-modes.js +18 -20
- package/src/lib/graph-color-modes.js.map +1 -1
- package/src/lib/graph-legend.d.ts +11 -3
- package/src/lib/graph-legend.js +25 -9
- package/src/lib/graph-legend.js.map +1 -1
- package/src/lib/graph-mode-state.client.d.ts +2 -1
- package/src/lib/graph-mode-state.client.js +3 -1
- package/src/lib/graph-mode-state.client.js.map +1 -1
- package/src/lib/graph-page-shell.js +2 -2
- package/src/lib/graph-page-shell.js.map +1 -1
- package/src/lib/graph-page-styles.js +4 -1
- package/src/lib/graph-page-styles.js.map +1 -1
- package/src/lib/graph-product-paint.client.d.ts +92 -0
- package/src/lib/graph-product-paint.client.js +196 -0
- package/src/lib/graph-product-paint.client.js.map +1 -0
- package/src/lib/graph-products.d.ts +54 -6
- package/src/lib/graph-products.js +62 -9
- package/src/lib/graph-products.js.map +1 -1
- package/src/lib/graph-render-model.d.ts +12 -12
- package/src/lib/graph-render-model.js +12 -14
- package/src/lib/graph-render-model.js.map +1 -1
- package/src/lib/graph-visualizer.client.d.ts +13 -1
- package/src/lib/graph-visualizer.client.js +27 -4
- package/src/lib/graph-visualizer.client.js.map +1 -1
- package/src/lib/graph-visualizer.d.ts +3 -3
- package/src/lib/graph-visualizer.js +13 -12
- package/src/lib/graph-visualizer.js.map +1 -1
- package/src/lib/runtime-goal-comparison.js +1 -1
- package/src/lib/runtime-goal-comparison.js.map +1 -1
- package/src/lib/runtime-html-page.js +3 -2
- package/src/lib/runtime-html-page.js.map +1 -1
- package/src/lib/runtime-layout.js +1 -1
- package/src/lib/runtime-layout.js.map +1 -1
- package/src/lib/runtime-viewer-adapter.d.ts +4 -1
- package/src/lib/runtime-viewer-adapter.js +6 -13
- package/src/lib/runtime-viewer-adapter.js.map +1 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* The Product color mode's paint, decided IN THE BROWSER from the products switched ON (#1319).
|
|
4
|
+
*
|
|
5
|
+
* Each product's legend row is a toggle. Painting every declared product at once gave a workspace with
|
|
6
|
+
* eight products eight saturated colors plus stripes, which hid exactly what the mode is for: one
|
|
7
|
+
* product's footprint, or where two overlap. So the page keeps an ON set — the first two products in
|
|
8
|
+
* legend (sorted-name) order at EVERY load, never persisted — and repaints from it without
|
|
9
|
+
* regenerating anything: the page already carries each node's products and the ONE palette (#1244).
|
|
10
|
+
*
|
|
11
|
+
* Prepended to graph-visualizer.client.js like graph-mode-state.client.ts. The painter is pure (no
|
|
12
|
+
* DOM), so a spec runs it in a bare VM; WpProductToggles is the only class here that touches the page. The colors it writes are graph-products.ts's (a spec pins that they agree);
|
|
13
|
+
* they are spelled here because this script is not a module and cannot import them.
|
|
14
|
+
*/
|
|
15
|
+
/** Which of the five looks a Product-mode node gets for one ON set. */
|
|
16
|
+
var WpProductPaintKind;
|
|
17
|
+
(function (WpProductPaintKind) {
|
|
18
|
+
/** The node belongs to no product: white, dashed border. */
|
|
19
|
+
WpProductPaintKind["NONE"] = "none";
|
|
20
|
+
/** Every product it carries is OFF: light grey, still visible. */
|
|
21
|
+
WpProductPaintKind["OFF"] = "off";
|
|
22
|
+
/** Exactly one of its products is ON: solid in that product's fixed color. */
|
|
23
|
+
WpProductPaintKind["SOLID"] = "solid";
|
|
24
|
+
/** Two or more of its products are ON: one stripe per ON product. */
|
|
25
|
+
WpProductPaintKind["STRIPES"] = "stripes";
|
|
26
|
+
/** Every product is ON and the node carries every one: the neutral shared fill. */
|
|
27
|
+
WpProductPaintKind["SHARED"] = "shared";
|
|
28
|
+
})(WpProductPaintKind || (WpProductPaintKind = {}));
|
|
29
|
+
/** How one node is painted in Product mode. Data-only. */
|
|
30
|
+
class WpProductPaint {
|
|
31
|
+
kind;
|
|
32
|
+
colors;
|
|
33
|
+
fontColor;
|
|
34
|
+
constructor(kind,
|
|
35
|
+
/** SOLID: its one color; STRIPES: one per ON product, in the node's product order; else empty. */
|
|
36
|
+
colors,
|
|
37
|
+
/** The text color that reads on the fill. */
|
|
38
|
+
fontColor) {
|
|
39
|
+
this.kind = kind;
|
|
40
|
+
this.colors = colors;
|
|
41
|
+
this.fontColor = fontColor;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Which products are ON when a page loads (#1319): the same answer on every load, never persisted. */
|
|
45
|
+
class WpProductSelection {
|
|
46
|
+
/** The first two products in legend order. */
|
|
47
|
+
initialOn(order) {
|
|
48
|
+
return new Set(order.slice(0, WpProductPainter.ON_AT_LOAD));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Decides and writes every Product-mode statement for one ON set. Immutable: a toggle builds a new one. */
|
|
52
|
+
class WpProductPainter {
|
|
53
|
+
legend;
|
|
54
|
+
on;
|
|
55
|
+
/** graph-products.ts PRODUCT_SHARED_FILL. */
|
|
56
|
+
static SHARED_FILL = '#c9ced8';
|
|
57
|
+
/** graph-products.ts PRODUCT_NONE_FILL / PRODUCT_NONE_BORDER. */
|
|
58
|
+
static NONE_FILL = '#ffffff';
|
|
59
|
+
static NONE_BORDER = '#8a90a0';
|
|
60
|
+
/** graph-products.ts PRODUCT_OFF_FILL / PRODUCT_OFF_BORDER. */
|
|
61
|
+
static OFF_FILL = '#eef0f4';
|
|
62
|
+
static OFF_BORDER = '#b4b9c4';
|
|
63
|
+
/** graph-color-modes.ts DARK_TEXT / LIGHT_TEXT / STRIPED_CLASS. */
|
|
64
|
+
static DARK_TEXT = '#1a1c22';
|
|
65
|
+
static LIGHT_TEXT = '#ffffff';
|
|
66
|
+
static STRIPED_CLASS = 'wp-striped';
|
|
67
|
+
/** graph-products.ts PRODUCTS_ON_AT_LOAD. */
|
|
68
|
+
static ON_AT_LOAD = 2;
|
|
69
|
+
colors = new Map();
|
|
70
|
+
/**
|
|
71
|
+
* @param legend the products the legend lists, in its order, each with its FIXED palette color —
|
|
72
|
+
* a color is never reassigned to the ON set, so it always means the same product
|
|
73
|
+
* @param on the products switched ON
|
|
74
|
+
*/
|
|
75
|
+
constructor(legend, on) {
|
|
76
|
+
this.legend = legend;
|
|
77
|
+
this.on = on;
|
|
78
|
+
for (const color of legend)
|
|
79
|
+
this.colors.set(color.product, color);
|
|
80
|
+
}
|
|
81
|
+
/** The paint for a node carrying `products`. */
|
|
82
|
+
paint(products) {
|
|
83
|
+
if (products.length === 0)
|
|
84
|
+
return new WpProductPaint(WpProductPaintKind.NONE, [], WpProductPainter.DARK_TEXT);
|
|
85
|
+
const on = products.filter((product) => this.isOn(product));
|
|
86
|
+
if (on.length === 0)
|
|
87
|
+
return new WpProductPaint(WpProductPaintKind.OFF, [], WpProductPainter.DARK_TEXT);
|
|
88
|
+
if (this.allOn() && this.legend.every((color) => products.includes(color.product)))
|
|
89
|
+
return new WpProductPaint(WpProductPaintKind.SHARED, [], WpProductPainter.DARK_TEXT);
|
|
90
|
+
const colors = on.map((product) => this.colors.get(product));
|
|
91
|
+
if (colors.length === 1)
|
|
92
|
+
return new WpProductPaint(WpProductPaintKind.SOLID, [colors[0].color], colors[0].fontColor);
|
|
93
|
+
return new WpProductPaint(WpProductPaintKind.STRIPES, colors.map((color) => color.color), WpProductPainter.DARK_TEXT);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The color a cross-product dependency is dashed in (#1204): the first product it crosses into
|
|
97
|
+
* that is ON; null draws it plain.
|
|
98
|
+
*/
|
|
99
|
+
edgeColor(crossesInto) {
|
|
100
|
+
const into = crossesInto.find((product) => this.isOn(product));
|
|
101
|
+
return into === undefined ? null : this.colors.get(into).color;
|
|
102
|
+
}
|
|
103
|
+
/** A node's Product-mode statement. */
|
|
104
|
+
nodeDot(template, products) {
|
|
105
|
+
const paint = this.paint(products);
|
|
106
|
+
if (template.kind === 'context')
|
|
107
|
+
return this.context(template.head, paint);
|
|
108
|
+
const label = paint.fontColor === WpProductPainter.LIGHT_TEXT ? template.lightLabel : template.darkLabel;
|
|
109
|
+
return `${template.head}${this.attrs(paint)}, label=<${label}>];\n`;
|
|
110
|
+
}
|
|
111
|
+
/** An edge's Product-mode statement: dashed in its ON target product's color, else plain. */
|
|
112
|
+
edgeDot(edge) {
|
|
113
|
+
const color = this.edgeColor(edge.crossesInto);
|
|
114
|
+
if (color === null)
|
|
115
|
+
return edge.dot;
|
|
116
|
+
return `${edge.dot.slice(0, -2)} [style=dashed, color="${color}", penwidth=2];\n`;
|
|
117
|
+
}
|
|
118
|
+
isOn(product) {
|
|
119
|
+
return this.on.has(product) && this.colors.has(product);
|
|
120
|
+
}
|
|
121
|
+
/** Every product is ON — of a legend listing two or more (a lone product is its own color). */
|
|
122
|
+
allOn() {
|
|
123
|
+
return this.legend.length > 1 && this.legend.every((color) => this.on.has(color.product));
|
|
124
|
+
}
|
|
125
|
+
attrs(paint) {
|
|
126
|
+
const dark = WpProductPainter.DARK_TEXT;
|
|
127
|
+
if (paint.kind === WpProductPaintKind.NONE)
|
|
128
|
+
return `style="filled,dashed", fillcolor="${WpProductPainter.NONE_FILL}", fontcolor="${dark}", color="${WpProductPainter.NONE_BORDER}", penwidth=1.5`;
|
|
129
|
+
if (paint.kind === WpProductPaintKind.OFF)
|
|
130
|
+
return `style="filled", fillcolor="${WpProductPainter.OFF_FILL}", fontcolor="${dark}", color="${WpProductPainter.OFF_BORDER}"`;
|
|
131
|
+
if (paint.kind === WpProductPaintKind.SHARED)
|
|
132
|
+
return `style="filled", fillcolor="${WpProductPainter.SHARED_FILL}", fontcolor="${dark}"`;
|
|
133
|
+
if (paint.kind === WpProductPaintKind.SOLID)
|
|
134
|
+
return `style="filled", fillcolor="${paint.colors[0]}", fontcolor="${paint.fontColor}"`;
|
|
135
|
+
return `${this.stripes(paint.colors)}, fontcolor="${dark}"`;
|
|
136
|
+
}
|
|
137
|
+
/** Equal vertical stripes, the last taking the remainder — the multi-runtime box's renderer. */
|
|
138
|
+
stripes(colors) {
|
|
139
|
+
const share = (1 / colors.length).toFixed(3);
|
|
140
|
+
const weighted = colors.map((color, index) => index === colors.length - 1 ? color : `${color};${share}`);
|
|
141
|
+
return `style="striped", fillcolor="${weighted.join(':')}", class="${WpProductPainter.STRIPED_CLASS}"`;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* A queue, clock, datastore or vendor keeps its kind statement; only its fill changes. Graphviz
|
|
145
|
+
* cylinders cannot stripe, so several ON products are a gradient; no product is dashed.
|
|
146
|
+
*/
|
|
147
|
+
context(dot, paint) {
|
|
148
|
+
const recolored = dot.replace(/fillcolor="[^"]*"/, `fillcolor="${this.contextFill(paint)}"`);
|
|
149
|
+
return paint.kind === WpProductPaintKind.NONE ? recolored.replace(/style="[^"]*"/, 'style="filled,dashed"') : recolored;
|
|
150
|
+
}
|
|
151
|
+
contextFill(paint) {
|
|
152
|
+
if (paint.kind === WpProductPaintKind.NONE)
|
|
153
|
+
return WpProductPainter.NONE_FILL;
|
|
154
|
+
if (paint.kind === WpProductPaintKind.OFF)
|
|
155
|
+
return WpProductPainter.OFF_FILL;
|
|
156
|
+
if (paint.kind === WpProductPaintKind.SHARED)
|
|
157
|
+
return WpProductPainter.SHARED_FILL;
|
|
158
|
+
return paint.colors.join(':');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/** The Product legend's checkboxes (#1319): one per product, in the drawer AND the popped-out legend. */
|
|
162
|
+
class WpProductToggles {
|
|
163
|
+
/** The products the legend lists, in its order, once each. */
|
|
164
|
+
products() {
|
|
165
|
+
const order = [];
|
|
166
|
+
this.inputs().forEach((input) => {
|
|
167
|
+
const product = input.dataset['wpProductToggle'] ?? '';
|
|
168
|
+
if (product !== '' && !order.includes(product))
|
|
169
|
+
order.push(product);
|
|
170
|
+
});
|
|
171
|
+
return order;
|
|
172
|
+
}
|
|
173
|
+
/** Each checkbox switches its product ON or OFF. */
|
|
174
|
+
wire(onChange) {
|
|
175
|
+
this.inputs().forEach((input) => {
|
|
176
|
+
input.addEventListener('change', () => onChange(input.dataset['wpProductToggle'] ?? '', input.checked));
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/** Every copy of every checkbox shows the ON set. */
|
|
180
|
+
sync(on) {
|
|
181
|
+
this.inputs().forEach((input) => {
|
|
182
|
+
input.checked = on.has(input.dataset['wpProductToggle'] ?? '');
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/** A painter over the legend's products with their FIXED palette colors, for a snapshot of `on`. */
|
|
186
|
+
painter(palette, on) {
|
|
187
|
+
const legend = this.products()
|
|
188
|
+
.map((product) => palette.find((color) => color.product === product))
|
|
189
|
+
.filter((color) => color !== undefined);
|
|
190
|
+
return new WpProductPainter(legend, new Set(on));
|
|
191
|
+
}
|
|
192
|
+
inputs() {
|
|
193
|
+
return document.querySelectorAll('[data-wp-product-toggle]');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=graph-product-paint.client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-product-paint.client.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-product-paint.client.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The Product color mode's paint, decided IN THE BROWSER from the products switched ON (#1319).\n *\n * Each product's legend row is a toggle. Painting every declared product at once gave a workspace with\n * eight products eight saturated colors plus stripes, which hid exactly what the mode is for: one\n * product's footprint, or where two overlap. So the page keeps an ON set — the first two products in\n * legend (sorted-name) order at EVERY load, never persisted — and repaints from it without\n * regenerating anything: the page already carries each node's products and the ONE palette (#1244).\n *\n * Prepended to graph-visualizer.client.js like graph-mode-state.client.ts. The painter is pure (no\n * DOM), so a spec runs it in a bare VM; WpProductToggles is the only class here that touches the page. The colors it writes are graph-products.ts's (a spec pins that they agree);\n * they are spelled here because this script is not a module and cannot import them.\n */\n\n/** Which of the five looks a Product-mode node gets for one ON set. */\nenum WpProductPaintKind {\n /** The node belongs to no product: white, dashed border. */\n NONE = 'none',\n /** Every product it carries is OFF: light grey, still visible. */\n OFF = 'off',\n /** Exactly one of its products is ON: solid in that product's fixed color. */\n SOLID = 'solid',\n /** Two or more of its products are ON: one stripe per ON product. */\n STRIPES = 'stripes',\n /** Every product is ON and the node carries every one: the neutral shared fill. */\n SHARED = 'shared',\n}\n\n/** How one node is painted in Product mode. Data-only. */\nclass WpProductPaint {\n constructor(\n readonly kind: WpProductPaintKind,\n /** SOLID: its one color; STRIPES: one per ON product, in the node's product order; else empty. */\n readonly colors: string[],\n /** The text color that reads on the fill. */\n readonly fontColor: string,\n ) {}\n}\n\n/** Which products are ON when a page loads (#1319): the same answer on every load, never persisted. */\nclass WpProductSelection {\n /** The first two products in legend order. */\n initialOn(order: string[]): Set<string> {\n return new Set(order.slice(0, WpProductPainter.ON_AT_LOAD));\n }\n}\n\n/** Decides and writes every Product-mode statement for one ON set. Immutable: a toggle builds a new one. */\nclass WpProductPainter {\n /** graph-products.ts PRODUCT_SHARED_FILL. */\n static readonly SHARED_FILL = '#c9ced8';\n /** graph-products.ts PRODUCT_NONE_FILL / PRODUCT_NONE_BORDER. */\n static readonly NONE_FILL = '#ffffff';\n static readonly NONE_BORDER = '#8a90a0';\n /** graph-products.ts PRODUCT_OFF_FILL / PRODUCT_OFF_BORDER. */\n static readonly OFF_FILL = '#eef0f4';\n static readonly OFF_BORDER = '#b4b9c4';\n /** graph-color-modes.ts DARK_TEXT / LIGHT_TEXT / STRIPED_CLASS. */\n static readonly DARK_TEXT = '#1a1c22';\n static readonly LIGHT_TEXT = '#ffffff';\n static readonly STRIPED_CLASS = 'wp-striped';\n /** graph-products.ts PRODUCTS_ON_AT_LOAD. */\n static readonly ON_AT_LOAD = 2;\n\n private readonly colors = new Map<string, ProductColorJson>();\n\n /**\n * @param legend the products the legend lists, in its order, each with its FIXED palette color —\n * a color is never reassigned to the ON set, so it always means the same product\n * @param on the products switched ON\n */\n constructor(\n private readonly legend: ProductColorJson[],\n private readonly on: ReadonlySet<string>,\n ) {\n for (const color of legend) this.colors.set(color.product, color);\n }\n\n /** The paint for a node carrying `products`. */\n paint(products: string[]): WpProductPaint {\n if (products.length === 0) return new WpProductPaint(WpProductPaintKind.NONE, [], WpProductPainter.DARK_TEXT);\n const on = products.filter((product: string): boolean => this.isOn(product));\n if (on.length === 0) return new WpProductPaint(WpProductPaintKind.OFF, [], WpProductPainter.DARK_TEXT);\n if (this.allOn() && this.legend.every((color: ProductColorJson): boolean => products.includes(color.product)))\n return new WpProductPaint(WpProductPaintKind.SHARED, [], WpProductPainter.DARK_TEXT);\n const colors = on.map((product: string): ProductColorJson => this.colors.get(product) as ProductColorJson);\n if (colors.length === 1) return new WpProductPaint(WpProductPaintKind.SOLID, [colors[0].color], colors[0].fontColor);\n return new WpProductPaint(\n WpProductPaintKind.STRIPES,\n colors.map((color: ProductColorJson): string => color.color),\n WpProductPainter.DARK_TEXT,\n );\n }\n\n /**\n * The color a cross-product dependency is dashed in (#1204): the first product it crosses into\n * that is ON; null draws it plain.\n */\n edgeColor(crossesInto: string[]): string | null {\n const into = crossesInto.find((product: string): boolean => this.isOn(product));\n return into === undefined ? null : (this.colors.get(into) as ProductColorJson).color;\n }\n\n /** A node's Product-mode statement. */\n nodeDot(template: ProductTemplateJson, products: string[]): string {\n const paint = this.paint(products);\n if (template.kind === 'context') return this.context(template.head, paint);\n const label = paint.fontColor === WpProductPainter.LIGHT_TEXT ? template.lightLabel : template.darkLabel;\n return `${template.head}${this.attrs(paint)}, label=<${label}>];\\n`;\n }\n\n /** An edge's Product-mode statement: dashed in its ON target product's color, else plain. */\n edgeDot(edge: RenderEdgeJson): string {\n const color = this.edgeColor(edge.crossesInto);\n if (color === null) return edge.dot;\n return `${edge.dot.slice(0, -2)} [style=dashed, color=\"${color}\", penwidth=2];\\n`;\n }\n\n private isOn(product: string): boolean {\n return this.on.has(product) && this.colors.has(product);\n }\n\n /** Every product is ON — of a legend listing two or more (a lone product is its own color). */\n private allOn(): boolean {\n return this.legend.length > 1 && this.legend.every((color: ProductColorJson): boolean => this.on.has(color.product));\n }\n\n private attrs(paint: WpProductPaint): string {\n const dark = WpProductPainter.DARK_TEXT;\n if (paint.kind === WpProductPaintKind.NONE)\n return `style=\"filled,dashed\", fillcolor=\"${WpProductPainter.NONE_FILL}\", fontcolor=\"${dark}\", color=\"${WpProductPainter.NONE_BORDER}\", penwidth=1.5`;\n if (paint.kind === WpProductPaintKind.OFF)\n return `style=\"filled\", fillcolor=\"${WpProductPainter.OFF_FILL}\", fontcolor=\"${dark}\", color=\"${WpProductPainter.OFF_BORDER}\"`;\n if (paint.kind === WpProductPaintKind.SHARED) return `style=\"filled\", fillcolor=\"${WpProductPainter.SHARED_FILL}\", fontcolor=\"${dark}\"`;\n if (paint.kind === WpProductPaintKind.SOLID)\n return `style=\"filled\", fillcolor=\"${paint.colors[0]}\", fontcolor=\"${paint.fontColor}\"`;\n return `${this.stripes(paint.colors)}, fontcolor=\"${dark}\"`;\n }\n\n /** Equal vertical stripes, the last taking the remainder — the multi-runtime box's renderer. */\n private stripes(colors: string[]): string {\n const share = (1 / colors.length).toFixed(3);\n const weighted = colors.map((color: string, index: number): string =>\n index === colors.length - 1 ? color : `${color};${share}`,\n );\n return `style=\"striped\", fillcolor=\"${weighted.join(':')}\", class=\"${WpProductPainter.STRIPED_CLASS}\"`;\n }\n\n /**\n * A queue, clock, datastore or vendor keeps its kind statement; only its fill changes. Graphviz\n * cylinders cannot stripe, so several ON products are a gradient; no product is dashed.\n */\n private context(dot: string, paint: WpProductPaint): string {\n const recolored = dot.replace(/fillcolor=\"[^\"]*\"/, `fillcolor=\"${this.contextFill(paint)}\"`);\n return paint.kind === WpProductPaintKind.NONE ? recolored.replace(/style=\"[^\"]*\"/, 'style=\"filled,dashed\"') : recolored;\n }\n\n private contextFill(paint: WpProductPaint): string {\n if (paint.kind === WpProductPaintKind.NONE) return WpProductPainter.NONE_FILL;\n if (paint.kind === WpProductPaintKind.OFF) return WpProductPainter.OFF_FILL;\n if (paint.kind === WpProductPaintKind.SHARED) return WpProductPainter.SHARED_FILL;\n return paint.colors.join(':');\n }\n}\n\n/** The Product legend's checkboxes (#1319): one per product, in the drawer AND the popped-out legend. */\nclass WpProductToggles {\n /** The products the legend lists, in its order, once each. */\n products(): string[] {\n const order: string[] = [];\n this.inputs().forEach((input: HTMLInputElement) => {\n const product = input.dataset['wpProductToggle'] ?? '';\n if (product !== '' && !order.includes(product)) order.push(product);\n });\n return order;\n }\n\n /** Each checkbox switches its product ON or OFF. */\n wire(onChange: (product: string, on: boolean) => void): void {\n this.inputs().forEach((input: HTMLInputElement) => {\n input.addEventListener('change', () => onChange(input.dataset['wpProductToggle'] ?? '', input.checked));\n });\n }\n\n /** Every copy of every checkbox shows the ON set. */\n sync(on: ReadonlySet<string>): void {\n this.inputs().forEach((input: HTMLInputElement) => {\n input.checked = on.has(input.dataset['wpProductToggle'] ?? '');\n });\n }\n\n /** A painter over the legend's products with their FIXED palette colors, for a snapshot of `on`. */\n painter(palette: ProductColorJson[], on: ReadonlySet<string>): WpProductPainter {\n const legend = this.products()\n .map((product: string): ProductColorJson | undefined => palette.find((color: ProductColorJson): boolean => color.product === product))\n .filter((color: ProductColorJson | undefined): color is ProductColorJson => color !== undefined);\n return new WpProductPainter(legend, new Set(on));\n }\n\n private inputs(): NodeListOf<HTMLInputElement> {\n return document.querySelectorAll<HTMLInputElement>('[data-wp-product-toggle]');\n }\n}\n"]}
|
|
@@ -17,10 +17,14 @@
|
|
|
17
17
|
* between them. A service whose project carries no tag is in no product.
|
|
18
18
|
* - ProductPalette — the Product color mode's colors (#1244): eight fixed saturated colors, each
|
|
19
19
|
* product hashed (SHA-256 of its name) to a preferred one, with collisions resolved in sorted-name
|
|
20
|
-
* order. Both viewers build it over the FULL declared product set, so they agree
|
|
21
|
-
*
|
|
20
|
+
* order. Both viewers build it over the FULL declared product set, so they agree, and a color is
|
|
21
|
+
* never reassigned when a product is toggled OFF.
|
|
22
|
+
* - ProductTemplate — what a page needs to paint one box in the Product color mode (#1319). Each
|
|
23
|
+
* product's legend row is a toggle and the page repaints in the browser from the ON set
|
|
24
|
+
* (WpProductPainter, graph-product-paint.client.ts), so the box's product statement is computed
|
|
25
|
+
* there, not here.
|
|
22
26
|
* - ProductBoundary — which dependency edges cross from one product into another (#1204), drawn
|
|
23
|
-
* dashed in the target product's color in the Product color mode.
|
|
27
|
+
* dashed in the target product's color in the Product color mode while that product is ON.
|
|
24
28
|
*
|
|
25
29
|
* Membership is stable per checkout (it depends on tags only, never on a diff), so it is written into
|
|
26
30
|
* the committed dependencies.json / runtime-dependencies.json rather than into a sidecar.
|
|
@@ -42,11 +46,50 @@ export declare class ProductPaletteSlot {
|
|
|
42
46
|
}
|
|
43
47
|
/** The eight product colors, indexed by slot. Distinct from every runtime and role color. */
|
|
44
48
|
export declare const PRODUCT_PALETTE_SLOTS: readonly ProductPaletteSlot[];
|
|
45
|
-
/** A box shared by EVERY product. */
|
|
49
|
+
/** A box shared by EVERY product, drawn neutral only while every product is ON (#1319). */
|
|
46
50
|
export declare const PRODUCT_SHARED_FILL = "#c9ced8";
|
|
47
51
|
/** A box in no product: white, with a dashed border. */
|
|
48
52
|
export declare const PRODUCT_NONE_FILL = "#ffffff";
|
|
49
53
|
export declare const PRODUCT_NONE_BORDER = "#8a90a0";
|
|
54
|
+
/**
|
|
55
|
+
* A box carrying only products switched OFF (#1319): light grey with a grey border, so it stays
|
|
56
|
+
* visible — hiding boxes is the Filter's job — and never reads as the neutral shared fill.
|
|
57
|
+
*/
|
|
58
|
+
export declare const PRODUCT_OFF_FILL = "#eef0f4";
|
|
59
|
+
export declare const PRODUCT_OFF_BORDER = "#b4b9c4";
|
|
60
|
+
/**
|
|
61
|
+
* How many products are ON when a page loads (#1319): the first ones in legend (sorted-name) order.
|
|
62
|
+
* Always this many, on every load — the selection is never persisted.
|
|
63
|
+
*/
|
|
64
|
+
export declare const PRODUCTS_ON_AT_LOAD = 2;
|
|
65
|
+
/** Whether a ProductTemplate paints a styled box or restyles a contextual node's kind statement. */
|
|
66
|
+
export declare enum ProductTemplateKind {
|
|
67
|
+
/** A project or service box: the page writes its whole statement. */
|
|
68
|
+
BOX = "box",
|
|
69
|
+
/** A runtime queue, clock, datastore or vendor: the page recolors its kind statement's fill. */
|
|
70
|
+
CONTEXT = "context"
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* What the page needs to write one node's Product-mode statement for any ON set (#1319). Data-only.
|
|
74
|
+
* A BOX statement is `head` + the paint's attributes + the label for its text color; a CONTEXT
|
|
75
|
+
* statement is `head` (the kind statement) with its fill replaced.
|
|
76
|
+
*/
|
|
77
|
+
export declare class ProductTemplate {
|
|
78
|
+
readonly kind: ProductTemplateKind;
|
|
79
|
+
/** BOX: ` "id" [`. CONTEXT: the node's whole kind statement. */
|
|
80
|
+
readonly head: string;
|
|
81
|
+
/** BOX: the HTML label written in dark text; '' for CONTEXT. */
|
|
82
|
+
readonly darkLabel: string;
|
|
83
|
+
/** BOX: the HTML label written in light text; '' for CONTEXT. */
|
|
84
|
+
readonly lightLabel: string;
|
|
85
|
+
constructor(kind: ProductTemplateKind,
|
|
86
|
+
/** BOX: ` "id" [`. CONTEXT: the node's whole kind statement. */
|
|
87
|
+
head: string,
|
|
88
|
+
/** BOX: the HTML label written in dark text; '' for CONTEXT. */
|
|
89
|
+
darkLabel: string,
|
|
90
|
+
/** BOX: the HTML label written in light text; '' for CONTEXT. */
|
|
91
|
+
lightLabel: string);
|
|
92
|
+
}
|
|
50
93
|
export declare class ProductPalette {
|
|
51
94
|
/** Every declared product, sorted, each with its color. */
|
|
52
95
|
readonly colors: ProductColor[];
|
|
@@ -72,13 +115,18 @@ export declare class ProductPalette {
|
|
|
72
115
|
* Where a dependency edge crosses a product boundary (#1204), for the Product color mode: an edge
|
|
73
116
|
* A -> B crosses when B does not carry every product of A, and it is drawn dashed in the color of the
|
|
74
117
|
* product it crosses INTO, so the boundaries read at a glance while same-product edges stay quiet.
|
|
118
|
+
* Since #1319 that is the first of those products that is ON; with none ON the edge draws plain.
|
|
75
119
|
* Whether a crossing is ALLOWED is the product dependency matrix's call (product-dependencies.ts):
|
|
76
120
|
* with `product-tags` ON, generation already refused every crossing the matrix does not allow, so
|
|
77
121
|
* every crossing drawn is an allowed one.
|
|
78
122
|
*/
|
|
79
123
|
export declare class ProductBoundary {
|
|
80
|
-
/**
|
|
81
|
-
|
|
124
|
+
/**
|
|
125
|
+
* The products the edge crosses into, in the target's order: the target's products its source
|
|
126
|
+
* lacks, else (a target narrower than its source) every product of the target. Empty when the
|
|
127
|
+
* target carries every product of the source. The page draws the edge in the first one that is ON.
|
|
128
|
+
*/
|
|
129
|
+
crossedInto(from: readonly string[], to: readonly string[]): string[];
|
|
82
130
|
}
|
|
83
131
|
/**
|
|
84
132
|
* The architecture graph's product membership: exactly each project's declared `product:` tags (#1216).
|
|
@@ -18,16 +18,20 @@
|
|
|
18
18
|
* between them. A service whose project carries no tag is in no product.
|
|
19
19
|
* - ProductPalette — the Product color mode's colors (#1244): eight fixed saturated colors, each
|
|
20
20
|
* product hashed (SHA-256 of its name) to a preferred one, with collisions resolved in sorted-name
|
|
21
|
-
* order. Both viewers build it over the FULL declared product set, so they agree
|
|
22
|
-
*
|
|
21
|
+
* order. Both viewers build it over the FULL declared product set, so they agree, and a color is
|
|
22
|
+
* never reassigned when a product is toggled OFF.
|
|
23
|
+
* - ProductTemplate — what a page needs to paint one box in the Product color mode (#1319). Each
|
|
24
|
+
* product's legend row is a toggle and the page repaints in the browser from the ON set
|
|
25
|
+
* (WpProductPainter, graph-product-paint.client.ts), so the box's product statement is computed
|
|
26
|
+
* there, not here.
|
|
23
27
|
* - ProductBoundary — which dependency edges cross from one product into another (#1204), drawn
|
|
24
|
-
* dashed in the target product's color in the Product color mode.
|
|
28
|
+
* dashed in the target product's color in the Product color mode while that product is ON.
|
|
25
29
|
*
|
|
26
30
|
* Membership is stable per checkout (it depends on tags only, never on a diff), so it is written into
|
|
27
31
|
* the committed dependencies.json / runtime-dependencies.json rather than into a sidecar.
|
|
28
32
|
*/
|
|
29
33
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.RuntimeProductMembership = exports.ProductMembership = exports.ProductBoundary = exports.ProductPalette = exports.PRODUCT_NONE_BORDER = exports.PRODUCT_NONE_FILL = exports.PRODUCT_SHARED_FILL = exports.PRODUCT_PALETTE_SLOTS = exports.ProductPaletteSlot = exports.ProductColor = void 0;
|
|
34
|
+
exports.RuntimeProductMembership = exports.ProductMembership = exports.ProductBoundary = exports.ProductPalette = exports.ProductTemplate = exports.ProductTemplateKind = exports.PRODUCTS_ON_AT_LOAD = exports.PRODUCT_OFF_BORDER = exports.PRODUCT_OFF_FILL = exports.PRODUCT_NONE_BORDER = exports.PRODUCT_NONE_FILL = exports.PRODUCT_SHARED_FILL = exports.PRODUCT_PALETTE_SLOTS = exports.ProductPaletteSlot = exports.ProductColor = void 0;
|
|
31
35
|
const node_crypto_1 = require("node:crypto");
|
|
32
36
|
// The same two text colors graph-color-modes.ts writes on every fill. Spelled here rather than
|
|
33
37
|
// imported because graph-color-modes.ts imports THIS file (for the palette), and a file-import cycle
|
|
@@ -67,11 +71,54 @@ exports.PRODUCT_PALETTE_SLOTS = [
|
|
|
67
71
|
new ProductPaletteSlot('#8e5ea2', LIGHT_TEXT),
|
|
68
72
|
new ProductPaletteSlot('#ff9da7', DARK_TEXT),
|
|
69
73
|
];
|
|
70
|
-
/** A box shared by EVERY product. */
|
|
74
|
+
/** A box shared by EVERY product, drawn neutral only while every product is ON (#1319). */
|
|
71
75
|
exports.PRODUCT_SHARED_FILL = '#c9ced8';
|
|
72
76
|
/** A box in no product: white, with a dashed border. */
|
|
73
77
|
exports.PRODUCT_NONE_FILL = '#ffffff';
|
|
74
78
|
exports.PRODUCT_NONE_BORDER = '#8a90a0';
|
|
79
|
+
/**
|
|
80
|
+
* A box carrying only products switched OFF (#1319): light grey with a grey border, so it stays
|
|
81
|
+
* visible — hiding boxes is the Filter's job — and never reads as the neutral shared fill.
|
|
82
|
+
*/
|
|
83
|
+
exports.PRODUCT_OFF_FILL = '#eef0f4';
|
|
84
|
+
exports.PRODUCT_OFF_BORDER = '#b4b9c4';
|
|
85
|
+
/**
|
|
86
|
+
* How many products are ON when a page loads (#1319): the first ones in legend (sorted-name) order.
|
|
87
|
+
* Always this many, on every load — the selection is never persisted.
|
|
88
|
+
*/
|
|
89
|
+
exports.PRODUCTS_ON_AT_LOAD = 2;
|
|
90
|
+
/** Whether a ProductTemplate paints a styled box or restyles a contextual node's kind statement. */
|
|
91
|
+
var ProductTemplateKind;
|
|
92
|
+
(function (ProductTemplateKind) {
|
|
93
|
+
/** A project or service box: the page writes its whole statement. */
|
|
94
|
+
ProductTemplateKind["BOX"] = "box";
|
|
95
|
+
/** A runtime queue, clock, datastore or vendor: the page recolors its kind statement's fill. */
|
|
96
|
+
ProductTemplateKind["CONTEXT"] = "context";
|
|
97
|
+
})(ProductTemplateKind || (exports.ProductTemplateKind = ProductTemplateKind = {}));
|
|
98
|
+
/**
|
|
99
|
+
* What the page needs to write one node's Product-mode statement for any ON set (#1319). Data-only.
|
|
100
|
+
* A BOX statement is `head` + the paint's attributes + the label for its text color; a CONTEXT
|
|
101
|
+
* statement is `head` (the kind statement) with its fill replaced.
|
|
102
|
+
*/
|
|
103
|
+
class ProductTemplate {
|
|
104
|
+
kind;
|
|
105
|
+
head;
|
|
106
|
+
darkLabel;
|
|
107
|
+
lightLabel;
|
|
108
|
+
constructor(kind,
|
|
109
|
+
/** BOX: ` "id" [`. CONTEXT: the node's whole kind statement. */
|
|
110
|
+
head,
|
|
111
|
+
/** BOX: the HTML label written in dark text; '' for CONTEXT. */
|
|
112
|
+
darkLabel,
|
|
113
|
+
/** BOX: the HTML label written in light text; '' for CONTEXT. */
|
|
114
|
+
lightLabel) {
|
|
115
|
+
this.kind = kind;
|
|
116
|
+
this.head = head;
|
|
117
|
+
this.darkLabel = darkLabel;
|
|
118
|
+
this.lightLabel = lightLabel;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.ProductTemplate = ProductTemplate;
|
|
75
122
|
class ProductPalette {
|
|
76
123
|
/** Every declared product, sorted, each with its color. */
|
|
77
124
|
colors;
|
|
@@ -136,18 +183,24 @@ exports.ProductPalette = ProductPalette;
|
|
|
136
183
|
* Where a dependency edge crosses a product boundary (#1204), for the Product color mode: an edge
|
|
137
184
|
* A -> B crosses when B does not carry every product of A, and it is drawn dashed in the color of the
|
|
138
185
|
* product it crosses INTO, so the boundaries read at a glance while same-product edges stay quiet.
|
|
186
|
+
* Since #1319 that is the first of those products that is ON; with none ON the edge draws plain.
|
|
139
187
|
* Whether a crossing is ALLOWED is the product dependency matrix's call (product-dependencies.ts):
|
|
140
188
|
* with `product-tags` ON, generation already refused every crossing the matrix does not allow, so
|
|
141
189
|
* every crossing drawn is an allowed one.
|
|
142
190
|
*/
|
|
143
191
|
class ProductBoundary {
|
|
144
|
-
/**
|
|
192
|
+
/**
|
|
193
|
+
* The products the edge crosses into, in the target's order: the target's products its source
|
|
194
|
+
* lacks, else (a target narrower than its source) every product of the target. Empty when the
|
|
195
|
+
* target carries every product of the source. The page draws the edge in the first one that is ON.
|
|
196
|
+
*/
|
|
145
197
|
crossedInto(from, to) {
|
|
146
198
|
if (from.length === 0 || to.length === 0)
|
|
147
|
-
return
|
|
199
|
+
return [];
|
|
148
200
|
if (from.every((product) => to.includes(product)))
|
|
149
|
-
return
|
|
150
|
-
|
|
201
|
+
return [];
|
|
202
|
+
const added = to.filter((product) => !from.includes(product));
|
|
203
|
+
return added.length > 0 ? added : [...to];
|
|
151
204
|
}
|
|
152
205
|
}
|
|
153
206
|
exports.ProductBoundary = ProductBoundary;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-products.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-products.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Products on the architecture graphs (#1179).\n *\n * A monorepo can hold several PRODUCTS, each with its own clients and servers. A project declares the\n * products it belongs to with the nx tag `product:<name>` (product-resolver.ts). Membership is TAGS\n * ONLY (#1216) on both graphs — nothing is inherited over an edge:\n *\n * - ProductMembership — the dependency graph. A project's products are exactly its own declared\n * `product:` tags. An UNTAGGED project is in no product, however many tagged projects depend on it,\n * and renders as \"no product\" — a box that still needs a tag. A consumer that wants every project\n * assigned lists the library roles in `product-tags.roles`, so an untagged library fails generation\n * instead of quietly sitting outside every product (and outside the product dependency matrix,\n * which skips any edge with an untagged end — product-dependencies.ts).\n * - RuntimeProductMembership — the runtime graph. A service's products are exactly its project's\n * declared `product:` tags. A service is never put in a product because another service calls it or\n * feeds its queue; a call from one product into another product's service shows as the runtime EDGE\n * between them. A service whose project carries no tag is in no product.\n * - ProductPalette — the Product color mode's colors (#1244): eight fixed saturated colors, each\n * product hashed (SHA-256 of its name) to a preferred one, with collisions resolved in sorted-name\n * order. Both viewers build it over the FULL declared product set, so they agree. Neutral means\n * shared by EVERY product; a dashed white box belongs to none.\n * - ProductBoundary — which dependency edges cross from one product into another (#1204), drawn\n * dashed in the target product's color in the Product color mode.\n *\n * Membership is stable per checkout (it depends on tags only, never on a diff), so it is written into\n * the committed dependencies.json / runtime-dependencies.json rather than into a sidecar.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { EnhancedGraph } from './graph-sorter';\nimport type { RuntimeGraph } from './runtime-graph-model';\n\n// The same two text colors graph-color-modes.ts writes on every fill. Spelled here rather than\n// imported because graph-color-modes.ts imports THIS file (for the palette), and a file-import cycle\n// is refused by `no-file-import-cycles`.\nconst DARK_TEXT = '#1a1c22';\nconst LIGHT_TEXT = '#ffffff';\n\n/** One product's color, and the text color that reads on it. Data-only. */\nexport class ProductColor {\n constructor(\n public readonly product: string,\n public readonly color: string,\n public readonly fontColor: string,\n ) {}\n}\n\n/** One of the palette's eight colors, and the text color that reads on it. Data-only. */\nexport class ProductPaletteSlot {\n constructor(\n public readonly color: string,\n public readonly fontColor: string,\n ) {}\n}\n\n/** The eight product colors, indexed by slot. Distinct from every runtime and role color. */\nexport const PRODUCT_PALETTE_SLOTS: readonly ProductPaletteSlot[] = [\n new ProductPaletteSlot('#3f6fb5', LIGHT_TEXT),\n new ProductPaletteSlot('#f28e2b', DARK_TEXT),\n new ProductPaletteSlot('#c0392b', LIGHT_TEXT),\n new ProductPaletteSlot('#5cb8b2', DARK_TEXT),\n new ProductPaletteSlot('#3d8b3d', LIGHT_TEXT),\n new ProductPaletteSlot('#edc948', DARK_TEXT),\n new ProductPaletteSlot('#8e5ea2', LIGHT_TEXT),\n new ProductPaletteSlot('#ff9da7', DARK_TEXT),\n];\n\n/** A box shared by EVERY product. */\nexport const PRODUCT_SHARED_FILL = '#c9ced8';\n/** A box in no product: white, with a dashed border. */\nexport const PRODUCT_NONE_FILL = '#ffffff';\nexport const PRODUCT_NONE_BORDER = '#8a90a0';\n\nexport class ProductPalette {\n /** Every declared product, sorted, each with its color. */\n readonly colors: ProductColor[];\n\n /**\n * @param products the FULL declared product set. Collision resolution depends on the set, so a\n * viewer showing a subset must still pass every declared product, or its colors would\n * disagree with the other viewer's.\n */\n constructor(products: readonly string[]) {\n const sorted = [...new Set(products)].sort();\n const taken = new Set<number>();\n this.colors = sorted.map((product: string): ProductColor => {\n const slot = this.freeSlot(this.preferredSlot(product), taken);\n taken.add(slot);\n return this.paint(product, slot);\n });\n }\n\n /** The slot a product's name hashes to: SHA-256 of the name, first byte, mod the palette size. */\n preferredSlot(product: string): number {\n return createHash('sha256').update(product, 'utf8').digest()[0] % PRODUCT_PALETTE_SLOTS.length;\n }\n\n /** The preferred slot when free, else the next free one (wrapping); with all taken, the preferred one. */\n private freeSlot(preferred: number, taken: ReadonlySet<number>): number {\n for (let step = 0; step < PRODUCT_PALETTE_SLOTS.length; step++) {\n const slot = (preferred + step) % PRODUCT_PALETTE_SLOTS.length;\n if (!taken.has(slot)) return slot;\n }\n return preferred;\n }\n\n private paint(product: string, slot: number): ProductColor {\n const entry = PRODUCT_PALETTE_SLOTS[slot];\n return new ProductColor(product, entry.color, entry.fontColor);\n }\n\n /** Every product carried by a project of the graph, as one palette. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static fromGraph(graph: EnhancedGraph): ProductPalette {\n const all: string[] = [];\n for (const entry of Object.values(graph)) all.push(...(entry.products ?? []));\n return new ProductPalette(all);\n }\n\n names(): string[] {\n return this.colors.map((color: ProductColor): string => color.product);\n }\n\n colorOf(product: string): ProductColor {\n // The assignment contract (#1244): the slot SHA-256 of the name prefers, with collisions\n // resolved in sorted-name order over the whole set — the earlier name keeps a contested slot,\n // the later takes the next free one. So adding a product recolors nobody unless it hashes onto\n // a taken slot AND sorts before its holder. Never assign by sorted index: every addition would\n // shift every later product. A product outside the set gets its preferred slot.\n return (\n this.colors.find((color: ProductColor): boolean => color.product === product) ??\n this.paint(product, this.preferredSlot(product))\n );\n }\n\n /** True for a box carrying every product of a workspace that declares two or more. */\n sharedByAll(products: readonly string[]): boolean {\n return this.colors.length > 1 && this.colors.every((color: ProductColor): boolean => products.includes(color.product));\n }\n}\n\n/**\n * Where a dependency edge crosses a product boundary (#1204), for the Product color mode: an edge\n * A -> B crosses when B does not carry every product of A, and it is drawn dashed in the color of the\n * product it crosses INTO, so the boundaries read at a glance while same-product edges stay quiet.\n * Whether a crossing is ALLOWED is the product dependency matrix's call (product-dependencies.ts):\n * with `product-tags` ON, generation already refused every crossing the matrix does not allow, so\n * every crossing drawn is an allowed one.\n */\nexport class ProductBoundary {\n /** The product the edge crosses into, or null when the target carries every product of the source. */\n crossedInto(from: readonly string[], to: readonly string[]): string | null {\n if (from.length === 0 || to.length === 0) return null;\n if (from.every((product: string): boolean => to.includes(product))) return null;\n return to.find((product: string): boolean => !from.includes(product)) ?? to[0];\n }\n}\n\n/**\n * The architecture graph's product membership: exactly each project's declared `product:` tags (#1216).\n */\nexport class ProductMembership {\n /**\n * @param seeds project → the products it DECLARES (its `product:` tags)\n * @returns project → its sorted declared products. An untagged project is absent — it is in no\n * product, however many tagged projects depend on it — so with no tag anywhere the map is\n * empty.\n */\n compute(seeds: ReadonlyMap<string, readonly string[]>, graph: EnhancedGraph): Map<string, string[]> {\n const result = new Map<string, string[]>();\n seeds.forEach((products: readonly string[], project: string): void => {\n if (products.length > 0 && graph[project] !== undefined) result.set(project, [...new Set(products)].sort());\n });\n return result;\n }\n}\n\n/**\n * The runtime graph's product membership: exactly each service's project's declared `product:` tags\n * (#1216). Never derived from who calls a service or feeds its queue.\n */\nexport class RuntimeProductMembership {\n /**\n * Stamp `products` onto every service of `graph` whose project declares any. `projects` is the\n * dependency graph, whose `products` are exactly each project's declared tags (ProductMembership);\n * a service whose project declares none gets no field.\n */\n apply(graph: RuntimeGraph, projects: EnhancedGraph): void {\n for (const [name, service] of Object.entries(graph.services)) {\n const declared = projects[name]?.products ?? [];\n if (declared.length > 0) service.products = [...declared].sort();\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"graph-products.js","sourceRoot":"","sources":["../../../../../../packages/tooling/nx-webpieces-rules/src/lib/graph-products.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;;AAEH,6CAAyC;AAIzC,+FAA+F;AAC/F,qGAAqG;AACrG,yCAAyC;AACzC,MAAM,SAAS,GAAG,SAAS,CAAC;AAC5B,MAAM,UAAU,GAAG,SAAS,CAAC;AAE7B,2EAA2E;AAC3E,MAAa,YAAY;IAED;IACA;IACA;IAHpB,YACoB,OAAe,EACf,KAAa,EACb,SAAiB;QAFjB,YAAO,GAAP,OAAO,CAAQ;QACf,UAAK,GAAL,KAAK,CAAQ;QACb,cAAS,GAAT,SAAS,CAAQ;IAClC,CAAC;CACP;AAND,oCAMC;AAED,yFAAyF;AACzF,MAAa,kBAAkB;IAEP;IACA;IAFpB,YACoB,KAAa,EACb,SAAiB;QADjB,UAAK,GAAL,KAAK,CAAQ;QACb,cAAS,GAAT,SAAS,CAAQ;IAClC,CAAC;CACP;AALD,gDAKC;AAED,6FAA6F;AAChF,QAAA,qBAAqB,GAAkC;IAChE,IAAI,kBAAkB,CAAC,SAAS,EAAE,UAAU,CAAC;IAC7C,IAAI,kBAAkB,CAAC,SAAS,EAAE,SAAS,CAAC;IAC5C,IAAI,kBAAkB,CAAC,SAAS,EAAE,UAAU,CAAC;IAC7C,IAAI,kBAAkB,CAAC,SAAS,EAAE,SAAS,CAAC;IAC5C,IAAI,kBAAkB,CAAC,SAAS,EAAE,UAAU,CAAC;IAC7C,IAAI,kBAAkB,CAAC,SAAS,EAAE,SAAS,CAAC;IAC5C,IAAI,kBAAkB,CAAC,SAAS,EAAE,UAAU,CAAC;IAC7C,IAAI,kBAAkB,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/C,CAAC;AAEF,2FAA2F;AAC9E,QAAA,mBAAmB,GAAG,SAAS,CAAC;AAC7C,wDAAwD;AAC3C,QAAA,iBAAiB,GAAG,SAAS,CAAC;AAC9B,QAAA,mBAAmB,GAAG,SAAS,CAAC;AAC7C;;;GAGG;AACU,QAAA,gBAAgB,GAAG,SAAS,CAAC;AAC7B,QAAA,kBAAkB,GAAG,SAAS,CAAC;AAE5C;;;GAGG;AACU,QAAA,mBAAmB,GAAG,CAAC,CAAC;AAErC,oGAAoG;AACpG,IAAY,mBAKX;AALD,WAAY,mBAAmB;IAC3B,qEAAqE;IACrE,kCAAW,CAAA;IACX,gGAAgG;IAChG,0CAAmB,CAAA;AACvB,CAAC,EALW,mBAAmB,mCAAnB,mBAAmB,QAK9B;AAED;;;;GAIG;AACH,MAAa,eAAe;IAEJ;IAEA;IAEA;IAEA;IAPpB,YACoB,IAAyB;IACzC,iEAAiE;IACjD,IAAY;IAC5B,gEAAgE;IAChD,SAAiB;IACjC,iEAAiE;IACjD,UAAkB;QANlB,SAAI,GAAJ,IAAI,CAAqB;QAEzB,SAAI,GAAJ,IAAI,CAAQ;QAEZ,cAAS,GAAT,SAAS,CAAQ;QAEjB,eAAU,GAAV,UAAU,CAAQ;IACnC,CAAC;CACP;AAVD,0CAUC;AAED,MAAa,cAAc;IACvB,2DAA2D;IAClD,MAAM,CAAiB;IAEhC;;;;OAIG;IACH,YAAY,QAA2B;QACnC,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,OAAe,EAAgB,EAAE;YACvD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;YAC/D,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAChB,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACP,CAAC;IAED,kGAAkG;IAClG,aAAa,CAAC,OAAe;QACzB,OAAO,IAAA,wBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,6BAAqB,CAAC,MAAM,CAAC;IACnG,CAAC;IAED,0GAA0G;IAClG,QAAQ,CAAC,SAAiB,EAAE,KAA0B;QAC1D,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,6BAAqB,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;YAC7D,MAAM,IAAI,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,6BAAqB,CAAC,MAAM,CAAC;YAC/D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC;QACtC,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;IAEO,KAAK,CAAC,OAAe,EAAE,IAAY;QACvC,MAAM,KAAK,GAAG,6BAAqB,CAAC,IAAI,CAAC,CAAC;QAC1C,OAAO,IAAI,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACnE,CAAC;IAED,uEAAuE;IACvE,8EAA8E;IAC9E,MAAM,CAAC,SAAS,CAAC,KAAoB;QACjC,MAAM,GAAG,GAAa,EAAE,CAAC;QACzB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC;QAC9E,OAAO,IAAI,cAAc,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IAED,KAAK;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAmB,EAAU,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3E,CAAC;IAED,OAAO,CAAC,OAAe;QACnB,yFAAyF;QACzF,8FAA8F;QAC9F,+FAA+F;QAC/F,+FAA+F;QAC/F,gFAAgF;QAChF,OAAO,CACH,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAmB,EAAW,EAAE,CAAC,KAAK,CAAC,OAAO,KAAK,OAAO,CAAC;YAC7E,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CACnD,CAAC;IACN,CAAC;IAED,sFAAsF;IACtF,WAAW,CAAC,QAA2B;QACnC,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAmB,EAAW,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3H,CAAC;CACJ;AAlED,wCAkEC;AAED;;;;;;;;GAQG;AACH,MAAa,eAAe;IACxB;;;;OAIG;IACH,WAAW,CAAC,IAAuB,EAAE,EAAqB;QACtD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACpD,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,OAAe,EAAW,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAAE,OAAO,EAAE,CAAC;QAC9E,MAAM,KAAK,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,OAAe,EAAW,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QAC/E,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IAC9C,CAAC;CACJ;AAZD,0CAYC;AAED;;GAEG;AACH,MAAa,iBAAiB;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,KAA6C,EAAE,KAAoB;QACvE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;QAC3C,KAAK,CAAC,OAAO,CAAC,CAAC,QAA2B,EAAE,OAAe,EAAQ,EAAE;YACjE,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,SAAS;gBAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAChH,CAAC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAClB,CAAC;CACJ;AAdD,8CAcC;AAED;;;GAGG;AACH,MAAa,wBAAwB;IACjC;;;;OAIG;IACH,KAAK,CAAC,KAAmB,EAAE,QAAuB;QAC9C,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3D,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,IAAI,EAAE,CAAC;YAChD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,CAAC,QAAQ,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACrE,CAAC;IACL,CAAC;CACJ;AAZD,4DAYC","sourcesContent":["/**\n * Products on the architecture graphs (#1179).\n *\n * A monorepo can hold several PRODUCTS, each with its own clients and servers. A project declares the\n * products it belongs to with the nx tag `product:<name>` (product-resolver.ts). Membership is TAGS\n * ONLY (#1216) on both graphs — nothing is inherited over an edge:\n *\n * - ProductMembership — the dependency graph. A project's products are exactly its own declared\n * `product:` tags. An UNTAGGED project is in no product, however many tagged projects depend on it,\n * and renders as \"no product\" — a box that still needs a tag. A consumer that wants every project\n * assigned lists the library roles in `product-tags.roles`, so an untagged library fails generation\n * instead of quietly sitting outside every product (and outside the product dependency matrix,\n * which skips any edge with an untagged end — product-dependencies.ts).\n * - RuntimeProductMembership — the runtime graph. A service's products are exactly its project's\n * declared `product:` tags. A service is never put in a product because another service calls it or\n * feeds its queue; a call from one product into another product's service shows as the runtime EDGE\n * between them. A service whose project carries no tag is in no product.\n * - ProductPalette — the Product color mode's colors (#1244): eight fixed saturated colors, each\n * product hashed (SHA-256 of its name) to a preferred one, with collisions resolved in sorted-name\n * order. Both viewers build it over the FULL declared product set, so they agree, and a color is\n * never reassigned when a product is toggled OFF.\n * - ProductTemplate — what a page needs to paint one box in the Product color mode (#1319). Each\n * product's legend row is a toggle and the page repaints in the browser from the ON set\n * (WpProductPainter, graph-product-paint.client.ts), so the box's product statement is computed\n * there, not here.\n * - ProductBoundary — which dependency edges cross from one product into another (#1204), drawn\n * dashed in the target product's color in the Product color mode while that product is ON.\n *\n * Membership is stable per checkout (it depends on tags only, never on a diff), so it is written into\n * the committed dependencies.json / runtime-dependencies.json rather than into a sidecar.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { EnhancedGraph } from './graph-sorter';\nimport type { RuntimeGraph } from './runtime-graph-model';\n\n// The same two text colors graph-color-modes.ts writes on every fill. Spelled here rather than\n// imported because graph-color-modes.ts imports THIS file (for the palette), and a file-import cycle\n// is refused by `no-file-import-cycles`.\nconst DARK_TEXT = '#1a1c22';\nconst LIGHT_TEXT = '#ffffff';\n\n/** One product's color, and the text color that reads on it. Data-only. */\nexport class ProductColor {\n constructor(\n public readonly product: string,\n public readonly color: string,\n public readonly fontColor: string,\n ) {}\n}\n\n/** One of the palette's eight colors, and the text color that reads on it. Data-only. */\nexport class ProductPaletteSlot {\n constructor(\n public readonly color: string,\n public readonly fontColor: string,\n ) {}\n}\n\n/** The eight product colors, indexed by slot. Distinct from every runtime and role color. */\nexport const PRODUCT_PALETTE_SLOTS: readonly ProductPaletteSlot[] = [\n new ProductPaletteSlot('#3f6fb5', LIGHT_TEXT),\n new ProductPaletteSlot('#f28e2b', DARK_TEXT),\n new ProductPaletteSlot('#c0392b', LIGHT_TEXT),\n new ProductPaletteSlot('#5cb8b2', DARK_TEXT),\n new ProductPaletteSlot('#3d8b3d', LIGHT_TEXT),\n new ProductPaletteSlot('#edc948', DARK_TEXT),\n new ProductPaletteSlot('#8e5ea2', LIGHT_TEXT),\n new ProductPaletteSlot('#ff9da7', DARK_TEXT),\n];\n\n/** A box shared by EVERY product, drawn neutral only while every product is ON (#1319). */\nexport const PRODUCT_SHARED_FILL = '#c9ced8';\n/** A box in no product: white, with a dashed border. */\nexport const PRODUCT_NONE_FILL = '#ffffff';\nexport const PRODUCT_NONE_BORDER = '#8a90a0';\n/**\n * A box carrying only products switched OFF (#1319): light grey with a grey border, so it stays\n * visible — hiding boxes is the Filter's job — and never reads as the neutral shared fill.\n */\nexport const PRODUCT_OFF_FILL = '#eef0f4';\nexport const PRODUCT_OFF_BORDER = '#b4b9c4';\n\n/**\n * How many products are ON when a page loads (#1319): the first ones in legend (sorted-name) order.\n * Always this many, on every load — the selection is never persisted.\n */\nexport const PRODUCTS_ON_AT_LOAD = 2;\n\n/** Whether a ProductTemplate paints a styled box or restyles a contextual node's kind statement. */\nexport enum ProductTemplateKind {\n /** A project or service box: the page writes its whole statement. */\n BOX = 'box',\n /** A runtime queue, clock, datastore or vendor: the page recolors its kind statement's fill. */\n CONTEXT = 'context',\n}\n\n/**\n * What the page needs to write one node's Product-mode statement for any ON set (#1319). Data-only.\n * A BOX statement is `head` + the paint's attributes + the label for its text color; a CONTEXT\n * statement is `head` (the kind statement) with its fill replaced.\n */\nexport class ProductTemplate {\n constructor(\n public readonly kind: ProductTemplateKind,\n /** BOX: ` \"id\" [`. CONTEXT: the node's whole kind statement. */\n public readonly head: string,\n /** BOX: the HTML label written in dark text; '' for CONTEXT. */\n public readonly darkLabel: string,\n /** BOX: the HTML label written in light text; '' for CONTEXT. */\n public readonly lightLabel: string,\n ) {}\n}\n\nexport class ProductPalette {\n /** Every declared product, sorted, each with its color. */\n readonly colors: ProductColor[];\n\n /**\n * @param products the FULL declared product set. Collision resolution depends on the set, so a\n * viewer showing a subset must still pass every declared product, or its colors would\n * disagree with the other viewer's.\n */\n constructor(products: readonly string[]) {\n const sorted = [...new Set(products)].sort();\n const taken = new Set<number>();\n this.colors = sorted.map((product: string): ProductColor => {\n const slot = this.freeSlot(this.preferredSlot(product), taken);\n taken.add(slot);\n return this.paint(product, slot);\n });\n }\n\n /** The slot a product's name hashes to: SHA-256 of the name, first byte, mod the palette size. */\n preferredSlot(product: string): number {\n return createHash('sha256').update(product, 'utf8').digest()[0] % PRODUCT_PALETTE_SLOTS.length;\n }\n\n /** The preferred slot when free, else the next free one (wrapping); with all taken, the preferred one. */\n private freeSlot(preferred: number, taken: ReadonlySet<number>): number {\n for (let step = 0; step < PRODUCT_PALETTE_SLOTS.length; step++) {\n const slot = (preferred + step) % PRODUCT_PALETTE_SLOTS.length;\n if (!taken.has(slot)) return slot;\n }\n return preferred;\n }\n\n private paint(product: string, slot: number): ProductColor {\n const entry = PRODUCT_PALETTE_SLOTS[slot];\n return new ProductColor(product, entry.color, entry.fontColor);\n }\n\n /** Every product carried by a project of the graph, as one palette. */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static fromGraph(graph: EnhancedGraph): ProductPalette {\n const all: string[] = [];\n for (const entry of Object.values(graph)) all.push(...(entry.products ?? []));\n return new ProductPalette(all);\n }\n\n names(): string[] {\n return this.colors.map((color: ProductColor): string => color.product);\n }\n\n colorOf(product: string): ProductColor {\n // The assignment contract (#1244): the slot SHA-256 of the name prefers, with collisions\n // resolved in sorted-name order over the whole set — the earlier name keeps a contested slot,\n // the later takes the next free one. So adding a product recolors nobody unless it hashes onto\n // a taken slot AND sorts before its holder. Never assign by sorted index: every addition would\n // shift every later product. A product outside the set gets its preferred slot.\n return (\n this.colors.find((color: ProductColor): boolean => color.product === product) ??\n this.paint(product, this.preferredSlot(product))\n );\n }\n\n /** True for a box carrying every product of a workspace that declares two or more. */\n sharedByAll(products: readonly string[]): boolean {\n return this.colors.length > 1 && this.colors.every((color: ProductColor): boolean => products.includes(color.product));\n }\n}\n\n/**\n * Where a dependency edge crosses a product boundary (#1204), for the Product color mode: an edge\n * A -> B crosses when B does not carry every product of A, and it is drawn dashed in the color of the\n * product it crosses INTO, so the boundaries read at a glance while same-product edges stay quiet.\n * Since #1319 that is the first of those products that is ON; with none ON the edge draws plain.\n * Whether a crossing is ALLOWED is the product dependency matrix's call (product-dependencies.ts):\n * with `product-tags` ON, generation already refused every crossing the matrix does not allow, so\n * every crossing drawn is an allowed one.\n */\nexport class ProductBoundary {\n /**\n * The products the edge crosses into, in the target's order: the target's products its source\n * lacks, else (a target narrower than its source) every product of the target. Empty when the\n * target carries every product of the source. The page draws the edge in the first one that is ON.\n */\n crossedInto(from: readonly string[], to: readonly string[]): string[] {\n if (from.length === 0 || to.length === 0) return [];\n if (from.every((product: string): boolean => to.includes(product))) return [];\n const added = to.filter((product: string): boolean => !from.includes(product));\n return added.length > 0 ? added : [...to];\n }\n}\n\n/**\n * The architecture graph's product membership: exactly each project's declared `product:` tags (#1216).\n */\nexport class ProductMembership {\n /**\n * @param seeds project → the products it DECLARES (its `product:` tags)\n * @returns project → its sorted declared products. An untagged project is absent — it is in no\n * product, however many tagged projects depend on it — so with no tag anywhere the map is\n * empty.\n */\n compute(seeds: ReadonlyMap<string, readonly string[]>, graph: EnhancedGraph): Map<string, string[]> {\n const result = new Map<string, string[]>();\n seeds.forEach((products: readonly string[], project: string): void => {\n if (products.length > 0 && graph[project] !== undefined) result.set(project, [...new Set(products)].sort());\n });\n return result;\n }\n}\n\n/**\n * The runtime graph's product membership: exactly each service's project's declared `product:` tags\n * (#1216). Never derived from who calls a service or feeds its queue.\n */\nexport class RuntimeProductMembership {\n /**\n * Stamp `products` onto every service of `graph` whose project declares any. `projects` is the\n * dependency graph, whose `products` are exactly each project's declared tags (ProductMembership);\n * a service whose project declares none gets no field.\n */\n apply(graph: RuntimeGraph, projects: EnhancedGraph): void {\n for (const [name, service] of Object.entries(graph.services)) {\n const declared = projects[name]?.products ?? [];\n if (declared.length > 0) service.products = [...declared].sort();\n }\n }\n}\n"]}
|
|
@@ -32,18 +32,18 @@ export declare class RenderEdge {
|
|
|
32
32
|
readonly dot: string;
|
|
33
33
|
readonly id: string;
|
|
34
34
|
/**
|
|
35
|
-
* The
|
|
36
|
-
*
|
|
37
|
-
* other edge, which draws `dot` in every mode.
|
|
35
|
+
* The products a dependency crosses into (#1204, ProductBoundary), in the target's order. The
|
|
36
|
+
* page's Product color mode draws the edge dashed in the first one that is ON (#1319); empty
|
|
37
|
+
* for every other edge, which draws `dot` in every mode.
|
|
38
38
|
*/
|
|
39
|
-
readonly
|
|
39
|
+
readonly crossesInto: string[];
|
|
40
40
|
constructor(from: string, to: string, dot: string, id: string,
|
|
41
41
|
/**
|
|
42
|
-
* The
|
|
43
|
-
*
|
|
44
|
-
* other edge, which draws `dot` in every mode.
|
|
42
|
+
* The products a dependency crosses into (#1204, ProductBoundary), in the target's order. The
|
|
43
|
+
* page's Product color mode draws the edge dashed in the first one that is ON (#1319); empty
|
|
44
|
+
* for every other edge, which draws `dot` in every mode.
|
|
45
45
|
*/
|
|
46
|
-
|
|
46
|
+
crossesInto?: string[]);
|
|
47
47
|
}
|
|
48
48
|
/** The immutable drawable snapshot carried by a generated page. */
|
|
49
49
|
export declare class GraphRenderModel {
|
|
@@ -70,11 +70,11 @@ export declare class GraphRenderModel {
|
|
|
70
70
|
styledNode(facts: NodeFacts, modes: NodeModeDots): string;
|
|
71
71
|
completeEndpoints(): void;
|
|
72
72
|
/**
|
|
73
|
-
* One edge. `
|
|
74
|
-
* the
|
|
75
|
-
*
|
|
73
|
+
* One edge. `crossesInto`, when not empty, names the products a cross-product dependency crosses
|
|
74
|
+
* into: the page's Product color mode dashes it in the first ON one; `fullDot` always carries the
|
|
75
|
+
* plain statement.
|
|
76
76
|
*/
|
|
77
|
-
edge(from: string, to: string, dot: string,
|
|
77
|
+
edge(from: string, to: string, dot: string, crossesInto?: string[]): string;
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
80
|
* What a runtime page carries for its Goal vs Current color mode (#1239), serialized into the page
|
|
@@ -47,19 +47,19 @@ class RenderEdge {
|
|
|
47
47
|
to;
|
|
48
48
|
dot;
|
|
49
49
|
id;
|
|
50
|
-
|
|
50
|
+
crossesInto;
|
|
51
51
|
constructor(from, to, dot, id,
|
|
52
52
|
/**
|
|
53
|
-
* The
|
|
54
|
-
*
|
|
55
|
-
* other edge, which draws `dot` in every mode.
|
|
53
|
+
* The products a dependency crosses into (#1204, ProductBoundary), in the target's order. The
|
|
54
|
+
* page's Product color mode draws the edge dashed in the first one that is ON (#1319); empty
|
|
55
|
+
* for every other edge, which draws `dot` in every mode.
|
|
56
56
|
*/
|
|
57
|
-
|
|
57
|
+
crossesInto = []) {
|
|
58
58
|
this.from = from;
|
|
59
59
|
this.to = to;
|
|
60
60
|
this.dot = dot;
|
|
61
61
|
this.id = id;
|
|
62
|
-
this.
|
|
62
|
+
this.crossesInto = crossesInto;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
exports.RenderEdge = RenderEdge;
|
|
@@ -124,16 +124,14 @@ class GraphRenderModel {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
/**
|
|
127
|
-
* One edge. `
|
|
128
|
-
* the
|
|
129
|
-
*
|
|
127
|
+
* One edge. `crossesInto`, when not empty, names the products a cross-product dependency crosses
|
|
128
|
+
* into: the page's Product color mode dashes it in the first ON one; `fullDot` always carries the
|
|
129
|
+
* plain statement.
|
|
130
130
|
*/
|
|
131
|
-
edge(from, to, dot,
|
|
131
|
+
edge(from, to, dot, crossesInto = []) {
|
|
132
132
|
const id = `wp-real-edge-${this.edges.length}`;
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
const productDot = productAttrs === '' ? null : `${base} [${productAttrs}];\n`;
|
|
136
|
-
this.edges.push(new RenderEdge(from, to, statement, id, productDot));
|
|
133
|
+
const statement = `${dot.slice(0, -2)} [id="${id}"];\n`;
|
|
134
|
+
this.edges.push(new RenderEdge(from, to, statement, id, crossesInto));
|
|
137
135
|
return statement;
|
|
138
136
|
}
|
|
139
137
|
}
|