@coldtea/pr-lens-renderer 0.3.6 → 0.4.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/dist/atlas.d.ts +2 -0
- package/dist/atlas.d.ts.map +1 -1
- package/dist/atlas.js +1 -1
- package/dist/atlas.js.map +1 -1
- package/dist/corrections.d.ts.map +1 -1
- package/dist/corrections.js +7 -1
- package/dist/corrections.js.map +1 -1
- package/dist/design.d.ts +25 -0
- package/dist/design.d.ts.map +1 -1
- package/dist/design.js +25 -0
- package/dist/design.js.map +1 -1
- package/dist/layout/architecture.d.ts +6 -2
- package/dist/layout/architecture.d.ts.map +1 -1
- package/dist/layout/architecture.js +12 -19
- package/dist/layout/architecture.js.map +1 -1
- package/dist/layout/datamodel.d.ts +70 -0
- package/dist/layout/datamodel.d.ts.map +1 -0
- package/dist/layout/datamodel.js +381 -0
- package/dist/layout/datamodel.js.map +1 -0
- package/dist/layout/edges.d.ts +8 -0
- package/dist/layout/edges.d.ts.map +1 -1
- package/dist/layout/edges.js +2 -2
- package/dist/layout/edges.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +18 -1
- package/dist/render.js.map +1 -1
- package/dist/scope.d.ts +4 -1
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +28 -2
- package/dist/scope.js.map +1 -1
- package/dist/svg/architecture.d.ts +5 -2
- package/dist/svg/architecture.d.ts.map +1 -1
- package/dist/svg/architecture.js +10 -11
- package/dist/svg/architecture.js.map +1 -1
- package/dist/svg/datamodel.d.ts +11 -0
- package/dist/svg/datamodel.d.ts.map +1 -0
- package/dist/svg/datamodel.js +252 -0
- package/dist/svg/datamodel.js.map +1 -0
- package/dist/svg/icons.d.ts +2 -0
- package/dist/svg/icons.d.ts.map +1 -1
- package/dist/svg/icons.js +14 -0
- package/dist/svg/icons.js.map +1 -1
- package/dist/svg/styles.d.ts +3 -1
- package/dist/svg/styles.d.ts.map +1 -1
- package/dist/svg/styles.js +6 -0
- package/dist/svg/styles.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/atlas.ts +3 -1
- package/src/corrections.ts +7 -1
- package/src/design.ts +26 -0
- package/src/layout/architecture.ts +16 -10
- package/src/layout/datamodel.ts +524 -0
- package/src/layout/edges.ts +2 -2
- package/src/render.ts +18 -1
- package/src/scope.ts +46 -3
- package/src/svg/architecture.ts +12 -13
- package/src/svg/datamodel.ts +344 -0
- package/src/svg/icons.ts +20 -0
- package/src/svg/styles.ts +8 -1
- package/src/version.ts +1 -1
package/src/svg/architecture.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assertNever } from "@coldtea/pr-lens-schema";
|
|
2
|
-
import type {
|
|
2
|
+
import type { Delta, GraphEdge, LayoutHints } from "@coldtea/pr-lens-schema";
|
|
3
3
|
import { truncate } from "../text.js";
|
|
4
4
|
import { glyphGroup } from "./icons.js";
|
|
5
5
|
import type { Palette } from "../theme.js";
|
|
@@ -23,11 +23,11 @@ import {
|
|
|
23
23
|
import {
|
|
24
24
|
badgeRow,
|
|
25
25
|
badgeWidth,
|
|
26
|
-
cardBadges,
|
|
27
26
|
cardTextWidth,
|
|
28
27
|
deltaBadgeText,
|
|
29
28
|
laneHeaderText,
|
|
30
29
|
occupiedBoxes,
|
|
30
|
+
type BadgeRow,
|
|
31
31
|
type PlacedNode,
|
|
32
32
|
} from "../layout/architecture.js";
|
|
33
33
|
import {
|
|
@@ -47,23 +47,22 @@ import {
|
|
|
47
47
|
SUBTITLE_SIZE,
|
|
48
48
|
} from "../design.js";
|
|
49
49
|
|
|
50
|
-
const badgeTone = (
|
|
51
|
-
text === deltaBadgeText(
|
|
50
|
+
const badgeTone = (delta: Delta, text: string): Tone =>
|
|
51
|
+
text === deltaBadgeText(delta) ? toneFor(delta) : "neutral";
|
|
52
52
|
|
|
53
53
|
/** The badge row, laid left to right across the strip the layout reserved. */
|
|
54
|
-
const
|
|
54
|
+
export const paintBadgeStrip = (row: BadgeRow | undefined, delta: Delta, palette: Palette): string => {
|
|
55
55
|
const styles = stylesFor(palette);
|
|
56
|
-
const row = badgeRow(placed);
|
|
57
56
|
if (row === undefined) return "";
|
|
58
57
|
|
|
59
58
|
let x = row.box.x;
|
|
60
59
|
return lines(
|
|
61
60
|
row.badges.map((text) => {
|
|
62
61
|
const width = badgeWidth(text);
|
|
63
|
-
const colours = badgeColours(badgeTone(
|
|
62
|
+
const colours = badgeColours(badgeTone(delta, text), palette);
|
|
64
63
|
const painted = wrap(
|
|
65
64
|
"g",
|
|
66
|
-
{ class: `bdg bdg-${badgeTone(
|
|
65
|
+
{ class: `bdg bdg-${badgeTone(delta, text)}` },
|
|
67
66
|
tag("rect", {
|
|
68
67
|
x: coord(x),
|
|
69
68
|
y: coord(row.box.y),
|
|
@@ -91,8 +90,8 @@ const paintBadges = (placed: PlacedNode, palette: Palette): string => {
|
|
|
91
90
|
);
|
|
92
91
|
};
|
|
93
92
|
|
|
94
|
-
const cardOutlineClass = (
|
|
95
|
-
switch (
|
|
93
|
+
export const cardOutlineClass = (delta: Delta): string => {
|
|
94
|
+
switch (delta) {
|
|
96
95
|
case "added":
|
|
97
96
|
return "card card-added";
|
|
98
97
|
case "modified":
|
|
@@ -101,7 +100,7 @@ const cardOutlineClass = (node: GraphNode): string => {
|
|
|
101
100
|
case "unchanged":
|
|
102
101
|
return "card";
|
|
103
102
|
default:
|
|
104
|
-
return assertNever(
|
|
103
|
+
return assertNever(delta, "Unhandled delta");
|
|
105
104
|
}
|
|
106
105
|
};
|
|
107
106
|
|
|
@@ -159,7 +158,7 @@ export const paintCard = (placed: PlacedNode, palette: Palette): string => {
|
|
|
159
158
|
{ class: groupClass, ...cardGroupAttributes(node.delta, palette) },
|
|
160
159
|
lines([
|
|
161
160
|
tag("rect", {
|
|
162
|
-
class: cardOutlineClass(node),
|
|
161
|
+
class: cardOutlineClass(node.delta),
|
|
163
162
|
x: coord(box.x),
|
|
164
163
|
y: coord(box.y),
|
|
165
164
|
width: coord(box.width),
|
|
@@ -170,7 +169,7 @@ export const paintCard = (placed: PlacedNode, palette: Palette): string => {
|
|
|
170
169
|
chip,
|
|
171
170
|
title,
|
|
172
171
|
subtitle,
|
|
173
|
-
|
|
172
|
+
paintBadgeStrip(badgeRow(placed), node.delta, palette),
|
|
174
173
|
]),
|
|
175
174
|
);
|
|
176
175
|
};
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { assertNever } from "@coldtea/pr-lens-schema";
|
|
2
|
+
import type { ColumnKey, Lane } from "@coldtea/pr-lens-schema";
|
|
3
|
+
import { measure, truncate } from "../text.js";
|
|
4
|
+
import type { Palette } from "../theme.js";
|
|
5
|
+
import type { ScopedGraph } from "../scope.js";
|
|
6
|
+
import { canvasFor, union } from "../bounds.js";
|
|
7
|
+
import { coord, type Box } from "../geometry.js";
|
|
8
|
+
import { curveBounds } from "../layout/edges.js";
|
|
9
|
+
import { lines, tag, textNode, wrap } from "./primitives.js";
|
|
10
|
+
import { tableGlyph } from "./icons.js";
|
|
11
|
+
import { atlasBoxes, emptyAtlas, type RenderAtlas } from "../atlas.js";
|
|
12
|
+
import { markerFor, shifted, toneColour, toneFor } from "./document.js";
|
|
13
|
+
import { cardAttributes, cardGroupAttributes, relationAttributes, stylesFor } from "./styles.js";
|
|
14
|
+
import { cardOutlineClass, paintBadgeStrip, paintLabelPill } from "./architecture.js";
|
|
15
|
+
import { badgeStrip, badgesFor, cardTextWidth, laneHeaderText } from "../layout/architecture.js";
|
|
16
|
+
import {
|
|
17
|
+
layoutDataModel,
|
|
18
|
+
routeRelations,
|
|
19
|
+
type PlacedTable,
|
|
20
|
+
type RoutedRelation,
|
|
21
|
+
} from "../layout/datamodel.js";
|
|
22
|
+
import {
|
|
23
|
+
CARD_HEIGHT_WITH_SUBTITLE,
|
|
24
|
+
CARD_PADDING_X,
|
|
25
|
+
CARD_RADIUS,
|
|
26
|
+
DIAGRAM_MARGIN,
|
|
27
|
+
ICON_CHIP_GAP,
|
|
28
|
+
ICON_CHIP_RADIUS,
|
|
29
|
+
ICON_CHIP_SIZE,
|
|
30
|
+
KEY_CHIP_GAP,
|
|
31
|
+
KEY_CHIP_HEIGHT,
|
|
32
|
+
KEY_CHIP_WIDTH,
|
|
33
|
+
LANE_HEADER_BASELINE,
|
|
34
|
+
LANE_PADDING_X,
|
|
35
|
+
LANE_RADIUS,
|
|
36
|
+
LANE_TOP,
|
|
37
|
+
SUBTITLE_SIZE,
|
|
38
|
+
TABLE_ROW_TEXT_SIZE,
|
|
39
|
+
TABLE_ROW_TYPE_SIZE,
|
|
40
|
+
} from "../design.js";
|
|
41
|
+
|
|
42
|
+
const keyChipText = (key: ColumnKey): string => {
|
|
43
|
+
switch (key) {
|
|
44
|
+
case "primary":
|
|
45
|
+
return "PK";
|
|
46
|
+
case "foreign":
|
|
47
|
+
return "FK";
|
|
48
|
+
case "index":
|
|
49
|
+
return "IX";
|
|
50
|
+
default:
|
|
51
|
+
return assertNever(key, "Unhandled column key");
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** The subtitle under a table's name: its physical name, and how many columns it has. */
|
|
56
|
+
const tableSubtitle = (placed: PlacedTable): string => {
|
|
57
|
+
const count = `${placed.table.columns.length} ${placed.table.columns.length === 1 ? "column" : "columns"}`;
|
|
58
|
+
return placed.table.name === undefined ? count : `${placed.table.name} · ${count}`;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const paintColumns = (placed: PlacedTable, palette: Palette): string => {
|
|
62
|
+
const styles = stylesFor(palette);
|
|
63
|
+
const { box } = placed;
|
|
64
|
+
const left = box.x + CARD_PADDING_X;
|
|
65
|
+
const nameX = left + KEY_CHIP_WIDTH + KEY_CHIP_GAP;
|
|
66
|
+
const right = box.x + box.width - CARD_PADDING_X;
|
|
67
|
+
|
|
68
|
+
return lines(
|
|
69
|
+
placed.columns.map(({ column, centre }) => {
|
|
70
|
+
const tone = toneFor(column.delta);
|
|
71
|
+
const ink = (() => {
|
|
72
|
+
switch (column.delta) {
|
|
73
|
+
case "added":
|
|
74
|
+
return palette.addedText;
|
|
75
|
+
case "removed":
|
|
76
|
+
return palette.removedText;
|
|
77
|
+
case "modified":
|
|
78
|
+
case "unchanged":
|
|
79
|
+
return palette.foreground;
|
|
80
|
+
default:
|
|
81
|
+
return assertNever(column.delta, "Unhandled delta");
|
|
82
|
+
}
|
|
83
|
+
})();
|
|
84
|
+
|
|
85
|
+
const chip =
|
|
86
|
+
column.key === undefined
|
|
87
|
+
? ""
|
|
88
|
+
: tag("rect", {
|
|
89
|
+
class: "chip",
|
|
90
|
+
x: coord(left),
|
|
91
|
+
y: coord(centre - KEY_CHIP_HEIGHT / 2),
|
|
92
|
+
width: KEY_CHIP_WIDTH,
|
|
93
|
+
height: KEY_CHIP_HEIGHT,
|
|
94
|
+
rx: KEY_CHIP_HEIGHT / 2,
|
|
95
|
+
...styles.chip,
|
|
96
|
+
}) +
|
|
97
|
+
textNode(
|
|
98
|
+
{
|
|
99
|
+
class: "keytext",
|
|
100
|
+
x: coord(left + KEY_CHIP_WIDTH / 2),
|
|
101
|
+
y: coord(centre + 3),
|
|
102
|
+
"text-anchor": "middle",
|
|
103
|
+
"font-size": 8,
|
|
104
|
+
"font-weight": 700,
|
|
105
|
+
"letter-spacing": ".06em",
|
|
106
|
+
fill: palette.muted,
|
|
107
|
+
},
|
|
108
|
+
keyChipText(column.key),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// A changed type says what it was: the old spelling struck, then the new one.
|
|
112
|
+
// Non-breaking spaces, because a tspan swallows the ordinary kind at its edges.
|
|
113
|
+
const typeText = column.previousType === undefined ? column.type : `${column.previousType}\u00a0→\u00a0${column.type}`;
|
|
114
|
+
const typeWidth = measure(typeText, "mono", TABLE_ROW_TYPE_SIZE);
|
|
115
|
+
const typeFill = tone === "modified" ? palette.modifiedText : tone === "added" ? palette.addedText : palette.muted;
|
|
116
|
+
const type =
|
|
117
|
+
column.previousType === undefined
|
|
118
|
+
? textNode(
|
|
119
|
+
{ class: "coltype", x: coord(right), y: coord(centre + 3.5), "text-anchor": "end", "font-size": TABLE_ROW_TYPE_SIZE, "font-family": styles.subtitle["font-family"], fill: typeFill },
|
|
120
|
+
typeText,
|
|
121
|
+
)
|
|
122
|
+
: wrap(
|
|
123
|
+
"text",
|
|
124
|
+
{ class: "coltype", x: coord(right), y: coord(centre + 3.5), "text-anchor": "end", "font-size": TABLE_ROW_TYPE_SIZE, "font-family": styles.subtitle["font-family"], fill: typeFill },
|
|
125
|
+
wrap("tspan", { fill: palette.muted, "text-decoration": "line-through" }, column.previousType) +
|
|
126
|
+
wrap("tspan", {}, `\u00a0→\u00a0${column.type}`),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const nameRoom = right - typeWidth - KEY_CHIP_GAP - nameX;
|
|
130
|
+
const name = textNode(
|
|
131
|
+
{
|
|
132
|
+
class: column.delta === "removed" ? "colname strike" : "colname",
|
|
133
|
+
x: coord(nameX),
|
|
134
|
+
y: coord(centre + 4),
|
|
135
|
+
"font-size": TABLE_ROW_TEXT_SIZE,
|
|
136
|
+
"font-family": styles.subtitle["font-family"],
|
|
137
|
+
fill: ink,
|
|
138
|
+
"text-decoration": column.delta === "removed" ? "line-through" : undefined,
|
|
139
|
+
},
|
|
140
|
+
truncate(column.name, "mono", TABLE_ROW_TEXT_SIZE, nameRoom),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return lines([chip, name, type]);
|
|
144
|
+
}),
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** The closing row of a folded card: a dashed rule, then how many columns stayed out of the picture. */
|
|
149
|
+
const paintFold = (placed: PlacedTable, palette: Palette): string => {
|
|
150
|
+
const { fold, box } = placed;
|
|
151
|
+
if (fold === undefined) return "";
|
|
152
|
+
const styles = stylesFor(palette);
|
|
153
|
+
const label = `+ ${fold.hidden} ${fold.unchangedOnly ? "unchanged columns" : "more columns"}`;
|
|
154
|
+
return lines([
|
|
155
|
+
tag("path", {
|
|
156
|
+
class: "rule",
|
|
157
|
+
d: `M${coord(box.x + CARD_PADDING_X)},${coord(fold.top)} h${coord(box.width - CARD_PADDING_X * 2)}`,
|
|
158
|
+
stroke: palette.cardBorder,
|
|
159
|
+
"stroke-width": 1,
|
|
160
|
+
"stroke-dasharray": "3 3",
|
|
161
|
+
}),
|
|
162
|
+
textNode(
|
|
163
|
+
{
|
|
164
|
+
class: "fold",
|
|
165
|
+
x: coord(box.x + CARD_PADDING_X + KEY_CHIP_WIDTH + KEY_CHIP_GAP),
|
|
166
|
+
y: coord(fold.centre + 4),
|
|
167
|
+
"font-size": TABLE_ROW_TEXT_SIZE,
|
|
168
|
+
"font-family": styles.subtitle["font-family"],
|
|
169
|
+
fill: palette.muted,
|
|
170
|
+
},
|
|
171
|
+
label,
|
|
172
|
+
),
|
|
173
|
+
]);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const paintTable = (placed: PlacedTable, palette: Palette): string => {
|
|
177
|
+
const styles = stylesFor(palette);
|
|
178
|
+
const { table, box, titleSize } = placed;
|
|
179
|
+
const textX = box.x + CARD_PADDING_X + ICON_CHIP_SIZE + ICON_CHIP_GAP;
|
|
180
|
+
const textWidth = cardTextWidth(box.width, true);
|
|
181
|
+
const headerBottom = box.y + CARD_HEIGHT_WITH_SUBTITLE;
|
|
182
|
+
|
|
183
|
+
const groupClass =
|
|
184
|
+
table.delta === "removed" ? "cardsh ghost" : table.delta === "unchanged" ? "cardsh context" : "cardsh";
|
|
185
|
+
|
|
186
|
+
return wrap(
|
|
187
|
+
"g",
|
|
188
|
+
{ class: groupClass, ...cardGroupAttributes(table.delta, palette) },
|
|
189
|
+
lines([
|
|
190
|
+
tag("rect", {
|
|
191
|
+
class: cardOutlineClass(table.delta),
|
|
192
|
+
x: coord(box.x),
|
|
193
|
+
y: coord(box.y),
|
|
194
|
+
width: coord(box.width),
|
|
195
|
+
height: coord(box.height),
|
|
196
|
+
rx: CARD_RADIUS,
|
|
197
|
+
...cardAttributes(table.delta, palette),
|
|
198
|
+
}),
|
|
199
|
+
tag("rect", {
|
|
200
|
+
class: "chip",
|
|
201
|
+
x: coord(box.x + CARD_PADDING_X),
|
|
202
|
+
y: coord(box.y + (CARD_HEIGHT_WITH_SUBTITLE - ICON_CHIP_SIZE) / 2),
|
|
203
|
+
width: ICON_CHIP_SIZE,
|
|
204
|
+
height: ICON_CHIP_SIZE,
|
|
205
|
+
rx: ICON_CHIP_RADIUS,
|
|
206
|
+
...styles.chip,
|
|
207
|
+
}),
|
|
208
|
+
tableGlyph(box.x + CARD_PADDING_X + ICON_CHIP_SIZE / 2, box.y + CARD_HEIGHT_WITH_SUBTITLE / 2, styles),
|
|
209
|
+
textNode(
|
|
210
|
+
{
|
|
211
|
+
class: table.delta === "removed" ? "ntitle strike" : "ntitle",
|
|
212
|
+
x: coord(textX),
|
|
213
|
+
y: coord(box.y + 27),
|
|
214
|
+
"font-size": titleSize,
|
|
215
|
+
...styles.title,
|
|
216
|
+
"text-decoration": table.delta === "removed" ? "line-through" : undefined,
|
|
217
|
+
},
|
|
218
|
+
truncate(table.label, "sans-bold", titleSize, textWidth),
|
|
219
|
+
),
|
|
220
|
+
textNode(
|
|
221
|
+
{ class: "nsub", x: coord(textX), y: coord(box.y + 45), ...styles.subtitle },
|
|
222
|
+
truncate(tableSubtitle(placed), "mono", SUBTITLE_SIZE, textWidth),
|
|
223
|
+
),
|
|
224
|
+
tag("path", {
|
|
225
|
+
class: "rule",
|
|
226
|
+
d: `M${coord(box.x)},${coord(headerBottom)} h${coord(box.width)}`,
|
|
227
|
+
stroke: palette.cardBorder,
|
|
228
|
+
"stroke-width": 1,
|
|
229
|
+
}),
|
|
230
|
+
paintColumns(placed, palette),
|
|
231
|
+
paintFold(placed, palette),
|
|
232
|
+
paintBadgeStrip(badgeStrip(box, badgesFor(table.badges, table.delta)), table.delta, palette),
|
|
233
|
+
]),
|
|
234
|
+
);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const paintRelation = (routed: RoutedRelation, palette: Palette): { markup: string; pill: string } => {
|
|
238
|
+
const { relation, path, curve } = routed;
|
|
239
|
+
const tone = toneFor(relation.delta);
|
|
240
|
+
const classes = ["edge", `edge-${tone}`];
|
|
241
|
+
if (relation.delta === "unchanged") classes.push("context");
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
markup: lines([
|
|
245
|
+
tag("path", {
|
|
246
|
+
class: classes.join(" "),
|
|
247
|
+
d: path,
|
|
248
|
+
"marker-end": routed.head ? markerFor(tone) : undefined,
|
|
249
|
+
...relationAttributes(relation, palette),
|
|
250
|
+
}),
|
|
251
|
+
tag("circle", {
|
|
252
|
+
class: "port",
|
|
253
|
+
cx: coord(curve.from.x),
|
|
254
|
+
cy: coord(curve.from.y),
|
|
255
|
+
r: 2.5,
|
|
256
|
+
fill: toneColour(palette, tone),
|
|
257
|
+
opacity: relation.delta === "removed" ? 0.7 : undefined,
|
|
258
|
+
}),
|
|
259
|
+
]),
|
|
260
|
+
pill:
|
|
261
|
+
routed.pill === undefined || relation.label === undefined
|
|
262
|
+
? ""
|
|
263
|
+
: paintLabelPill(relation.label, routed.pill, tone, palette),
|
|
264
|
+
};
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
export type DataModelPainting = {
|
|
268
|
+
width: number;
|
|
269
|
+
height: number;
|
|
270
|
+
body: string;
|
|
271
|
+
atlas: RenderAtlas;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export const paintDataModel = (graph: ScopedGraph, palette: Palette): DataModelPainting => {
|
|
275
|
+
const layout = layoutDataModel(graph);
|
|
276
|
+
const routed = routeRelations(layout, graph.relations);
|
|
277
|
+
const styles = stylesFor(palette);
|
|
278
|
+
|
|
279
|
+
const drawn: Box[] = layout.tables.flatMap(({ table, box }) => {
|
|
280
|
+
const strip = badgeStrip(box, badgesFor(table.badges, table.delta));
|
|
281
|
+
return strip === undefined ? [box] : [box, strip.box];
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const edgeMarkup: string[] = [];
|
|
285
|
+
const pillMarkup: string[] = [];
|
|
286
|
+
for (const relation of routed) {
|
|
287
|
+
const { markup, pill } = paintRelation(relation, palette);
|
|
288
|
+
edgeMarkup.push(markup);
|
|
289
|
+
pillMarkup.push(pill);
|
|
290
|
+
if (relation.pill !== undefined) drawn.push(relation.pill);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const headerBaseline = layout.laneTop + (LANE_HEADER_BASELINE - LANE_TOP);
|
|
294
|
+
const lanes = layout.stores.map(({ store, box }) => {
|
|
295
|
+
const header: Lane = { id: store.id, label: store.label, subtitle: store.platform };
|
|
296
|
+
return lines([
|
|
297
|
+
tag("rect", {
|
|
298
|
+
class: "lanebox",
|
|
299
|
+
x: coord(box.x),
|
|
300
|
+
y: coord(box.y),
|
|
301
|
+
width: coord(box.width),
|
|
302
|
+
height: coord(box.height),
|
|
303
|
+
rx: LANE_RADIUS,
|
|
304
|
+
...styles.lane,
|
|
305
|
+
}),
|
|
306
|
+
textNode(
|
|
307
|
+
{ class: "lanelabel", x: coord(box.x + LANE_PADDING_X), y: coord(headerBaseline), ...styles.laneLabel },
|
|
308
|
+
laneHeaderText(header),
|
|
309
|
+
),
|
|
310
|
+
]);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const canvas = canvasFor(
|
|
314
|
+
layout,
|
|
315
|
+
union([
|
|
316
|
+
...layout.stores.map(({ box }) => box),
|
|
317
|
+
...drawn,
|
|
318
|
+
...routed.map(({ curve }) => curveBounds(curve)),
|
|
319
|
+
]),
|
|
320
|
+
DIAGRAM_MARGIN,
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
const painted = lines([
|
|
324
|
+
wrap("g", {}, lines(lanes)),
|
|
325
|
+
wrap("g", {}, lines(edgeMarkup)),
|
|
326
|
+
wrap("g", {}, lines(layout.tables.map((table) => paintTable(table, palette)))),
|
|
327
|
+
wrap("g", {}, lines(pillMarkup)),
|
|
328
|
+
]);
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
width: canvas.width,
|
|
332
|
+
height: canvas.height,
|
|
333
|
+
body: shifted(canvas, painted),
|
|
334
|
+
atlas: {
|
|
335
|
+
...emptyAtlas(),
|
|
336
|
+
lanes: atlasBoxes(layout.stores.map(({ store, box }) => ({ id: store.id, box })), canvas),
|
|
337
|
+
tables: atlasBoxes(layout.tables.map(({ table, box }) => ({ id: table.id, box })), canvas),
|
|
338
|
+
edges: atlasBoxes(
|
|
339
|
+
routed.map(({ relation, curve }) => ({ id: relation.id, box: curveBounds(curve) })),
|
|
340
|
+
canvas,
|
|
341
|
+
),
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
};
|
package/src/svg/icons.ts
CHANGED
|
@@ -128,5 +128,25 @@ const kindGlyph = (kind: NodeKind, cx: number, cy: number, styles: SvgStyles): s
|
|
|
128
128
|
}
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
+
/** A table: a box with a header rule and a column divider. */
|
|
132
|
+
export const tableGlyph = (cx: number, cy: number, styles: SvgStyles): string =>
|
|
133
|
+
wrap(
|
|
134
|
+
"g",
|
|
135
|
+
{},
|
|
136
|
+
lines([
|
|
137
|
+
tag("rect", {
|
|
138
|
+
class: "glyph-stroke",
|
|
139
|
+
x: coord(cx - 7),
|
|
140
|
+
y: coord(cy - 6),
|
|
141
|
+
width: 14,
|
|
142
|
+
height: 12,
|
|
143
|
+
rx: 2,
|
|
144
|
+
...styles.glyphStroke,
|
|
145
|
+
}),
|
|
146
|
+
tag("path", { class: "glyph-stroke", d: `M${coord(cx - 7)},${coord(cy - 2)} h14`, ...styles.glyphStroke }),
|
|
147
|
+
tag("path", { class: "glyph-stroke", d: `M${coord(cx - 2)},${coord(cy - 2)} v8`, ...styles.glyphStroke }),
|
|
148
|
+
]),
|
|
149
|
+
);
|
|
150
|
+
|
|
131
151
|
export const glyphGroup = (kind: NodeKind, cx: number, cy: number, styles: SvgStyles): string =>
|
|
132
152
|
wrap("g", {}, kindGlyph(kind, cx, cy, styles));
|
package/src/svg/styles.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assertNever } from "@coldtea/pr-lens-schema";
|
|
2
|
-
import type { Delta, FlowMessage, GraphEdge } from "@coldtea/pr-lens-schema";
|
|
2
|
+
import type { Delta, FlowMessage, GraphEdge, Relation } from "@coldtea/pr-lens-schema";
|
|
3
3
|
import { MONO_STACK } from "../text.js";
|
|
4
4
|
import type { Palette } from "../theme.js";
|
|
5
5
|
import type { Attributes } from "./primitives.js";
|
|
@@ -104,6 +104,13 @@ export const edgeAttributes = (edge: GraphEdge, palette: Palette): Attributes =>
|
|
|
104
104
|
}
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
+
/** A key between tables: an edge, dotted when nothing enforces it. */
|
|
108
|
+
export const relationAttributes = (relation: Relation, palette: Palette): Attributes => ({
|
|
109
|
+
...strokeAttributes(relation.delta, palette),
|
|
110
|
+
...(relation.delta === "unchanged" ? { opacity: 0.82 } : {}),
|
|
111
|
+
...(relation.enforced ? {} : { "stroke-dasharray": "2 4" }),
|
|
112
|
+
});
|
|
113
|
+
|
|
107
114
|
export const messageAttributes = (message: FlowMessage, palette: Palette): Attributes => {
|
|
108
115
|
const attributes = {
|
|
109
116
|
...strokeAttributes(message.delta, palette),
|
package/src/version.ts
CHANGED