@coldtea/pr-lens-renderer 0.1.0
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/LICENSE +21 -0
- package/README.md +91 -0
- package/dist/bounds.d.ts +23 -0
- package/dist/bounds.d.ts.map +1 -0
- package/dist/bounds.js +39 -0
- package/dist/bounds.js.map +1 -0
- package/dist/corrections.d.ts +12 -0
- package/dist/corrections.d.ts.map +1 -0
- package/dist/corrections.js +122 -0
- package/dist/corrections.js.map +1 -0
- package/dist/design.d.ts +112 -0
- package/dist/design.d.ts.map +1 -0
- package/dist/design.js +112 -0
- package/dist/design.js.map +1 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +9 -0
- package/dist/errors.js.map +1 -0
- package/dist/geometry.d.ts +20 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +15 -0
- package/dist/geometry.js.map +1 -0
- package/dist/glob.d.ts +7 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +17 -0
- package/dist/glob.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/architecture.d.ts +78 -0
- package/dist/layout/architecture.d.ts.map +1 -0
- package/dist/layout/architecture.js +207 -0
- package/dist/layout/architecture.js.map +1 -0
- package/dist/layout/congestion.d.ts +18 -0
- package/dist/layout/congestion.d.ts.map +1 -0
- package/dist/layout/congestion.js +50 -0
- package/dist/layout/congestion.js.map +1 -0
- package/dist/layout/dataflow.d.ts +85 -0
- package/dist/layout/dataflow.d.ts.map +1 -0
- package/dist/layout/dataflow.js +184 -0
- package/dist/layout/dataflow.js.map +1 -0
- package/dist/layout/edges.d.ts +48 -0
- package/dist/layout/edges.d.ts.map +1 -0
- package/dist/layout/edges.js +843 -0
- package/dist/layout/edges.js.map +1 -0
- package/dist/layout/labels.d.ts +9 -0
- package/dist/layout/labels.d.ts.map +1 -0
- package/dist/layout/labels.js +149 -0
- package/dist/layout/labels.js.map +1 -0
- package/dist/layout/rank.d.ts +16 -0
- package/dist/layout/rank.d.ts.map +1 -0
- package/dist/layout/rank.js +80 -0
- package/dist/layout/rank.js.map +1 -0
- package/dist/layout/seating.d.ts +29 -0
- package/dist/layout/seating.d.ts.map +1 -0
- package/dist/layout/seating.js +131 -0
- package/dist/layout/seating.js.map +1 -0
- package/dist/manifest.d.ts +39 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +99 -0
- package/dist/manifest.js.map +1 -0
- package/dist/render.d.ts +49 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +125 -0
- package/dist/render.js.map +1 -0
- package/dist/scope.d.ts +21 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +57 -0
- package/dist/scope.js.map +1 -0
- package/dist/svg/architecture.d.ts +15 -0
- package/dist/svg/architecture.d.ts.map +1 -0
- package/dist/svg/architecture.js +197 -0
- package/dist/svg/architecture.js.map +1 -0
- package/dist/svg/dataflow.d.ts +9 -0
- package/dist/svg/dataflow.d.ts.map +1 -0
- package/dist/svg/dataflow.js +284 -0
- package/dist/svg/dataflow.js.map +1 -0
- package/dist/svg/document.d.ts +37 -0
- package/dist/svg/document.d.ts.map +1 -0
- package/dist/svg/document.js +148 -0
- package/dist/svg/document.js.map +1 -0
- package/dist/svg/icons.d.ts +3 -0
- package/dist/svg/icons.d.ts.map +1 -0
- package/dist/svg/icons.js +106 -0
- package/dist/svg/icons.js.map +1 -0
- package/dist/svg/primitives.d.ts +13 -0
- package/dist/svg/primitives.d.ts.map +1 -0
- package/dist/svg/primitives.js +34 -0
- package/dist/svg/primitives.js.map +1 -0
- package/dist/svg/pulse.d.ts +24 -0
- package/dist/svg/pulse.d.ts.map +1 -0
- package/dist/svg/pulse.js +33 -0
- package/dist/svg/pulse.js.map +1 -0
- package/dist/text.d.ts +18 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +70 -0
- package/dist/text.js.map +1 -0
- package/dist/theme.d.ts +44 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +72 -0
- package/dist/theme.js.map +1 -0
- package/dist/version.d.ts +9 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +9 -0
- package/dist/version.js.map +1 -0
- package/package.json +56 -0
- package/src/bounds.ts +50 -0
- package/src/corrections.ts +166 -0
- package/src/design.ts +124 -0
- package/src/errors.ts +21 -0
- package/src/geometry.ts +22 -0
- package/src/glob.ts +20 -0
- package/src/index.ts +30 -0
- package/src/layout/architecture.ts +324 -0
- package/src/layout/congestion.ts +76 -0
- package/src/layout/dataflow.ts +307 -0
- package/src/layout/edges.ts +1061 -0
- package/src/layout/labels.ts +192 -0
- package/src/layout/rank.ts +95 -0
- package/src/layout/seating.ts +189 -0
- package/src/manifest.ts +118 -0
- package/src/render.ts +190 -0
- package/src/scope.ts +67 -0
- package/src/svg/architecture.ts +293 -0
- package/src/svg/dataflow.ts +408 -0
- package/src/svg/document.ts +198 -0
- package/src/svg/icons.ts +121 -0
- package/src/svg/primitives.ts +50 -0
- package/src/svg/pulse.ts +48 -0
- package/src/text.ts +92 -0
- package/src/theme.ts +107 -0
- package/src/version.ts +9 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code points XML 1.0 has no spelling for: most control characters, the
|
|
3
|
+
* surrogate halves, and the two non-characters at the end of the basic plane.
|
|
4
|
+
* They are dropped rather than escaped, because there is no escape — a
|
|
5
|
+
* document containing one is not merely ugly, it does not parse.
|
|
6
|
+
*
|
|
7
|
+
* Labels reach here from an extraction model by way of the schema, which
|
|
8
|
+
* constrains their length but not their alphabet, so this is the last place
|
|
9
|
+
* such a character can be stopped. Matching by code point rather than by code
|
|
10
|
+
* unit is what keeps a well-formed astral character — which is a pair of
|
|
11
|
+
* surrogates in memory — from being mistaken for two lone halves.
|
|
12
|
+
*/
|
|
13
|
+
const FORBIDDEN_IN_XML = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE\uFFFF]/gu;
|
|
14
|
+
/**
|
|
15
|
+
* XML escaping for text nodes and attribute values alike. Attributes are
|
|
16
|
+
* always written in double quotes, so the single quote does not need an
|
|
17
|
+
* entity, but `<` and `&` do in both positions.
|
|
18
|
+
*/
|
|
19
|
+
export const escapeXml = (value) => value
|
|
20
|
+
.replace(FORBIDDEN_IN_XML, "")
|
|
21
|
+
.replace(/&/g, "&")
|
|
22
|
+
.replace(/</g, "<")
|
|
23
|
+
.replace(/>/g, ">")
|
|
24
|
+
.replace(/"/g, """);
|
|
25
|
+
const serialiseAttributes = (attributes) => Object.entries(attributes)
|
|
26
|
+
.filter((entry) => entry[1] !== undefined)
|
|
27
|
+
.map(([name, value]) => ` ${name}="${escapeXml(String(value))}"`)
|
|
28
|
+
.join("");
|
|
29
|
+
export const tag = (name, attributes) => `<${name}${serialiseAttributes(attributes)}/>`;
|
|
30
|
+
export const wrap = (name, attributes, children) => `<${name}${serialiseAttributes(attributes)}>${children}</${name}>`;
|
|
31
|
+
export const textNode = (attributes, content) => wrap("text", attributes, escapeXml(content));
|
|
32
|
+
/** Joins emitted fragments with newlines, dropping the ones that produced nothing. */
|
|
33
|
+
export const lines = (fragments) => fragments.filter((fragment) => fragment.length > 0).join("\n");
|
|
34
|
+
//# sourceMappingURL=primitives.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primitives.js","sourceRoot":"","sources":["../../src/svg/primitives.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,gBAAgB,GACpB,qEAAqE,CAAC;AAExE;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAa,EAAU,EAAE,CACjD,KAAK;KACF,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;KAC7B,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;KACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;KACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;KACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAI7B,MAAM,mBAAmB,GAAG,CAAC,UAAsB,EAAU,EAAE,CAC7D,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC;KACvB,MAAM,CAAC,CAAC,KAAK,EAAsC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;KAC7E,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,KAAK,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC;KAChE,IAAI,CAAC,EAAE,CAAC,CAAC;AAEd,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,UAAsB,EAAU,EAAE,CAClE,IAAI,IAAI,GAAG,mBAAmB,CAAC,UAAU,CAAC,IAAI,CAAC;AAEjD,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,UAAsB,EAAE,QAAgB,EAAU,EAAE,CACrF,IAAI,IAAI,GAAG,mBAAmB,CAAC,UAAU,CAAC,IAAI,QAAQ,KAAK,IAAI,GAAG,CAAC;AAErE,MAAM,CAAC,MAAM,QAAQ,GAAG,CACtB,UAAsB,EACtB,OAAe,EACP,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;AAE1D,sFAAsF;AACtF,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,SAA4B,EAAU,EAAE,CAC5D,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The travelling pulse: the mark that says a connection carries traffic
|
|
3
|
+
* rather than merely existing. Both lenses draw it from here, because a
|
|
4
|
+
* reader moving between them is reading one language — an architecture edge
|
|
5
|
+
* and a sequence message differ in what they connect, not in how they live.
|
|
6
|
+
*
|
|
7
|
+
* A line running behind the drawing's clock says so with a negative `begin`,
|
|
8
|
+
* which starts its motion that far into its own turn. A positive delay would
|
|
9
|
+
* describe the same steady state and lie for the seconds after load: an
|
|
10
|
+
* animation has no effect before it begins, so the dot waiting for its turn
|
|
11
|
+
* would sit at the canvas origin, in the corner, in full view.
|
|
12
|
+
*/
|
|
13
|
+
/** The dot itself, so both lenses draw the same mark whatever clock moves it. */
|
|
14
|
+
export declare const PULSE_RADIUS = 2.6;
|
|
15
|
+
export declare const TRAIN_RADIUS = 3;
|
|
16
|
+
export declare const travellingPulses: (pulse: {
|
|
17
|
+
path: string;
|
|
18
|
+
colour: string;
|
|
19
|
+
/** Dots riding this line at once, spread evenly around the turn. */
|
|
20
|
+
count: number;
|
|
21
|
+
/** How far this line runs behind the drawing's clock, in seconds. */
|
|
22
|
+
lag: number;
|
|
23
|
+
}) => string;
|
|
24
|
+
//# sourceMappingURL=pulse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pulse.d.ts","sourceRoot":"","sources":["../../src/svg/pulse.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;GAWG;AACH,iFAAiF;AACjF,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B,eAAO,MAAM,gBAAgB,UAAW;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;CACb,KAAG,MAoBH,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { HERO_PULSE_DURATION, PULSE_DURATION } from "../design.js";
|
|
2
|
+
import { coord } from "../geometry.js";
|
|
3
|
+
import { lines, tag, wrap } from "./primitives.js";
|
|
4
|
+
/**
|
|
5
|
+
* The travelling pulse: the mark that says a connection carries traffic
|
|
6
|
+
* rather than merely existing. Both lenses draw it from here, because a
|
|
7
|
+
* reader moving between them is reading one language — an architecture edge
|
|
8
|
+
* and a sequence message differ in what they connect, not in how they live.
|
|
9
|
+
*
|
|
10
|
+
* A line running behind the drawing's clock says so with a negative `begin`,
|
|
11
|
+
* which starts its motion that far into its own turn. A positive delay would
|
|
12
|
+
* describe the same steady state and lie for the seconds after load: an
|
|
13
|
+
* animation has no effect before it begins, so the dot waiting for its turn
|
|
14
|
+
* would sit at the canvas origin, in the corner, in full view.
|
|
15
|
+
*/
|
|
16
|
+
/** The dot itself, so both lenses draw the same mark whatever clock moves it. */
|
|
17
|
+
export const PULSE_RADIUS = 2.6;
|
|
18
|
+
export const TRAIN_RADIUS = 3;
|
|
19
|
+
export const travellingPulses = (pulse) => {
|
|
20
|
+
const { path, colour, count, lag } = pulse;
|
|
21
|
+
const train = count > 1;
|
|
22
|
+
const duration = train ? HERO_PULSE_DURATION : PULSE_DURATION;
|
|
23
|
+
return lines(Array.from({ length: count }, (_, index) => {
|
|
24
|
+
const behind = (lag + (duration / count) * index) % duration;
|
|
25
|
+
return wrap("circle", { r: train ? TRAIN_RADIUS : PULSE_RADIUS, fill: colour }, tag("animateMotion", {
|
|
26
|
+
dur: `${coord(duration)}s`,
|
|
27
|
+
begin: behind === 0 ? undefined : `${coord(behind - duration)}s`,
|
|
28
|
+
repeatCount: "indefinite",
|
|
29
|
+
path,
|
|
30
|
+
}));
|
|
31
|
+
}));
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=pulse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pulse.js","sourceRoot":"","sources":["../../src/svg/pulse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,iFAAiF;AACjF,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAC;AAChC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAE9B,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAOhC,EAAU,EAAE;IACX,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC;IACxB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,cAAc,CAAC;IAE9D,OAAO,KAAK,CACV,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE;QACzC,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,CAAC,QAAQ,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,QAAQ,CAAC;QAC7D,OAAO,IAAI,CACT,QAAQ,EACR,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,EACxD,GAAG,CAAC,eAAe,EAAE;YACnB,GAAG,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG;YAC1B,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC,GAAG;YAChE,WAAW,EAAE,YAAY;YACzB,IAAI;SACL,CAAC,CACH,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;AACJ,CAAC,CAAC"}
|
package/dist/text.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The renderer never asks a font engine how wide a string is: it must produce
|
|
3
|
+
* the same geometry on a CI runner with no fonts installed as on a laptop
|
|
4
|
+
* with all of them. Widths come from a table instead, and every box is sized
|
|
5
|
+
* with enough slack that the small error against a real face stays invisible.
|
|
6
|
+
*/
|
|
7
|
+
export type Face = "sans" | "sans-bold" | "mono";
|
|
8
|
+
export declare const SANS_STACK = "-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Noto Sans\", Helvetica, Arial, sans-serif";
|
|
9
|
+
export declare const MONO_STACK = "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace";
|
|
10
|
+
/** Width of `text` in user units at `fontSize`, to a hundredth of a unit. */
|
|
11
|
+
export declare const measure: (text: string, face: Face, fontSize: number) => number;
|
|
12
|
+
/**
|
|
13
|
+
* Shortens `text` until it fits `maxWidth`, ellipsis included. Cutting by
|
|
14
|
+
* code point rather than by UTF-16 unit keeps a surrogate pair from being
|
|
15
|
+
* split into a replacement character.
|
|
16
|
+
*/
|
|
17
|
+
export declare const truncate: (text: string, face: Face, fontSize: number, maxWidth: number) => string;
|
|
18
|
+
//# sourceMappingURL=text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AAEjD,eAAO,MAAM,UAAU,iGACqE,CAAC;AAC7F,eAAO,MAAM,UAAU,0EACgD,CAAC;AA0CxE,6EAA6E;AAC7E,eAAO,MAAM,OAAO,SAAU,MAAM,QAAQ,IAAI,YAAY,MAAM,KAAG,MAIpE,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,QAAQ,SACb,MAAM,QACN,IAAI,YACA,MAAM,YACN,MAAM,KACf,MAiBF,CAAC"}
|
package/dist/text.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { assertNever } from "@coldtea/pr-lens-schema";
|
|
2
|
+
export const SANS_STACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif';
|
|
3
|
+
export const MONO_STACK = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace';
|
|
4
|
+
/** Advance widths per 1000 units of em, for the regular sans face. */
|
|
5
|
+
const SANS_ADVANCE = {
|
|
6
|
+
" ": 278, "!": 278, '"': 355, "#": 556, $: 556, "%": 889, "&": 667, "'": 191,
|
|
7
|
+
"(": 333, ")": 333, "*": 389, "+": 584, ",": 278, "-": 333, ".": 278, "/": 278,
|
|
8
|
+
"0": 556, "1": 556, "2": 556, "3": 556, "4": 556, "5": 556, "6": 556, "7": 556,
|
|
9
|
+
"8": 556, "9": 556, ":": 278, ";": 278, "<": 584, "=": 584, ">": 584, "?": 556,
|
|
10
|
+
"@": 1015, A: 667, B: 667, C: 722, D: 722, E: 667, F: 611, G: 778, H: 722,
|
|
11
|
+
I: 278, J: 500, K: 667, L: 556, M: 833, N: 722, O: 778, P: 667, Q: 778, R: 722,
|
|
12
|
+
S: 667, T: 611, U: 722, V: 667, W: 944, X: 667, Y: 667, Z: 611, "[": 278,
|
|
13
|
+
"\\": 278, "]": 278, "^": 469, _: 556, "`": 333, a: 556, b: 556, c: 500, d: 556,
|
|
14
|
+
e: 556, f: 278, g: 556, h: 556, i: 222, j: 222, k: 500, l: 222, m: 833, n: 556,
|
|
15
|
+
o: 556, p: 556, q: 556, r: 333, s: 500, t: 278, u: 556, v: 500, w: 722, x: 500,
|
|
16
|
+
y: 500, z: 500, "{": 334, "|": 260, "}": 334, "~": 584,
|
|
17
|
+
};
|
|
18
|
+
/** What a character outside the table is assumed to cost, in the same units. */
|
|
19
|
+
const FALLBACK_ADVANCE = 600;
|
|
20
|
+
/**
|
|
21
|
+
* The bold face of the same family runs a little wider at every weight step.
|
|
22
|
+
* One factor over the regular table is close enough for boxes that already
|
|
23
|
+
* carry padding, and it keeps a second table from drifting out of sync.
|
|
24
|
+
*/
|
|
25
|
+
const BOLD_WIDENING = 1.06;
|
|
26
|
+
const MONO_ADVANCE = 600;
|
|
27
|
+
const advanceFor = (face, character) => {
|
|
28
|
+
switch (face) {
|
|
29
|
+
case "sans":
|
|
30
|
+
return SANS_ADVANCE[character] ?? FALLBACK_ADVANCE;
|
|
31
|
+
case "sans-bold":
|
|
32
|
+
return (SANS_ADVANCE[character] ?? FALLBACK_ADVANCE) * BOLD_WIDENING;
|
|
33
|
+
case "mono":
|
|
34
|
+
return MONO_ADVANCE;
|
|
35
|
+
default:
|
|
36
|
+
return assertNever(face, "Unhandled font face");
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
/** Width of `text` in user units at `fontSize`, to a hundredth of a unit. */
|
|
40
|
+
export const measure = (text, face, fontSize) => {
|
|
41
|
+
let thousandths = 0;
|
|
42
|
+
for (const character of text)
|
|
43
|
+
thousandths += advanceFor(face, character);
|
|
44
|
+
return Math.round((thousandths / 1000) * fontSize * 100) / 100;
|
|
45
|
+
};
|
|
46
|
+
const ELLIPSIS = "…";
|
|
47
|
+
/**
|
|
48
|
+
* Shortens `text` until it fits `maxWidth`, ellipsis included. Cutting by
|
|
49
|
+
* code point rather than by UTF-16 unit keeps a surrogate pair from being
|
|
50
|
+
* split into a replacement character.
|
|
51
|
+
*/
|
|
52
|
+
export const truncate = (text, face, fontSize, maxWidth) => {
|
|
53
|
+
if (measure(text, face, fontSize) <= maxWidth)
|
|
54
|
+
return text;
|
|
55
|
+
const characters = [...text];
|
|
56
|
+
const ellipsisWidth = measure(ELLIPSIS, face, fontSize);
|
|
57
|
+
let width = 0;
|
|
58
|
+
let kept = 0;
|
|
59
|
+
for (const character of characters) {
|
|
60
|
+
const next = width + measure(character, face, fontSize);
|
|
61
|
+
if (next + ellipsisWidth > maxWidth)
|
|
62
|
+
break;
|
|
63
|
+
width = next;
|
|
64
|
+
kept += 1;
|
|
65
|
+
}
|
|
66
|
+
if (kept === 0)
|
|
67
|
+
return ELLIPSIS;
|
|
68
|
+
return `${characters.slice(0, kept).join("").trimEnd()}${ELLIPSIS}`;
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=text.js.map
|
package/dist/text.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"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"}
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { THEMES, type Theme } from "@coldtea/pr-lens-schema";
|
|
2
|
+
/**
|
|
3
|
+
* The two halves of a GitHub `<picture>` pair. A comment ships both and the
|
|
4
|
+
* client picks; neither half may depend on the page it lands in, because an
|
|
5
|
+
* SVG served as an image cannot see it.
|
|
6
|
+
*
|
|
7
|
+
* The contract owns the pair, because the number of themes is one half of the
|
|
8
|
+
* arithmetic that bounds a document's view tree. Re-exported here so a caller
|
|
9
|
+
* rendering a diagram does not have to reach into the schema for it.
|
|
10
|
+
*/
|
|
11
|
+
export { THEMES, type Theme };
|
|
12
|
+
/**
|
|
13
|
+
* Every colour the renderer can paint, resolved to a literal before it
|
|
14
|
+
* reaches the document. CSS custom properties would be shorter, but GitHub
|
|
15
|
+
* serves these files through an image proxy where nothing outside the file
|
|
16
|
+
* exists, so the palette is baked in per theme.
|
|
17
|
+
*/
|
|
18
|
+
export type Palette = {
|
|
19
|
+
background: string;
|
|
20
|
+
dot: string;
|
|
21
|
+
lane: string;
|
|
22
|
+
card: string;
|
|
23
|
+
cardBorder: string;
|
|
24
|
+
foreground: string;
|
|
25
|
+
muted: string;
|
|
26
|
+
edge: string;
|
|
27
|
+
chip: string;
|
|
28
|
+
pill: string;
|
|
29
|
+
pillBorder: string;
|
|
30
|
+
lifeline: string;
|
|
31
|
+
added: string;
|
|
32
|
+
addedFill: string;
|
|
33
|
+
addedBorder: string;
|
|
34
|
+
modified: string;
|
|
35
|
+
modifiedFill: string;
|
|
36
|
+
modifiedBorder: string;
|
|
37
|
+
removed: string;
|
|
38
|
+
removedFill: string;
|
|
39
|
+
removedBorder: string;
|
|
40
|
+
neutralFill: string;
|
|
41
|
+
shadow: string;
|
|
42
|
+
};
|
|
43
|
+
export declare const paletteFor: (theme: Theme) => Palette;
|
|
44
|
+
//# sourceMappingURL=theme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,MAAM,EAAE,KAAK,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAE1E;;;;;;;;GAQG;AACH,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,EAAE,CAAC;AAE9B;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAsDF,eAAO,MAAM,UAAU,UAAW,KAAK,KAAG,OASzC,CAAC"}
|
package/dist/theme.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { assertNever, THEMES } from "@coldtea/pr-lens-schema";
|
|
2
|
+
/**
|
|
3
|
+
* The two halves of a GitHub `<picture>` pair. A comment ships both and the
|
|
4
|
+
* client picks; neither half may depend on the page it lands in, because an
|
|
5
|
+
* SVG served as an image cannot see it.
|
|
6
|
+
*
|
|
7
|
+
* The contract owns the pair, because the number of themes is one half of the
|
|
8
|
+
* arithmetic that bounds a document's view tree. Re-exported here so a caller
|
|
9
|
+
* rendering a diagram does not have to reach into the schema for it.
|
|
10
|
+
*/
|
|
11
|
+
export { THEMES };
|
|
12
|
+
const LIGHT = {
|
|
13
|
+
background: "#ffffff",
|
|
14
|
+
dot: "rgba(140,149,159,.35)",
|
|
15
|
+
lane: "#f6f8fa",
|
|
16
|
+
card: "#ffffff",
|
|
17
|
+
cardBorder: "#d1d9e0",
|
|
18
|
+
foreground: "#1f2328",
|
|
19
|
+
muted: "#59636e",
|
|
20
|
+
edge: "#8c959f",
|
|
21
|
+
chip: "#f6f8fa",
|
|
22
|
+
pill: "#ffffff",
|
|
23
|
+
pillBorder: "#d8dee4",
|
|
24
|
+
lifeline: "#d1d9e0",
|
|
25
|
+
added: "#1a7f37",
|
|
26
|
+
addedFill: "#dafbe1",
|
|
27
|
+
addedBorder: "rgba(31,136,61,.4)",
|
|
28
|
+
modified: "#9a6700",
|
|
29
|
+
modifiedFill: "#fff8c5",
|
|
30
|
+
modifiedBorder: "rgba(154,103,0,.35)",
|
|
31
|
+
removed: "#d1242f",
|
|
32
|
+
removedFill: "#ffebe9",
|
|
33
|
+
removedBorder: "rgba(209,36,47,.35)",
|
|
34
|
+
neutralFill: "#f6f8fa",
|
|
35
|
+
shadow: "rgba(31,35,40,.14)",
|
|
36
|
+
};
|
|
37
|
+
const DARK = {
|
|
38
|
+
background: "#0d1117",
|
|
39
|
+
dot: "rgba(110,118,129,.22)",
|
|
40
|
+
lane: "rgba(110,118,129,.07)",
|
|
41
|
+
card: "#1c2128",
|
|
42
|
+
cardBorder: "#3d444d",
|
|
43
|
+
foreground: "#e6edf3",
|
|
44
|
+
muted: "#9198a1",
|
|
45
|
+
edge: "#6e7681",
|
|
46
|
+
chip: "rgba(110,118,129,.18)",
|
|
47
|
+
pill: "#0d1117",
|
|
48
|
+
pillBorder: "#21262d",
|
|
49
|
+
lifeline: "#30363d",
|
|
50
|
+
added: "#3fb950",
|
|
51
|
+
addedFill: "rgba(46,160,67,.15)",
|
|
52
|
+
addedBorder: "rgba(63,185,80,.4)",
|
|
53
|
+
modified: "#d29922",
|
|
54
|
+
modifiedFill: "rgba(187,128,9,.15)",
|
|
55
|
+
modifiedBorder: "rgba(210,153,34,.4)",
|
|
56
|
+
removed: "#f85149",
|
|
57
|
+
removedFill: "rgba(248,81,73,.12)",
|
|
58
|
+
removedBorder: "rgba(248,81,73,.4)",
|
|
59
|
+
neutralFill: "rgba(110,118,129,.18)",
|
|
60
|
+
shadow: "rgba(0,0,0,.28)",
|
|
61
|
+
};
|
|
62
|
+
export const paletteFor = (theme) => {
|
|
63
|
+
switch (theme) {
|
|
64
|
+
case "light":
|
|
65
|
+
return LIGHT;
|
|
66
|
+
case "dark":
|
|
67
|
+
return DARK;
|
|
68
|
+
default:
|
|
69
|
+
return assertNever(theme, "Unhandled theme");
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAc,MAAM,yBAAyB,CAAC;AAE1E;;;;;;;;GAQG;AACH,OAAO,EAAE,MAAM,EAAc,CAAC;AAkC9B,MAAM,KAAK,GAAY;IACrB,UAAU,EAAE,SAAS;IACrB,GAAG,EAAE,uBAAuB;IAC5B,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,UAAU,EAAE,SAAS;IACrB,UAAU,EAAE,SAAS;IACrB,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,UAAU,EAAE,SAAS;IACrB,QAAQ,EAAE,SAAS;IACnB,KAAK,EAAE,SAAS;IAChB,SAAS,EAAE,SAAS;IACpB,WAAW,EAAE,oBAAoB;IACjC,QAAQ,EAAE,SAAS;IACnB,YAAY,EAAE,SAAS;IACvB,cAAc,EAAE,qBAAqB;IACrC,OAAO,EAAE,SAAS;IAClB,WAAW,EAAE,SAAS;IACtB,aAAa,EAAE,qBAAqB;IACpC,WAAW,EAAE,SAAS;IACtB,MAAM,EAAE,oBAAoB;CAC7B,CAAC;AAEF,MAAM,IAAI,GAAY;IACpB,UAAU,EAAE,SAAS;IACrB,GAAG,EAAE,uBAAuB;IAC5B,IAAI,EAAE,uBAAuB;IAC7B,IAAI,EAAE,SAAS;IACf,UAAU,EAAE,SAAS;IACrB,UAAU,EAAE,SAAS;IACrB,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,uBAAuB;IAC7B,IAAI,EAAE,SAAS;IACf,UAAU,EAAE,SAAS;IACrB,QAAQ,EAAE,SAAS;IACnB,KAAK,EAAE,SAAS;IAChB,SAAS,EAAE,qBAAqB;IAChC,WAAW,EAAE,oBAAoB;IACjC,QAAQ,EAAE,SAAS;IACnB,YAAY,EAAE,qBAAqB;IACnC,cAAc,EAAE,qBAAqB;IACrC,OAAO,EAAE,SAAS;IAClB,WAAW,EAAE,qBAAqB;IAClC,aAAa,EAAE,oBAAoB;IACnC,WAAW,EAAE,uBAAuB;IACpC,MAAM,EAAE,iBAAiB;CAC1B,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAY,EAAW,EAAE;IAClD,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,OAAO;YACV,OAAO,KAAK,CAAC;QACf,KAAK,MAAM;YACT,OAAO,IAAI,CAAC;QACd;YACE,OAAO,WAAW,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;IACjD,CAAC;AACH,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stamped into every render manifest so a stored diagram can be traced to the
|
|
3
|
+
* code that drew it. Kept in step with package.json by a test, since a
|
|
4
|
+
* package cannot read its own manifest without doing I/O the renderer does
|
|
5
|
+
* not otherwise need.
|
|
6
|
+
*/
|
|
7
|
+
export declare const RENDERER_NAME = "@coldtea/pr-lens-renderer";
|
|
8
|
+
export declare const RENDERER_VERSION = "0.1.0";
|
|
9
|
+
//# sourceMappingURL=version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,aAAa,8BAA8B,CAAC;AAEzD,eAAO,MAAM,gBAAgB,UAAU,CAAC"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stamped into every render manifest so a stored diagram can be traced to the
|
|
3
|
+
* code that drew it. Kept in step with package.json by a test, since a
|
|
4
|
+
* package cannot read its own manifest without doing I/O the renderer does
|
|
5
|
+
* not otherwise need.
|
|
6
|
+
*/
|
|
7
|
+
export const RENDERER_NAME = "@coldtea/pr-lens-renderer";
|
|
8
|
+
export const RENDERER_VERSION = "0.1.0";
|
|
9
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,2BAA2B,CAAC;AAEzD,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@coldtea/pr-lens-renderer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The PR Lens renderer: a schema-valid graph document in, a self-contained animated SVG out.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Ohans Emmanuel",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/coldteadotai/pr-lens.git",
|
|
10
|
+
"directory": "packages/renderer"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/coldteadotai/pr-lens/tree/main/packages/renderer#readme",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"pr-lens",
|
|
15
|
+
"code-review",
|
|
16
|
+
"architecture-diagram",
|
|
17
|
+
"data-flow",
|
|
18
|
+
"svg",
|
|
19
|
+
"diagram"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"src",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=20.11"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@coldtea/pr-lens-schema": "0.1.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.19.0",
|
|
47
|
+
"typescript": "7.0.2",
|
|
48
|
+
"vitest": "4.1.11"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
52
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/bounds.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Box } from "./geometry.js";
|
|
2
|
+
|
|
3
|
+
/** The smallest box containing all of them, or nothing when there are none. */
|
|
4
|
+
export const union = (boxes: readonly Box[]): Box | undefined => {
|
|
5
|
+
const first = boxes[0];
|
|
6
|
+
if (first === undefined) return undefined;
|
|
7
|
+
|
|
8
|
+
let left = first.x;
|
|
9
|
+
let top = first.y;
|
|
10
|
+
let right = first.x + first.width;
|
|
11
|
+
let bottom = first.y + first.height;
|
|
12
|
+
|
|
13
|
+
for (const box of boxes) {
|
|
14
|
+
left = Math.min(left, box.x);
|
|
15
|
+
top = Math.min(top, box.y);
|
|
16
|
+
right = Math.max(right, box.x + box.width);
|
|
17
|
+
bottom = Math.max(bottom, box.y + box.height);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return { x: left, y: top, width: right - left, height: bottom - top };
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type Canvas = { width: number; height: number; shiftX: number; shiftY: number };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A canvas big enough for everything that was drawn.
|
|
27
|
+
*
|
|
28
|
+
* Edge routes deliberately leave their lane, and a label pill can be pushed
|
|
29
|
+
* past the last card in search of clear air, so the space the lanes occupy is
|
|
30
|
+
* a floor rather than the answer. The canvas only ever grows: the margins the
|
|
31
|
+
* design leaves above and beside the lanes are part of the design, not slack
|
|
32
|
+
* to be reclaimed.
|
|
33
|
+
*/
|
|
34
|
+
export const canvasFor = (
|
|
35
|
+
laid: { width: number; height: number },
|
|
36
|
+
drawn: Box | undefined,
|
|
37
|
+
margin: number,
|
|
38
|
+
): Canvas => {
|
|
39
|
+
if (drawn === undefined) return { width: laid.width, height: laid.height, shiftX: 0, shiftY: 0 };
|
|
40
|
+
|
|
41
|
+
const shiftX = Math.max(0, margin - drawn.x);
|
|
42
|
+
const shiftY = Math.max(0, margin - drawn.y);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
width: Math.ceil(Math.max(laid.width, drawn.x + drawn.width + margin) + shiftX),
|
|
46
|
+
height: Math.ceil(Math.max(laid.height, drawn.y + drawn.height + margin) + shiftY),
|
|
47
|
+
shiftX,
|
|
48
|
+
shiftY,
|
|
49
|
+
};
|
|
50
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Flow,
|
|
3
|
+
FlowMessage,
|
|
4
|
+
GraphDoc,
|
|
5
|
+
GraphEdge,
|
|
6
|
+
GraphNode,
|
|
7
|
+
Lane,
|
|
8
|
+
MapCorrections,
|
|
9
|
+
View,
|
|
10
|
+
ViewScope,
|
|
11
|
+
} from "@coldtea/pr-lens-schema";
|
|
12
|
+
import { assertNever } from "@coldtea/pr-lens-schema";
|
|
13
|
+
import { PrLensRenderError } from "./errors.js";
|
|
14
|
+
import { matchesGlob } from "./glob.js";
|
|
15
|
+
|
|
16
|
+
const ID_SELECTOR = "id:";
|
|
17
|
+
|
|
18
|
+
const selects = (selector: string, node: GraphNode): boolean => {
|
|
19
|
+
if (selector.startsWith(ID_SELECTOR)) return node.id === selector.slice(ID_SELECTOR.length);
|
|
20
|
+
return node.files.some((file) => matchesGlob(selector, file.path));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A view of a selection with the ids the overlay removed taken out, or
|
|
25
|
+
* `undefined` when nothing it pointed at survived. A selection is never
|
|
26
|
+
* allowed to empty out into "show everything": that is a different view.
|
|
27
|
+
*/
|
|
28
|
+
const narrowScope = (
|
|
29
|
+
scope: ViewScope,
|
|
30
|
+
survives: { lane: (id: string) => boolean; node: (id: string) => boolean; edge: (id: string) => boolean; flow: (id: string) => boolean },
|
|
31
|
+
): ViewScope | undefined => {
|
|
32
|
+
switch (scope.kind) {
|
|
33
|
+
case "all":
|
|
34
|
+
return scope;
|
|
35
|
+
case "selection": {
|
|
36
|
+
const narrowed = {
|
|
37
|
+
kind: "selection" as const,
|
|
38
|
+
lanes: scope.lanes.filter(survives.lane),
|
|
39
|
+
nodes: scope.nodes.filter(survives.node),
|
|
40
|
+
edges: scope.edges.filter(survives.edge),
|
|
41
|
+
flows: scope.flows.filter(survives.flow),
|
|
42
|
+
};
|
|
43
|
+
const total =
|
|
44
|
+
narrowed.lanes.length + narrowed.nodes.length + narrowed.edges.length + narrowed.flows.length;
|
|
45
|
+
return total > 0 ? narrowed : undefined;
|
|
46
|
+
}
|
|
47
|
+
default:
|
|
48
|
+
return assertNever(scope, "Unhandled view scope");
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const narrowViews = (
|
|
53
|
+
views: readonly View[],
|
|
54
|
+
survives: Parameters<typeof narrowScope>[1],
|
|
55
|
+
): View[] =>
|
|
56
|
+
views.flatMap((view) => {
|
|
57
|
+
const scope = narrowScope(view.scope, survives);
|
|
58
|
+
if (scope === undefined) return [];
|
|
59
|
+
return [{ ...view, scope, children: narrowViews(view.children, survives) }];
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Applies a repository's corrections over an inferred document.
|
|
64
|
+
*
|
|
65
|
+
* The overlay is re-applied to fresh inference on every run, so a correction
|
|
66
|
+
* keeps holding as the code moves and inference never learns to agree with
|
|
67
|
+
* it. Everything a removed node touched goes with it: an edge to a node that
|
|
68
|
+
* is no longer drawn would point at empty space, and a flow step from one
|
|
69
|
+
* would animate out of nowhere.
|
|
70
|
+
*/
|
|
71
|
+
export const applyCorrections = (doc: GraphDoc, corrections: MapCorrections): GraphDoc => {
|
|
72
|
+
const excluded = new Set(
|
|
73
|
+
doc.nodes.filter((node) => corrections.exclude.some((selector) => selects(selector, node))).map((node) => node.id),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const nodes: GraphNode[] = doc.nodes
|
|
77
|
+
.filter((node) => !excluded.has(node.id))
|
|
78
|
+
.map((node) => {
|
|
79
|
+
const rename = corrections.rename.find((rule) => selects(rule.match, node));
|
|
80
|
+
const lane = corrections.lane.find((rule) => selects(rule.match, node));
|
|
81
|
+
const group = corrections.group.find((rule) => selects(rule.match, node));
|
|
82
|
+
return {
|
|
83
|
+
...node,
|
|
84
|
+
label: rename ? rename.to : node.label,
|
|
85
|
+
lane: lane ? lane.lane : node.lane,
|
|
86
|
+
group: group ? group.group : node.group,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (nodes.length === 0)
|
|
91
|
+
throw new PrLensRenderError(
|
|
92
|
+
"NOTHING_TO_RENDER",
|
|
93
|
+
"the exclude rules in this repository's config removed every node",
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* A `lane` correction may name a band the document never declared, which is
|
|
98
|
+
* how a repository adds one of its own. It gets the id for a label, since
|
|
99
|
+
* that is the only name the config carries.
|
|
100
|
+
*/
|
|
101
|
+
const declared = new Set(doc.lanes.map((lane) => lane.id));
|
|
102
|
+
const invented: Lane[] = [...new Set(nodes.map((node) => node.lane))]
|
|
103
|
+
.filter((id) => !declared.has(id))
|
|
104
|
+
.map((id) => ({ id, label: id }));
|
|
105
|
+
|
|
106
|
+
const lanes = [...doc.lanes, ...invented];
|
|
107
|
+
|
|
108
|
+
const edges: GraphEdge[] = doc.edges.filter(
|
|
109
|
+
(edge) => !excluded.has(edge.from) && !excluded.has(edge.to),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const flows: Flow[] = doc.flows.flatMap((flow) => {
|
|
113
|
+
const participants = flow.participants.filter((participant) => !excluded.has(participant.node));
|
|
114
|
+
if (participants.length < 2) return [];
|
|
115
|
+
|
|
116
|
+
const kept = new Set(participants.map((participant) => participant.node));
|
|
117
|
+
const messages: FlowMessage[] = flow.messages.filter(
|
|
118
|
+
(message) => kept.has(message.from) && kept.has(message.to),
|
|
119
|
+
);
|
|
120
|
+
if (messages.length === 0) return [];
|
|
121
|
+
|
|
122
|
+
return [{ ...flow, participants, messages }];
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const survives = {
|
|
126
|
+
lane: (id: string) => lanes.some((lane) => lane.id === id),
|
|
127
|
+
node: (id: string) => !excluded.has(id),
|
|
128
|
+
edge: (id: string) => edges.some((edge) => edge.id === id),
|
|
129
|
+
flow: (id: string) => flows.some((flow) => flow.id === id),
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
...doc,
|
|
134
|
+
lanes,
|
|
135
|
+
nodes,
|
|
136
|
+
edges,
|
|
137
|
+
flows,
|
|
138
|
+
views: narrowViews(doc.views, survives),
|
|
139
|
+
layout: narrowLayout(doc.layout, survives),
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Layout hints name nodes and lanes, so an exclusion can leave one pointing at
|
|
145
|
+
* something that is no longer there. The renderer would not notice — a hint
|
|
146
|
+
* for a node it is not drawing simply goes unread — but the corrected document
|
|
147
|
+
* is a document like any other, and a surface that writes it out and parses it
|
|
148
|
+
* back is entitled to have it still be valid.
|
|
149
|
+
*/
|
|
150
|
+
const narrowLayout = (
|
|
151
|
+
layout: GraphDoc["layout"],
|
|
152
|
+
survives: Parameters<typeof narrowScope>[1],
|
|
153
|
+
): GraphDoc["layout"] => {
|
|
154
|
+
if (layout === undefined) return undefined;
|
|
155
|
+
|
|
156
|
+
const rank =
|
|
157
|
+
layout.rank === undefined
|
|
158
|
+
? undefined
|
|
159
|
+
: Object.fromEntries(Object.entries(layout.rank).filter(([id]) => survives.node(id)));
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
...layout,
|
|
163
|
+
laneOrder: layout.laneOrder.filter(survives.lane),
|
|
164
|
+
...(rank === undefined ? {} : { rank }),
|
|
165
|
+
};
|
|
166
|
+
};
|