@coldtea/pr-lens-renderer 0.2.3 → 0.2.4
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/dist/svg/architecture.d.ts +6 -6
- package/dist/svg/architecture.d.ts.map +1 -1
- package/dist/svg/architecture.js +56 -34
- package/dist/svg/architecture.js.map +1 -1
- package/dist/svg/dataflow.d.ts +1 -1
- package/dist/svg/dataflow.d.ts.map +1 -1
- package/dist/svg/dataflow.js +17 -11
- package/dist/svg/dataflow.js.map +1 -1
- package/dist/svg/document.d.ts.map +1 -1
- package/dist/svg/document.js +3 -50
- package/dist/svg/document.js.map +1 -1
- package/dist/svg/icons.d.ts +2 -1
- package/dist/svg/icons.d.ts.map +1 -1
- package/dist/svg/icons.js +31 -23
- package/dist/svg/icons.js.map +1 -1
- package/dist/svg/styles.d.ts +74 -0
- package/dist/svg/styles.d.ts.map +1 -0
- package/dist/svg/styles.js +109 -0
- package/dist/svg/styles.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/svg/architecture.ts +64 -36
- package/src/svg/dataflow.ts +32 -23
- package/src/svg/document.ts +4 -53
- package/src/svg/icons.ts +39 -28
- package/src/svg/styles.ts +122 -0
- package/src/version.ts +1 -1
package/src/svg/dataflow.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import type { Flow, FlowMessage, GraphNode, MessageKind } from "@coldtea/pr-lens-schema";
|
|
2
1
|
import { assertNever } from "@coldtea/pr-lens-schema";
|
|
2
|
+
import type { Flow, FlowMessage, GraphNode, MessageKind } from "@coldtea/pr-lens-schema";
|
|
3
|
+
import { measure } from "../text.js";
|
|
4
|
+
import { coord } from "../geometry.js";
|
|
5
|
+
import type { Box } from "../geometry.js";
|
|
6
|
+
import type { Palette } from "../theme.js";
|
|
7
|
+
import { lines, tag, wrap } from "./primitives.js";
|
|
8
|
+
import { PULSE_RADIUS, TRAIN_RADIUS } from "./pulse.js";
|
|
9
|
+
import { messageAttributes, stylesFor } from "./styles.js";
|
|
10
|
+
import { paintCard, paintLabelPill } from "./architecture.js";
|
|
11
|
+
import { atlasBoxes, emptyAtlas, type RenderAtlas } from "../atlas.js";
|
|
12
|
+
import { canvasFor, covering, union, type Canvas } from "../bounds.js";
|
|
13
|
+
import { markerFor, openMarkerFor, shifted, toneColour, toneFor, type Tone } from "./document.js";
|
|
3
14
|
import {
|
|
4
15
|
DIAGRAM_MARGIN,
|
|
5
16
|
FLOW_CYCLE_MAX,
|
|
@@ -12,11 +23,6 @@ import {
|
|
|
12
23
|
PILL_TEXT_SIZE,
|
|
13
24
|
TITLE_SIZE,
|
|
14
25
|
} from "../design.js";
|
|
15
|
-
import { atlasBoxes, emptyAtlas, type RenderAtlas } from "../atlas.js";
|
|
16
|
-
import { canvasFor, covering, union, type Canvas } from "../bounds.js";
|
|
17
|
-
import type { Box } from "../geometry.js";
|
|
18
|
-
import { measure } from "../text.js";
|
|
19
|
-
import { coord } from "../geometry.js";
|
|
20
26
|
import {
|
|
21
27
|
ACTIVATION_HALF_WIDTH,
|
|
22
28
|
FLOW_BAND_PAD_X,
|
|
@@ -32,11 +38,6 @@ import {
|
|
|
32
38
|
type FlowLayout,
|
|
33
39
|
type PlacedMessage,
|
|
34
40
|
} from "../layout/dataflow.js";
|
|
35
|
-
import type { Palette } from "../theme.js";
|
|
36
|
-
import { paintCard, paintLabelPill } from "./architecture.js";
|
|
37
|
-
import { markerFor, openMarkerFor, shifted, toneColour, toneFor, type Tone } from "./document.js";
|
|
38
|
-
import { lines, tag, wrap } from "./primitives.js";
|
|
39
|
-
import { PULSE_RADIUS, TRAIN_RADIUS } from "./pulse.js";
|
|
40
41
|
|
|
41
42
|
/** A ratio inside the animation cycle, written to a fixed number of places. */
|
|
42
43
|
const ratio = (value: number): string => String(Math.round(value * 10000) / 10000);
|
|
@@ -236,16 +237,17 @@ const paintMessage = (
|
|
|
236
237
|
const tone = toneFor(placed.message.delta);
|
|
237
238
|
const direction = travelDirection(placed.message.kind, placed.fromX, placed.toX);
|
|
238
239
|
const head = headFor(placed.message.kind, tone);
|
|
240
|
+
const attributes = messageAttributes(placed.message, palette);
|
|
239
241
|
|
|
240
242
|
if (direction === 0) {
|
|
241
243
|
const activated = activeAt(placed.message.from, placed.y);
|
|
242
244
|
const path = selfPath(placed.fromX, placed.y, activated);
|
|
243
245
|
return {
|
|
244
246
|
line: lines([
|
|
245
|
-
tag("path", { class: messageClasses(placed.message, tone), d: path, "marker-end": head }),
|
|
247
|
+
tag("path", { class: messageClasses(placed.message, tone), d: path, "marker-end": head, ...attributes }),
|
|
246
248
|
pulsesFor(placed, path, slotCount, palette),
|
|
247
249
|
]),
|
|
248
|
-
pill: paintLabelPill(placed.label, selfPillBox(placed, activated), tone),
|
|
250
|
+
pill: paintLabelPill(placed.label, selfPillBox(placed, activated), tone, palette),
|
|
249
251
|
};
|
|
250
252
|
}
|
|
251
253
|
|
|
@@ -254,10 +256,10 @@ const paintMessage = (
|
|
|
254
256
|
|
|
255
257
|
return {
|
|
256
258
|
line: lines([
|
|
257
|
-
tag("path", { class: messageClasses(placed.message, tone), d: path, "marker-end": head }),
|
|
259
|
+
tag("path", { class: messageClasses(placed.message, tone), d: path, "marker-end": head, ...attributes }),
|
|
258
260
|
pulsesFor(placed, path, slotCount, palette),
|
|
259
261
|
]),
|
|
260
|
-
pill: paintLabelPill(placed.label, pillBox(placed, ends), tone),
|
|
262
|
+
pill: paintLabelPill(placed.label, pillBox(placed, ends), tone, palette),
|
|
261
263
|
};
|
|
262
264
|
};
|
|
263
265
|
|
|
@@ -283,6 +285,7 @@ const paintFlow = (
|
|
|
283
285
|
palette: Palette,
|
|
284
286
|
): string => {
|
|
285
287
|
const activeAt = activationLookup(layout);
|
|
288
|
+
const styles = stylesFor(palette);
|
|
286
289
|
|
|
287
290
|
const lifelineBottom = layout.top + layout.height;
|
|
288
291
|
|
|
@@ -295,6 +298,7 @@ const paintFlow = (
|
|
|
295
298
|
width: coord(box.width),
|
|
296
299
|
height: coord(box.height),
|
|
297
300
|
rx: LANE_RADIUS,
|
|
301
|
+
...styles.lane,
|
|
298
302
|
});
|
|
299
303
|
});
|
|
300
304
|
|
|
@@ -306,6 +310,7 @@ const paintFlow = (
|
|
|
306
310
|
y1: coord(layout.lifelineTop),
|
|
307
311
|
x2: coord(participant.centreX),
|
|
308
312
|
y2: coord(lifelineBottom),
|
|
313
|
+
...styles.lifeline,
|
|
309
314
|
}),
|
|
310
315
|
...participant.activations.map((bar) =>
|
|
311
316
|
tag("rect", {
|
|
@@ -315,20 +320,24 @@ const paintFlow = (
|
|
|
315
320
|
width: ACTIVATION_HALF_WIDTH * 2,
|
|
316
321
|
height: coord(bar.bottom - bar.top),
|
|
317
322
|
rx: 4,
|
|
323
|
+
...styles.activation,
|
|
318
324
|
}),
|
|
319
325
|
),
|
|
320
326
|
]),
|
|
321
327
|
);
|
|
322
328
|
|
|
323
329
|
const cards = layout.participants.map((participant, index) =>
|
|
324
|
-
paintCard(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
330
|
+
paintCard(
|
|
331
|
+
{
|
|
332
|
+
node: participant.node,
|
|
333
|
+
box: participant.card,
|
|
334
|
+
showIcon: true,
|
|
335
|
+
titleSize: TITLE_SIZE,
|
|
336
|
+
row: 0,
|
|
337
|
+
laneIndex: index,
|
|
338
|
+
},
|
|
339
|
+
palette,
|
|
340
|
+
),
|
|
332
341
|
);
|
|
333
342
|
|
|
334
343
|
const lineMarkup: string[] = [];
|
package/src/svg/document.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Delta } from "@coldtea/pr-lens-schema";
|
|
2
2
|
import { assertNever } from "@coldtea/pr-lens-schema";
|
|
3
|
-
import type { Canvas } from "../bounds.js";
|
|
4
3
|
import { coord } from "../geometry.js";
|
|
4
|
+
import { SANS_STACK } from "../text.js";
|
|
5
|
+
import type { Canvas } from "../bounds.js";
|
|
5
6
|
import type { Palette } from "../theme.js";
|
|
6
|
-
import { MONO_STACK, SANS_STACK } from "../text.js";
|
|
7
7
|
import { escapeXml, lines, tag, wrap } from "./primitives.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -45,55 +45,6 @@ export const toneColour = (palette: Palette, tone: Tone): string => {
|
|
|
45
45
|
|
|
46
46
|
const TONES = ["added", "modified", "removed", "neutral"] as const satisfies readonly Tone[];
|
|
47
47
|
|
|
48
|
-
const stylesheet = (palette: Palette): string =>
|
|
49
|
-
[
|
|
50
|
-
`text{font-family:${SANS_STACK}}`,
|
|
51
|
-
`.lanebox{fill:${palette.lane}}`,
|
|
52
|
-
`.lanelabel{font-size:10px;font-weight:700;letter-spacing:.12em;fill:${palette.muted}}`,
|
|
53
|
-
`.card{fill:${palette.card};stroke:${palette.cardBorder};stroke-width:1}`,
|
|
54
|
-
`.card-added{stroke:${palette.added};stroke-opacity:.55}`,
|
|
55
|
-
`.card-modified{stroke:${palette.modified};stroke-opacity:.5}`,
|
|
56
|
-
`.ghost{opacity:.55}`,
|
|
57
|
-
`.ghost .card{stroke:${palette.removed};stroke-dasharray:4 3;stroke-opacity:.6}`,
|
|
58
|
-
`.context{opacity:.82}`,
|
|
59
|
-
`.ntitle{font-weight:600;fill:${palette.foreground}}`,
|
|
60
|
-
`.strike{text-decoration:line-through}`,
|
|
61
|
-
`.nsub{font-size:9.5px;fill:${palette.muted};font-family:${MONO_STACK}}`,
|
|
62
|
-
`.chip{fill:${palette.chip}}`,
|
|
63
|
-
`.glyph{fill:${palette.muted}}`,
|
|
64
|
-
`.glyph-stroke{stroke:${palette.muted};stroke-width:1.4;fill:none}`,
|
|
65
|
-
`.bdg text{font-size:8.5px;font-weight:700;letter-spacing:.06em}`,
|
|
66
|
-
`.bdg rect{stroke-width:1}`,
|
|
67
|
-
`.bdg-added rect{fill:${palette.addedFill};stroke:${palette.addedBorder}}`,
|
|
68
|
-
`.bdg-added text{fill:${palette.addedText}}`,
|
|
69
|
-
`.bdg-modified rect{fill:${palette.modifiedFill};stroke:${palette.modifiedBorder}}`,
|
|
70
|
-
`.bdg-modified text{fill:${palette.modifiedText}}`,
|
|
71
|
-
`.bdg-removed rect{fill:${palette.removedFill};stroke:${palette.removedBorder}}`,
|
|
72
|
-
`.bdg-removed text{fill:${palette.removedText}}`,
|
|
73
|
-
`.bdg-neutral rect{fill:${palette.neutralFill};stroke:${palette.cardBorder}}`,
|
|
74
|
-
`.bdg-neutral text{fill:${palette.muted}}`,
|
|
75
|
-
`.edge{fill:none;stroke-width:1.5}`,
|
|
76
|
-
`.edge-added{stroke:${palette.added}}`,
|
|
77
|
-
`.edge-modified{stroke:${palette.modified}}`,
|
|
78
|
-
`.edge-removed{stroke:${palette.removed};stroke-dasharray:5 4;opacity:.7}`,
|
|
79
|
-
`.edge-neutral{stroke:${palette.edge}}`,
|
|
80
|
-
`.hero{stroke-width:2.25}`,
|
|
81
|
-
`.faded{opacity:.45}`,
|
|
82
|
-
`.glow{fill:none;stroke-width:7;opacity:.14}`,
|
|
83
|
-
`.msg-self{font-size:11px;fill:${palette.foreground}}`,
|
|
84
|
-
`.lpill{fill:${palette.pill};stroke:${palette.pillBorder};stroke-width:1}`,
|
|
85
|
-
`.ltext{font-size:9.5px;font-weight:600;fill:${palette.muted}}`,
|
|
86
|
-
`.ltext-added{fill:${palette.addedText}}`,
|
|
87
|
-
`.ltext-modified{fill:${palette.modifiedText}}`,
|
|
88
|
-
`.ltext-removed{fill:${palette.removedText}}`,
|
|
89
|
-
`.cardsh{filter:drop-shadow(0 1px 2px ${palette.shadow})}`,
|
|
90
|
-
`.lifeline{stroke:${palette.lifeline};stroke-width:1;stroke-dasharray:3 4}`,
|
|
91
|
-
`.actbar{fill:${palette.addedFill};stroke:${palette.addedBorder}}`,
|
|
92
|
-
`.msg{fill:none;stroke-width:1.5}`,
|
|
93
|
-
`.msg-return{stroke-dasharray:4 3;opacity:.8}`,
|
|
94
|
-
`.msg-strong{stroke-width:2.25}`,
|
|
95
|
-
].join("");
|
|
96
|
-
|
|
97
48
|
/**
|
|
98
49
|
* Two arrowhead forms per tone, the classic sequence-diagram pair: a filled
|
|
99
50
|
* head for a message the sender waits on, an open line-form head for one it
|
|
@@ -174,10 +125,10 @@ export const svgDocument = (input: {
|
|
|
174
125
|
|
|
175
126
|
return lines([
|
|
176
127
|
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${coord(width)} ${coord(height)}" ` +
|
|
177
|
-
`width="${coord(width)}" height="${coord(height)}" role="img" aria-label="${escapeXml(title)}"
|
|
128
|
+
`width="${coord(width)}" height="${coord(height)}" role="img" aria-label="${escapeXml(title)}" ` +
|
|
129
|
+
`font-family="${escapeXml(SANS_STACK)}">`,
|
|
178
130
|
wrap("title", {}, escapeXml(title)),
|
|
179
131
|
description === undefined ? "" : wrap("desc", {}, escapeXml(description)),
|
|
180
|
-
wrap("style", {}, stylesheet(palette)),
|
|
181
132
|
defs,
|
|
182
133
|
tag("rect", { x: 0, y: 0, width: coord(width), height: coord(height), rx: 12, fill: palette.background }),
|
|
183
134
|
tag("rect", { x: 0, y: 0, width: coord(width), height: coord(height), rx: 12, fill: "url(#dots)" }),
|
package/src/svg/icons.ts
CHANGED
|
@@ -1,33 +1,23 @@
|
|
|
1
|
-
import type { NodeKind } from "@coldtea/pr-lens-schema";
|
|
2
1
|
import { assertNever } from "@coldtea/pr-lens-schema";
|
|
2
|
+
import type { NodeKind } from "@coldtea/pr-lens-schema";
|
|
3
3
|
import { coord } from "../geometry.js";
|
|
4
4
|
import { MONO_STACK } from "../text.js";
|
|
5
|
+
import type { SvgStyles } from "./styles.js";
|
|
6
|
+
import type { Attributes } from "./primitives.js";
|
|
5
7
|
import { lines, tag, textNode, wrap } from "./primitives.js";
|
|
6
8
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const glyphText = (cx: number, cy: number, content: string, style: string): string =>
|
|
9
|
+
const glyphText = (cx: number, cy: number, content: string, attributes: Attributes): string =>
|
|
10
10
|
textNode(
|
|
11
11
|
{
|
|
12
12
|
class: "glyph",
|
|
13
13
|
x: coord(cx),
|
|
14
14
|
y: coord(cy + 4.5),
|
|
15
15
|
"text-anchor": "middle",
|
|
16
|
-
|
|
16
|
+
...attributes,
|
|
17
17
|
},
|
|
18
18
|
content,
|
|
19
19
|
);
|
|
20
20
|
|
|
21
|
-
const box = (cx: number, cy: number, width: number, height: number, radius?: number): string =>
|
|
22
|
-
tag("rect", {
|
|
23
|
-
class: "glyph-stroke",
|
|
24
|
-
x: coord(cx - width / 2),
|
|
25
|
-
y: coord(cy - height / 2),
|
|
26
|
-
width: coord(width),
|
|
27
|
-
height: coord(height),
|
|
28
|
-
rx: radius === undefined ? undefined : coord(radius),
|
|
29
|
-
});
|
|
30
|
-
|
|
31
21
|
/**
|
|
32
22
|
* The chip glyph for a node's kind, centred on a point.
|
|
33
23
|
*
|
|
@@ -35,12 +25,26 @@ const box = (cx: number, cy: number, width: number, height: number, radius?: num
|
|
|
35
25
|
* is why the kind enum is coarse. Every kind draws something: a card with an
|
|
36
26
|
* empty chip reads as a rendering fault rather than as an unknown kind.
|
|
37
27
|
*/
|
|
38
|
-
const kindGlyph = (kind: NodeKind, cx: number, cy: number): string => {
|
|
28
|
+
const kindGlyph = (kind: NodeKind, cx: number, cy: number, styles: SvgStyles): string => {
|
|
29
|
+
const stroked = (path: string): string =>
|
|
30
|
+
tag("path", { class: "glyph-stroke", d: path, ...styles.glyphStroke });
|
|
31
|
+
|
|
32
|
+
const box = (cx: number, cy: number, width: number, height: number, radius?: number): string =>
|
|
33
|
+
tag("rect", {
|
|
34
|
+
class: "glyph-stroke",
|
|
35
|
+
x: coord(cx - width / 2),
|
|
36
|
+
y: coord(cy - height / 2),
|
|
37
|
+
width: coord(width),
|
|
38
|
+
height: coord(height),
|
|
39
|
+
rx: radius === undefined ? undefined : coord(radius),
|
|
40
|
+
...styles.glyphStroke,
|
|
41
|
+
});
|
|
42
|
+
|
|
39
43
|
switch (kind) {
|
|
40
44
|
case "service":
|
|
41
45
|
return lines([
|
|
42
46
|
box(cx, cy, 13, 13, 3),
|
|
43
|
-
tag("circle", { class: "glyph", cx: coord(cx), cy: coord(cy), r: 2 }),
|
|
47
|
+
tag("circle", { class: "glyph", ...styles.glyph, cx: coord(cx), cy: coord(cy), r: 2 }),
|
|
44
48
|
]);
|
|
45
49
|
case "app":
|
|
46
50
|
return lines([
|
|
@@ -48,17 +52,23 @@ const kindGlyph = (kind: NodeKind, cx: number, cy: number): string => {
|
|
|
48
52
|
stroked(`M${coord(cx - 7)},${coord(cy - 2)} h14`),
|
|
49
53
|
]);
|
|
50
54
|
case "module":
|
|
51
|
-
return glyphText(cx, cy, "{ }",
|
|
55
|
+
return glyphText(cx, cy, "{ }", { ...styles.glyph, "font-size": 11, "font-family": MONO_STACK });
|
|
52
56
|
case "function":
|
|
53
|
-
return glyphText(cx, cy, "ƒ",
|
|
57
|
+
return glyphText(cx, cy, "ƒ", {
|
|
58
|
+
...styles.glyph,
|
|
59
|
+
"font-size": 14,
|
|
60
|
+
"font-style": "italic",
|
|
61
|
+
"font-family": "Georgia,serif",
|
|
62
|
+
});
|
|
54
63
|
case "route":
|
|
55
64
|
return tag("path", {
|
|
56
65
|
class: "glyph",
|
|
66
|
+
...styles.glyph,
|
|
57
67
|
d: `M${coord(cx - 7)},${coord(cy - 6)} L${coord(cx + 7)},${coord(cy)} L${coord(cx - 7)},${coord(cy + 6)} Z`,
|
|
58
68
|
});
|
|
59
69
|
case "job":
|
|
60
70
|
return lines([
|
|
61
|
-
tag("circle", { class: "glyph-stroke", cx: coord(cx), cy: coord(cy), r: 6.5 }),
|
|
71
|
+
tag("circle", { class: "glyph-stroke", ...styles.glyphStroke, cx: coord(cx), cy: coord(cy), r: 6.5 }),
|
|
62
72
|
stroked(`M${coord(cx)},${coord(cy - 3.5)} V${coord(cy)} H${coord(cx + 3)}`),
|
|
63
73
|
]);
|
|
64
74
|
case "queue":
|
|
@@ -69,7 +79,7 @@ const kindGlyph = (kind: NodeKind, cx: number, cy: number): string => {
|
|
|
69
79
|
]);
|
|
70
80
|
case "datastore":
|
|
71
81
|
return lines([
|
|
72
|
-
tag("ellipse", { class: "glyph-stroke", cx: coord(cx), cy: coord(cy - 4.5), rx: 6.5, ry: 2.6 }),
|
|
82
|
+
tag("ellipse", { class: "glyph-stroke", ...styles.glyphStroke, cx: coord(cx), cy: coord(cy - 4.5), rx: 6.5, ry: 2.6 }),
|
|
73
83
|
stroked(
|
|
74
84
|
`M${coord(cx - 6.5)},${coord(cy - 4.5)} v9 a6.5,2.6 0 0 0 13 0 v-9`,
|
|
75
85
|
),
|
|
@@ -77,6 +87,7 @@ const kindGlyph = (kind: NodeKind, cx: number, cy: number): string => {
|
|
|
77
87
|
case "cache":
|
|
78
88
|
return tag("path", {
|
|
79
89
|
class: "glyph",
|
|
90
|
+
...styles.glyph,
|
|
80
91
|
d: `M${coord(cx + 1)},${coord(cy - 7)} L${coord(cx - 6)},${coord(cy + 1)} H${coord(cx - 1)} L${coord(cx - 1)},${coord(cy + 7)} L${coord(cx + 6)},${coord(cy - 1)} H${coord(cx + 1)} Z`,
|
|
81
92
|
});
|
|
82
93
|
case "external":
|
|
@@ -93,8 +104,8 @@ const kindGlyph = (kind: NodeKind, cx: number, cy: number): string => {
|
|
|
93
104
|
return lines([
|
|
94
105
|
stroked(`M${coord(cx - 7)},${coord(cy - 4)} h14`),
|
|
95
106
|
stroked(`M${coord(cx - 7)},${coord(cy + 4)} h14`),
|
|
96
|
-
tag("circle", { class: "glyph", cx: coord(cx - 2), cy: coord(cy - 4), r: 2.2 }),
|
|
97
|
-
tag("circle", { class: "glyph", cx: coord(cx + 3), cy: coord(cy + 4), r: 2.2 }),
|
|
107
|
+
tag("circle", { class: "glyph", ...styles.glyph, cx: coord(cx - 2), cy: coord(cy - 4), r: 2.2 }),
|
|
108
|
+
tag("circle", { class: "glyph", ...styles.glyph, cx: coord(cx + 3), cy: coord(cy + 4), r: 2.2 }),
|
|
98
109
|
]);
|
|
99
110
|
case "test":
|
|
100
111
|
return stroked(`M${coord(cx - 6)},${coord(cy)} l4,4 l8,-8`);
|
|
@@ -108,14 +119,14 @@ const kindGlyph = (kind: NodeKind, cx: number, cy: number): string => {
|
|
|
108
119
|
]);
|
|
109
120
|
case "other":
|
|
110
121
|
return lines([
|
|
111
|
-
tag("circle", { class: "glyph", cx: coord(cx - 5), cy: coord(cy), r: 1.8 }),
|
|
112
|
-
tag("circle", { class: "glyph", cx: coord(cx), cy: coord(cy), r: 1.8 }),
|
|
113
|
-
tag("circle", { class: "glyph", cx: coord(cx + 5), cy: coord(cy), r: 1.8 }),
|
|
122
|
+
tag("circle", { class: "glyph", ...styles.glyph, cx: coord(cx - 5), cy: coord(cy), r: 1.8 }),
|
|
123
|
+
tag("circle", { class: "glyph", ...styles.glyph, cx: coord(cx), cy: coord(cy), r: 1.8 }),
|
|
124
|
+
tag("circle", { class: "glyph", ...styles.glyph, cx: coord(cx + 5), cy: coord(cy), r: 1.8 }),
|
|
114
125
|
]);
|
|
115
126
|
default:
|
|
116
127
|
return assertNever(kind, "Unhandled node kind");
|
|
117
128
|
}
|
|
118
129
|
};
|
|
119
130
|
|
|
120
|
-
export const glyphGroup = (kind: NodeKind, cx: number, cy: number): string =>
|
|
121
|
-
wrap("g", {}, kindGlyph(kind, cx, cy));
|
|
131
|
+
export const glyphGroup = (kind: NodeKind, cx: number, cy: number, styles: SvgStyles): string =>
|
|
132
|
+
wrap("g", {}, kindGlyph(kind, cx, cy, styles));
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { assertNever } from "@coldtea/pr-lens-schema";
|
|
2
|
+
import type { Delta, FlowMessage, GraphEdge } from "@coldtea/pr-lens-schema";
|
|
3
|
+
import { MONO_STACK } from "../text.js";
|
|
4
|
+
import type { Palette } from "../theme.js";
|
|
5
|
+
import type { Attributes } from "./primitives.js";
|
|
6
|
+
import { toneColour, toneFor, type Tone } from "./document.js";
|
|
7
|
+
|
|
8
|
+
// GitHub's attachment viewer can ignore CSS, so appearance lives on the elements.
|
|
9
|
+
export const stylesFor = (palette: Palette) => ({
|
|
10
|
+
lane: { fill: palette.lane },
|
|
11
|
+
laneLabel: { "font-size": 10, "font-weight": 700, "letter-spacing": ".12em", fill: palette.muted },
|
|
12
|
+
title: { "font-weight": 600, fill: palette.foreground },
|
|
13
|
+
subtitle: { "font-size": 9.5, fill: palette.muted, "font-family": MONO_STACK },
|
|
14
|
+
chip: { fill: palette.chip },
|
|
15
|
+
glyph: { fill: palette.muted },
|
|
16
|
+
glyphStroke: { stroke: palette.muted, "stroke-width": 1.4, fill: "none" },
|
|
17
|
+
badgeText: { "font-size": 8.5, "font-weight": 700, "letter-spacing": ".06em" },
|
|
18
|
+
glow: { fill: "none", "stroke-width": 7, opacity: 0.14 },
|
|
19
|
+
pill: { fill: palette.pill, stroke: palette.pillBorder, "stroke-width": 1 },
|
|
20
|
+
pillText: { "font-size": 9.5, "font-weight": 600 },
|
|
21
|
+
lifeline: { stroke: palette.lifeline, "stroke-width": 1, "stroke-dasharray": "3 4" },
|
|
22
|
+
activation: { fill: palette.addedFill, stroke: palette.addedBorder },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export type SvgStyles = ReturnType<typeof stylesFor>;
|
|
26
|
+
|
|
27
|
+
const cardStroke = (delta: Delta, palette: Palette): Attributes => {
|
|
28
|
+
switch (delta) {
|
|
29
|
+
case "added":
|
|
30
|
+
return { stroke: palette.added, "stroke-opacity": 0.55 };
|
|
31
|
+
case "modified":
|
|
32
|
+
return { stroke: palette.modified, "stroke-opacity": 0.5 };
|
|
33
|
+
case "removed":
|
|
34
|
+
return { stroke: palette.removed, "stroke-dasharray": "4 3", "stroke-opacity": 0.6 };
|
|
35
|
+
case "unchanged":
|
|
36
|
+
return { stroke: palette.cardBorder };
|
|
37
|
+
default:
|
|
38
|
+
return assertNever(delta);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const cardAttributes = (delta: Delta, palette: Palette): Attributes => ({
|
|
43
|
+
fill: palette.card,
|
|
44
|
+
"stroke-width": 1,
|
|
45
|
+
...cardStroke(delta, palette),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const cardOpacity = (delta: Delta): number | undefined => {
|
|
49
|
+
switch (delta) {
|
|
50
|
+
case "removed":
|
|
51
|
+
return 0.55;
|
|
52
|
+
case "unchanged":
|
|
53
|
+
return 0.82;
|
|
54
|
+
case "added":
|
|
55
|
+
case "modified":
|
|
56
|
+
return undefined;
|
|
57
|
+
default:
|
|
58
|
+
return assertNever(delta);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const cardGroupAttributes = (delta: Delta, palette: Palette): Attributes => ({
|
|
63
|
+
filter: `drop-shadow(0 1px 2px ${palette.shadow})`,
|
|
64
|
+
opacity: cardOpacity(delta),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const badgeColours = (tone: Tone, palette: Palette) => {
|
|
68
|
+
switch (tone) {
|
|
69
|
+
case "added":
|
|
70
|
+
return { fill: palette.addedFill, stroke: palette.addedBorder, text: palette.addedText };
|
|
71
|
+
case "modified":
|
|
72
|
+
return { fill: palette.modifiedFill, stroke: palette.modifiedBorder, text: palette.modifiedText };
|
|
73
|
+
case "removed":
|
|
74
|
+
return { fill: palette.removedFill, stroke: palette.removedBorder, text: palette.removedText };
|
|
75
|
+
case "neutral":
|
|
76
|
+
return { fill: palette.neutralFill, stroke: palette.cardBorder, text: palette.muted };
|
|
77
|
+
default:
|
|
78
|
+
return assertNever(tone);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const strokeAttributes = (delta: Delta, palette: Palette): Attributes => ({
|
|
83
|
+
fill: "none",
|
|
84
|
+
stroke: toneColour(palette, toneFor(delta)),
|
|
85
|
+
"stroke-width": 1.5,
|
|
86
|
+
"stroke-dasharray": delta === "removed" ? "5 4" : undefined,
|
|
87
|
+
opacity: delta === "removed" ? 0.7 : undefined,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export const edgeAttributes = (edge: GraphEdge, palette: Palette): Attributes => {
|
|
91
|
+
const attributes = {
|
|
92
|
+
...strokeAttributes(edge.delta, palette),
|
|
93
|
+
...(edge.delta === "unchanged" ? { opacity: 0.82 } : {}),
|
|
94
|
+
};
|
|
95
|
+
switch (edge.emphasis) {
|
|
96
|
+
case "hero":
|
|
97
|
+
return { ...attributes, "stroke-width": 2.25 };
|
|
98
|
+
case "muted":
|
|
99
|
+
return { ...attributes, opacity: 0.45 };
|
|
100
|
+
case "normal":
|
|
101
|
+
return attributes;
|
|
102
|
+
default:
|
|
103
|
+
return assertNever(edge.emphasis);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const messageAttributes = (message: FlowMessage, palette: Palette): Attributes => {
|
|
108
|
+
const attributes = {
|
|
109
|
+
...strokeAttributes(message.delta, palette),
|
|
110
|
+
"stroke-width": message.delta === "added" ? 2.25 : 1.5,
|
|
111
|
+
};
|
|
112
|
+
switch (message.kind) {
|
|
113
|
+
case "return":
|
|
114
|
+
return { ...attributes, "stroke-dasharray": "4 3", opacity: 0.8 };
|
|
115
|
+
case "sync":
|
|
116
|
+
case "async":
|
|
117
|
+
case "self":
|
|
118
|
+
return attributes;
|
|
119
|
+
default:
|
|
120
|
+
return assertNever(message.kind);
|
|
121
|
+
}
|
|
122
|
+
};
|
package/src/version.ts
CHANGED