@coldtea/pr-lens-renderer 0.2.2 → 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/layout/architecture.d.ts +7 -1
- package/dist/layout/architecture.d.ts.map +1 -1
- package/dist/layout/architecture.js +11 -2
- package/dist/layout/architecture.js.map +1 -1
- 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/text.d.ts.map +1 -1
- package/dist/text.js +21 -3
- package/dist/text.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/layout/architecture.ts +10 -2
- 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/text.ts +27 -3
- package/src/version.ts +1 -1
package/src/svg/architecture.ts
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
|
-
import type { GraphEdge, GraphNode, LayoutHints } from "@coldtea/pr-lens-schema";
|
|
2
1
|
import { assertNever } from "@coldtea/pr-lens-schema";
|
|
2
|
+
import type { GraphEdge, GraphNode, LayoutHints } from "@coldtea/pr-lens-schema";
|
|
3
|
+
import { truncate } from "../text.js";
|
|
4
|
+
import { glyphGroup } from "./icons.js";
|
|
5
|
+
import type { Palette } from "../theme.js";
|
|
6
|
+
import { DIAGRAM_MARGIN } from "../design.js";
|
|
7
|
+
import { travellingPulses } from "./pulse.js";
|
|
8
|
+
import type { ScopedGraph } from "../scope.js";
|
|
9
|
+
import { canvasFor, union } from "../bounds.js";
|
|
10
|
+
import { coord, type Box } from "../geometry.js";
|
|
11
|
+
import { placeLabelPills } from "../layout/labels.js";
|
|
12
|
+
import { relieveCongestion } from "../layout/congestion.js";
|
|
13
|
+
import { lines, tag, textNode, wrap } from "./primitives.js";
|
|
14
|
+
import { curveBounds, type RoutedEdge } from "../layout/edges.js";
|
|
15
|
+
import { atlasBoxes, emptyAtlas, type RenderAtlas } from "../atlas.js";
|
|
16
|
+
import { markerFor, shifted, toneColour, toneFor, type Tone } from "./document.js";
|
|
17
|
+
import {
|
|
18
|
+
badgeColours,
|
|
19
|
+
cardAttributes,
|
|
20
|
+
cardGroupAttributes,
|
|
21
|
+
edgeAttributes,
|
|
22
|
+
stylesFor,
|
|
23
|
+
} from "./styles.js";
|
|
24
|
+
import {
|
|
25
|
+
badgeRow,
|
|
26
|
+
badgeWidth,
|
|
27
|
+
cardBadges,
|
|
28
|
+
cardTextWidth,
|
|
29
|
+
deltaBadgeText,
|
|
30
|
+
laneHeaderText,
|
|
31
|
+
type PlacedNode,
|
|
32
|
+
} from "../layout/architecture.js";
|
|
3
33
|
import {
|
|
4
34
|
BADGE_GAP,
|
|
5
35
|
BADGE_HEIGHT,
|
|
@@ -16,35 +46,13 @@ import {
|
|
|
16
46
|
LANE_RADIUS,
|
|
17
47
|
SUBTITLE_SIZE,
|
|
18
48
|
} from "../design.js";
|
|
19
|
-
import { atlasBoxes, emptyAtlas, type RenderAtlas } from "../atlas.js";
|
|
20
|
-
import { canvasFor, union } from "../bounds.js";
|
|
21
|
-
import { DIAGRAM_MARGIN } from "../design.js";
|
|
22
|
-
import { coord, type Box } from "../geometry.js";
|
|
23
|
-
import {
|
|
24
|
-
badgeRow,
|
|
25
|
-
badgeWidth,
|
|
26
|
-
cardBadges,
|
|
27
|
-
cardTextWidth,
|
|
28
|
-
deltaBadgeText,
|
|
29
|
-
laneHeaderText,
|
|
30
|
-
type PlacedNode,
|
|
31
|
-
} from "../layout/architecture.js";
|
|
32
|
-
import { relieveCongestion } from "../layout/congestion.js";
|
|
33
|
-
import { curveBounds, type RoutedEdge } from "../layout/edges.js";
|
|
34
|
-
import { placeLabelPills } from "../layout/labels.js";
|
|
35
|
-
import type { ScopedGraph } from "../scope.js";
|
|
36
|
-
import { truncate } from "../text.js";
|
|
37
|
-
import type { Palette } from "../theme.js";
|
|
38
|
-
import { markerFor, shifted, toneColour, toneFor, type Tone } from "./document.js";
|
|
39
|
-
import { glyphGroup } from "./icons.js";
|
|
40
|
-
import { lines, tag, textNode, wrap } from "./primitives.js";
|
|
41
|
-
import { travellingPulses } from "./pulse.js";
|
|
42
49
|
|
|
43
50
|
const badgeTone = (node: GraphNode, text: string): Tone =>
|
|
44
51
|
text === deltaBadgeText(node.delta) ? toneFor(node.delta) : "neutral";
|
|
45
52
|
|
|
46
53
|
/** The badge row, laid left to right across the strip the layout reserved. */
|
|
47
|
-
const paintBadges = (placed: PlacedNode): string => {
|
|
54
|
+
const paintBadges = (placed: PlacedNode, palette: Palette): string => {
|
|
55
|
+
const styles = stylesFor(palette);
|
|
48
56
|
const row = badgeRow(placed);
|
|
49
57
|
if (row === undefined) return "";
|
|
50
58
|
|
|
@@ -52,6 +60,7 @@ const paintBadges = (placed: PlacedNode): string => {
|
|
|
52
60
|
return lines(
|
|
53
61
|
row.badges.map((text) => {
|
|
54
62
|
const width = badgeWidth(text);
|
|
63
|
+
const colours = badgeColours(badgeTone(placed.node, text), palette);
|
|
55
64
|
const painted = wrap(
|
|
56
65
|
"g",
|
|
57
66
|
{ class: `bdg bdg-${badgeTone(placed.node, text)}` },
|
|
@@ -61,12 +70,17 @@ const paintBadges = (placed: PlacedNode): string => {
|
|
|
61
70
|
width: coord(width),
|
|
62
71
|
height: BADGE_HEIGHT,
|
|
63
72
|
rx: BADGE_RADIUS,
|
|
73
|
+
fill: colours.fill,
|
|
74
|
+
stroke: colours.stroke,
|
|
75
|
+
"stroke-width": 1,
|
|
64
76
|
}) +
|
|
65
77
|
textNode(
|
|
66
78
|
{
|
|
67
79
|
x: coord(x + width / 2),
|
|
68
80
|
y: coord(row.box.y + BADGE_HEIGHT / 2 + 3),
|
|
69
81
|
"text-anchor": "middle",
|
|
82
|
+
...styles.badgeText,
|
|
83
|
+
fill: colours.text,
|
|
70
84
|
},
|
|
71
85
|
text,
|
|
72
86
|
),
|
|
@@ -91,7 +105,8 @@ const cardOutlineClass = (node: GraphNode): string => {
|
|
|
91
105
|
}
|
|
92
106
|
};
|
|
93
107
|
|
|
94
|
-
export const paintCard = (placed: PlacedNode): string => {
|
|
108
|
+
export const paintCard = (placed: PlacedNode, palette: Palette): string => {
|
|
109
|
+
const styles = stylesFor(palette);
|
|
95
110
|
const { node, box, showIcon, titleSize } = placed;
|
|
96
111
|
const textX = box.x + CARD_PADDING_X + (showIcon ? ICON_CHIP_SIZE + ICON_CHIP_GAP : 0);
|
|
97
112
|
const textWidth = cardTextWidth(box.width, showIcon);
|
|
@@ -106,11 +121,13 @@ export const paintCard = (placed: PlacedNode): string => {
|
|
|
106
121
|
width: ICON_CHIP_SIZE,
|
|
107
122
|
height: ICON_CHIP_SIZE,
|
|
108
123
|
rx: ICON_CHIP_RADIUS,
|
|
124
|
+
...styles.chip,
|
|
109
125
|
}) +
|
|
110
126
|
glyphGroup(
|
|
111
127
|
node.kind,
|
|
112
128
|
box.x + CARD_PADDING_X + ICON_CHIP_SIZE / 2,
|
|
113
129
|
box.y + box.height / 2,
|
|
130
|
+
styles,
|
|
114
131
|
)
|
|
115
132
|
: "";
|
|
116
133
|
|
|
@@ -120,6 +137,8 @@ export const paintCard = (placed: PlacedNode): string => {
|
|
|
120
137
|
x: coord(textX),
|
|
121
138
|
y: coord(titleBaseline),
|
|
122
139
|
"font-size": titleSize,
|
|
140
|
+
...styles.title,
|
|
141
|
+
"text-decoration": node.delta === "removed" ? "line-through" : undefined,
|
|
123
142
|
},
|
|
124
143
|
truncate(node.label, "sans-bold", titleSize, textWidth),
|
|
125
144
|
);
|
|
@@ -128,7 +147,7 @@ export const paintCard = (placed: PlacedNode): string => {
|
|
|
128
147
|
node.subtitle === undefined
|
|
129
148
|
? ""
|
|
130
149
|
: textNode(
|
|
131
|
-
{ class: "nsub", x: coord(textX), y: coord(box.y + 45) },
|
|
150
|
+
{ class: "nsub", x: coord(textX), y: coord(box.y + 45), ...styles.subtitle },
|
|
132
151
|
truncate(node.subtitle, "mono", SUBTITLE_SIZE, textWidth),
|
|
133
152
|
);
|
|
134
153
|
|
|
@@ -137,7 +156,7 @@ export const paintCard = (placed: PlacedNode): string => {
|
|
|
137
156
|
|
|
138
157
|
return wrap(
|
|
139
158
|
"g",
|
|
140
|
-
{ class: groupClass },
|
|
159
|
+
{ class: groupClass, ...cardGroupAttributes(node.delta, palette) },
|
|
141
160
|
lines([
|
|
142
161
|
tag("rect", {
|
|
143
162
|
class: cardOutlineClass(node),
|
|
@@ -146,11 +165,12 @@ export const paintCard = (placed: PlacedNode): string => {
|
|
|
146
165
|
width: coord(box.width),
|
|
147
166
|
height: coord(box.height),
|
|
148
167
|
rx: CARD_RADIUS,
|
|
168
|
+
...cardAttributes(node.delta, palette),
|
|
149
169
|
}),
|
|
150
170
|
chip,
|
|
151
171
|
title,
|
|
152
172
|
subtitle,
|
|
153
|
-
paintBadges(placed),
|
|
173
|
+
paintBadges(placed, palette),
|
|
154
174
|
]),
|
|
155
175
|
);
|
|
156
176
|
};
|
|
@@ -181,6 +201,7 @@ const paintEdge = (
|
|
|
181
201
|
label: Box | undefined,
|
|
182
202
|
): { markup: string; pill: string } => {
|
|
183
203
|
const { edge, path } = routed;
|
|
204
|
+
const styles = stylesFor(palette);
|
|
184
205
|
const tone = toneFor(edge.delta);
|
|
185
206
|
const hero = edge.emphasis === "hero";
|
|
186
207
|
|
|
@@ -190,24 +211,25 @@ const paintEdge = (
|
|
|
190
211
|
if (edge.delta === "unchanged") classes.push("context");
|
|
191
212
|
|
|
192
213
|
const glow = hero
|
|
193
|
-
? tag("path", { class: "glow", stroke: toneColour(palette, tone), d: path })
|
|
214
|
+
? tag("path", { class: "glow", stroke: toneColour(palette, tone), d: path, ...styles.glow })
|
|
194
215
|
: "";
|
|
195
216
|
|
|
196
217
|
return {
|
|
197
218
|
markup: lines([
|
|
198
219
|
glow,
|
|
199
|
-
tag("path", { class: classes.join(" "), d: path, "marker-end": markerFor(tone) }),
|
|
220
|
+
tag("path", { class: classes.join(" "), d: path, "marker-end": markerFor(tone), ...edgeAttributes(edge, palette) }),
|
|
200
221
|
pulses(edge, path, palette),
|
|
201
222
|
]),
|
|
202
223
|
pill:
|
|
203
224
|
label === undefined || edge.label === undefined
|
|
204
225
|
? ""
|
|
205
|
-
: paintLabelPill(edge.label, label, tone),
|
|
226
|
+
: paintLabelPill(edge.label, label, tone, palette),
|
|
206
227
|
};
|
|
207
228
|
};
|
|
208
229
|
|
|
209
|
-
export const paintLabelPill = (text: string, box: Box, tone: Tone): string =>
|
|
210
|
-
|
|
230
|
+
export const paintLabelPill = (text: string, box: Box, tone: Tone, palette: Palette): string => {
|
|
231
|
+
const styles = stylesFor(palette);
|
|
232
|
+
return wrap(
|
|
211
233
|
"g",
|
|
212
234
|
{},
|
|
213
235
|
tag("rect", {
|
|
@@ -217,6 +239,7 @@ export const paintLabelPill = (text: string, box: Box, tone: Tone): string =>
|
|
|
217
239
|
width: coord(box.width),
|
|
218
240
|
height: coord(box.height),
|
|
219
241
|
rx: box.height / 2,
|
|
242
|
+
...styles.pill,
|
|
220
243
|
}) +
|
|
221
244
|
textNode(
|
|
222
245
|
{
|
|
@@ -224,10 +247,13 @@ export const paintLabelPill = (text: string, box: Box, tone: Tone): string =>
|
|
|
224
247
|
x: coord(box.x + box.width / 2),
|
|
225
248
|
y: coord(box.y + box.height / 2 + 3.5),
|
|
226
249
|
"text-anchor": "middle",
|
|
250
|
+
...styles.pillText,
|
|
251
|
+
fill: badgeColours(tone, palette).text,
|
|
227
252
|
},
|
|
228
253
|
text,
|
|
229
254
|
),
|
|
230
255
|
);
|
|
256
|
+
};
|
|
231
257
|
|
|
232
258
|
export type ArchitecturePainting = {
|
|
233
259
|
width: number;
|
|
@@ -242,6 +268,7 @@ export const paintArchitecture = (
|
|
|
242
268
|
palette: Palette,
|
|
243
269
|
): ArchitecturePainting => {
|
|
244
270
|
const { layout, routed } = relieveCongestion(graph, hints);
|
|
271
|
+
const styles = stylesFor(palette);
|
|
245
272
|
const pills = placeLabelPills(routed);
|
|
246
273
|
|
|
247
274
|
const drawn: Box[] = layout.nodes.flatMap((node) => {
|
|
@@ -267,9 +294,10 @@ export const paintArchitecture = (
|
|
|
267
294
|
width: coord(box.width),
|
|
268
295
|
height: coord(box.height),
|
|
269
296
|
rx: LANE_RADIUS,
|
|
297
|
+
...styles.lane,
|
|
270
298
|
}),
|
|
271
299
|
textNode(
|
|
272
|
-
{ class: "lanelabel", x: coord(box.x + LANE_PADDING_X), y: LANE_HEADER_BASELINE },
|
|
300
|
+
{ class: "lanelabel", x: coord(box.x + LANE_PADDING_X), y: LANE_HEADER_BASELINE, ...styles.laneLabel },
|
|
273
301
|
laneHeaderText(lane),
|
|
274
302
|
),
|
|
275
303
|
]),
|
|
@@ -288,7 +316,7 @@ export const paintArchitecture = (
|
|
|
288
316
|
const painted = lines([
|
|
289
317
|
wrap("g", {}, lines(lanes)),
|
|
290
318
|
wrap("g", {}, lines(edgeMarkup)),
|
|
291
|
-
wrap("g", {}, lines(layout.nodes.map(paintCard))),
|
|
319
|
+
wrap("g", {}, lines(layout.nodes.map((node) => paintCard(node, palette)))),
|
|
292
320
|
wrap("g", {}, lines(pillMarkup)),
|
|
293
321
|
]);
|
|
294
322
|
|
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));
|