@tanishqkancharla/diffmap 0.4.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -4
- package/package.json +1 -1
- package/src/annotations.ts +17 -1
- package/src/extractDocument.ts +10 -2
- package/src/host/home.md +89 -103
- package/src/host/home.test.ts +11 -1
- package/src/parseFence.ts +81 -12
- package/src/parseViewer.test.ts +147 -4
- package/src/parseViewer.ts +44 -9
- package/src/serve.test.ts +22 -1
- package/src/services.ts +122 -15
- package/src/viewer/CallStackDiff.tsx +29 -33
- package/src/viewer/ChangesCard.tsx +0 -2
- package/src/viewer/CodeBlock.tsx +79 -7
- package/src/viewer/ComarkView.tsx +34 -0
- package/src/viewer/Fence.tsx +4 -0
- package/src/viewer/HtmlBlock.tsx +2 -1
- package/src/viewer/MermaidBlock.tsx +55 -14
- package/src/viewer/ServiceDiagramBlock.tsx +90 -54
- package/src/viewer/ServiceStage.tsx +206 -60
- package/src/viewer/SourceDiffPanel.tsx +138 -42
- package/src/viewer/TableOfContents.tsx +44 -18
- package/src/viewer/TocButton.tsx +28 -0
- package/src/viewer/ViewerApp.tsx +10 -0
- package/src/viewer/diagramDiff.ts +69 -0
- package/src/viewer/diagramLinks.ts +54 -6
- package/src/viewer/notes.tsx +0 -1
- package/src/viewer/pierre.ts +20 -2
- package/src/viewer/serviceLayout.ts +16 -5
- package/vite.config.ts +4 -0
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
RotateAntiClockwise,
|
|
10
10
|
Text,
|
|
11
11
|
Tooltip,
|
|
12
|
-
borderColor,
|
|
13
12
|
colors,
|
|
14
13
|
flex,
|
|
15
14
|
focusRing,
|
|
@@ -19,6 +18,7 @@ import {
|
|
|
19
18
|
} from "maui";
|
|
20
19
|
import { style, useStyles } from "purse-styles";
|
|
21
20
|
import type { SourceNavigation } from "../annotations.js";
|
|
21
|
+
import { fenceTitle, pierreShell } from "./pierre.js";
|
|
22
22
|
import { ServiceStage, TRAVEL_MS } from "./ServiceStage.tsx";
|
|
23
23
|
import {
|
|
24
24
|
serviceStateAt,
|
|
@@ -28,10 +28,16 @@ import {
|
|
|
28
28
|
|
|
29
29
|
const DWELL_MS = 1200;
|
|
30
30
|
|
|
31
|
+
/** Names a diff step's side before its caption. */
|
|
32
|
+
const signLabel = { "+": "Added: ", "-": "Removed: ", " ": "" } as const;
|
|
33
|
+
|
|
31
34
|
type Phase = "travel" | "settled";
|
|
32
35
|
|
|
33
36
|
export function ServiceDiagramBlock(
|
|
34
|
-
props: {
|
|
37
|
+
props: {
|
|
38
|
+
source: string;
|
|
39
|
+
parsed: ServicesParse;
|
|
40
|
+
} & SourceNavigation,
|
|
35
41
|
) {
|
|
36
42
|
const error = useStyles(styles.error);
|
|
37
43
|
if (!props.parsed.ok)
|
|
@@ -61,13 +67,16 @@ function ServicePlayer(props: { diagram: ServiceDiagram } & SourceNavigation) {
|
|
|
61
67
|
const [playing, setPlaying] = useState(false);
|
|
62
68
|
const states = useMemo(
|
|
63
69
|
() =>
|
|
64
|
-
Array.from({ length: last + 1 }, (_, step) =>
|
|
65
|
-
serviceStateAt(diagram, step),
|
|
66
|
-
|
|
70
|
+
Array.from({ length: last + 1 }, (_, step) => ({
|
|
71
|
+
today: serviceStateAt(diagram, step, "today"),
|
|
72
|
+
proposed: serviceStateAt(diagram, step, "proposed"),
|
|
73
|
+
})),
|
|
67
74
|
[diagram, last],
|
|
68
75
|
);
|
|
69
76
|
const step = position.step;
|
|
70
77
|
const current = step === 0 ? undefined : diagram.steps[step - 1]!;
|
|
78
|
+
// Before step 1, the author's start caption, if any, explains the state.
|
|
79
|
+
const captioned = current ?? diagram.start;
|
|
71
80
|
const settled = position.phase === "settled";
|
|
72
81
|
// In flight, the events have not landed, so show the previous state.
|
|
73
82
|
const shownState = states[settled ? step : step - 1]!;
|
|
@@ -83,14 +92,15 @@ function ServicePlayer(props: { diagram: ServiceDiagram } & SourceNavigation) {
|
|
|
83
92
|
const goTo = (target: number, animate: boolean) =>
|
|
84
93
|
setPosition(positionFor(diagram, target, animate && !reduceMotion));
|
|
85
94
|
|
|
95
|
+
// Events travel one after another, so the step settles when the last lands.
|
|
86
96
|
useEffect(() => {
|
|
87
97
|
if (position.phase !== "travel") return;
|
|
88
98
|
const timer = setTimeout(
|
|
89
99
|
() => setPosition({ step: position.step, phase: "settled" }),
|
|
90
|
-
TRAVEL_MS,
|
|
100
|
+
TRAVEL_MS * (diagram.steps[position.step - 1]?.events.length ?? 1),
|
|
91
101
|
);
|
|
92
102
|
return () => clearTimeout(timer);
|
|
93
|
-
}, [position]);
|
|
103
|
+
}, [position, diagram]);
|
|
94
104
|
|
|
95
105
|
useEffect(() => {
|
|
96
106
|
if (!playing || position.phase !== "settled") return;
|
|
@@ -129,33 +139,51 @@ function ServicePlayer(props: { diagram: ServiceDiagram } & SourceNavigation) {
|
|
|
129
139
|
}
|
|
130
140
|
};
|
|
131
141
|
|
|
132
|
-
const frame = useStyles(styles.frame);
|
|
142
|
+
const frame = useStyles(pierreShell, styles.frame);
|
|
143
|
+
const body = useStyles(styles.body);
|
|
133
144
|
const controls = useStyles(styles.controls);
|
|
134
145
|
const scrubber = useStyles(styles.scrubber);
|
|
135
146
|
const segment = useStyles(styles.segment);
|
|
136
|
-
const detail = useStyles(styles.detail);
|
|
147
|
+
const detail = useStyles(fenceTitle, styles.detail);
|
|
137
148
|
const counter = useStyles(styles.counter);
|
|
138
149
|
|
|
139
150
|
return (
|
|
140
|
-
<
|
|
151
|
+
<figure
|
|
141
152
|
className={frame}
|
|
142
153
|
data-diffmap-kind="services"
|
|
143
154
|
// Capture: Maui's Button stops arrow keys from bubbling.
|
|
144
155
|
onKeyDownCapture={onKeyDown}
|
|
145
156
|
>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
{/* The caption leads: before step 1 it is the start caption, which
|
|
158
|
+
captions the whole diagram. */}
|
|
159
|
+
{(captioned !== undefined || last > 0) && (
|
|
160
|
+
<figcaption className={detail} aria-live="polite">
|
|
161
|
+
{captioned !== undefined && (
|
|
162
|
+
<Text size="sm" fontWeight={500}>
|
|
163
|
+
{current !== undefined && current.sign !== " " && (
|
|
164
|
+
<Text
|
|
165
|
+
size="sm"
|
|
166
|
+
fontWeight={600}
|
|
167
|
+
style={{
|
|
168
|
+
color:
|
|
169
|
+
current.sign === "+" ? colors.green[11] : colors.red[11],
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
{signLabel[current.sign]}
|
|
173
|
+
</Text>
|
|
174
|
+
)}
|
|
175
|
+
{captioned.caption}
|
|
176
|
+
</Text>
|
|
177
|
+
)}
|
|
178
|
+
{captioned?.description !== undefined && (
|
|
179
|
+
<Text size="xs" color="lowContrast">
|
|
180
|
+
{captioned.description}
|
|
181
|
+
</Text>
|
|
182
|
+
)}
|
|
183
|
+
</figcaption>
|
|
184
|
+
)}
|
|
185
|
+
<div className={body}>
|
|
186
|
+
{last > 0 && (
|
|
159
187
|
<div className={controls}>
|
|
160
188
|
<Tooltip content="Restart" placement="top">
|
|
161
189
|
<Button
|
|
@@ -220,6 +248,7 @@ function ServicePlayer(props: { diagram: ServiceDiagram } & SourceNavigation) {
|
|
|
220
248
|
<button
|
|
221
249
|
type="button"
|
|
222
250
|
className={segment}
|
|
251
|
+
data-change={entry.sign}
|
|
223
252
|
data-state={
|
|
224
253
|
index + 1 === step
|
|
225
254
|
? "current"
|
|
@@ -227,7 +256,7 @@ function ServicePlayer(props: { diagram: ServiceDiagram } & SourceNavigation) {
|
|
|
227
256
|
? "done"
|
|
228
257
|
: "todo"
|
|
229
258
|
}
|
|
230
|
-
aria-label={`Step ${index + 1}: ${entry.caption}`}
|
|
259
|
+
aria-label={`Step ${index + 1}: ${signLabel[entry.sign]}${entry.caption}`}
|
|
231
260
|
aria-current={index + 1 === step ? "step" : undefined}
|
|
232
261
|
onClick={() => {
|
|
233
262
|
setPlaying(false);
|
|
@@ -241,24 +270,20 @@ function ServicePlayer(props: { diagram: ServiceDiagram } & SourceNavigation) {
|
|
|
241
270
|
{step} / {last}
|
|
242
271
|
</Text>
|
|
243
272
|
</div>
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
</div>
|
|
259
|
-
</>
|
|
260
|
-
)}
|
|
261
|
-
</div>
|
|
273
|
+
)}
|
|
274
|
+
<ServiceStage
|
|
275
|
+
{...props}
|
|
276
|
+
state={shownState}
|
|
277
|
+
previousState={states[Math.max(step - 1, 0)]!}
|
|
278
|
+
changed={changed}
|
|
279
|
+
active={active}
|
|
280
|
+
events={current?.events ?? []}
|
|
281
|
+
updates={current?.updates ?? []}
|
|
282
|
+
phase={position.phase}
|
|
283
|
+
stepKey={step}
|
|
284
|
+
/>
|
|
285
|
+
</div>
|
|
286
|
+
</figure>
|
|
262
287
|
);
|
|
263
288
|
}
|
|
264
289
|
|
|
@@ -273,17 +298,16 @@ function positionFor(
|
|
|
273
298
|
|
|
274
299
|
const styles = {
|
|
275
300
|
error: style(text({ size: "sm", color: "lowContrast" })),
|
|
276
|
-
|
|
301
|
+
// A card like a code block: the caption row, then the controls and stage.
|
|
302
|
+
frame: style({ width: "100%", margin: 0 }),
|
|
303
|
+
body: style(
|
|
277
304
|
flex({ direction: "column", gap: 4 }),
|
|
278
|
-
radius.lg,
|
|
279
305
|
spacing.padding({ all: 6 }),
|
|
280
|
-
{
|
|
281
|
-
width: "100%",
|
|
282
|
-
minWidth: 0,
|
|
283
|
-
border: `1px solid ${borderColor.outline}`,
|
|
284
|
-
},
|
|
285
306
|
),
|
|
286
|
-
controls: style(flex({ direction: "row", alignItems: "center", gap: 2 })
|
|
307
|
+
controls: style(flex({ direction: "row", alignItems: "center", gap: 2 }), {
|
|
308
|
+
// Buttons keep their full label; only the step scrubber gives up width.
|
|
309
|
+
"& > button": { flexShrink: 0, overflow: "visible" },
|
|
310
|
+
}),
|
|
287
311
|
scrubber: style(flex({ direction: "row", alignItems: "center", gap: 1 }), {
|
|
288
312
|
flex: "1 1 auto",
|
|
289
313
|
minWidth: 0,
|
|
@@ -312,12 +336,24 @@ const styles = {
|
|
|
312
336
|
"&:hover::after": { backgroundColor: colors.gray[7] },
|
|
313
337
|
"&[data-state='done']::after": { backgroundColor: colors.accent[7] },
|
|
314
338
|
"&[data-state='current']::after": { backgroundColor: colors.accent[9] },
|
|
339
|
+
"&[data-change='+']::after": { backgroundColor: colors.green[6] },
|
|
340
|
+
"&[data-change='+'][data-state='done']::after": {
|
|
341
|
+
backgroundColor: colors.green[8],
|
|
342
|
+
},
|
|
343
|
+
"&[data-change='+'][data-state='current']::after": {
|
|
344
|
+
backgroundColor: colors.green[10],
|
|
345
|
+
},
|
|
346
|
+
"&[data-change='-']::after": { backgroundColor: colors.red[6] },
|
|
347
|
+
"&[data-change='-'][data-state='done']::after": {
|
|
348
|
+
backgroundColor: colors.red[8],
|
|
349
|
+
},
|
|
350
|
+
"&[data-change='-'][data-state='current']::after": {
|
|
351
|
+
backgroundColor: colors.red[10],
|
|
352
|
+
},
|
|
315
353
|
},
|
|
316
354
|
focusRing(),
|
|
317
355
|
),
|
|
318
356
|
counter: style({ flexShrink: 0, minWidth: "5ch", textAlign: "end" }),
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
paddingInline: spacing.value(2),
|
|
322
|
-
}),
|
|
357
|
+
// Holds two lines so the controls below don't move between steps.
|
|
358
|
+
detail: style(flex({ direction: "column", gap: 2 }), { minHeight: 44 }),
|
|
323
359
|
};
|