markgraphy 0.1.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/LICENSE.txt +21 -0
- package/README.md +154 -0
- package/dist/animated/GraphAgni.svelte +430 -0
- package/dist/animated/GraphAgni.svelte.d.ts +22 -0
- package/dist/animated/GraphAum.svelte +294 -0
- package/dist/animated/GraphAum.svelte.d.ts +20 -0
- package/dist/animated/GraphBoot.svelte +325 -0
- package/dist/animated/GraphBoot.svelte.d.ts +28 -0
- package/dist/animated/GraphCron.svelte +476 -0
- package/dist/animated/GraphCron.svelte.d.ts +42 -0
- package/dist/animated/GraphDamru.svelte +341 -0
- package/dist/animated/GraphDamru.svelte.d.ts +18 -0
- package/dist/animated/GraphDeps.svelte +245 -0
- package/dist/animated/GraphDeps.svelte.d.ts +29 -0
- package/dist/animated/GraphDiya.svelte +330 -0
- package/dist/animated/GraphDiya.svelte.d.ts +22 -0
- package/dist/animated/GraphFire.svelte +246 -0
- package/dist/animated/GraphFire.svelte.d.ts +22 -0
- package/dist/animated/GraphFlame.svelte +258 -0
- package/dist/animated/GraphFlame.svelte.d.ts +14 -0
- package/dist/animated/GraphFlowPlayer.svelte +346 -0
- package/dist/animated/GraphFlowPlayer.svelte.d.ts +27 -0
- package/dist/animated/GraphGanga.svelte +305 -0
- package/dist/animated/GraphGanga.svelte.d.ts +20 -0
- package/dist/animated/GraphHash.svelte +251 -0
- package/dist/animated/GraphHash.svelte.d.ts +20 -0
- package/dist/animated/GraphJapa.svelte +253 -0
- package/dist/animated/GraphJapa.svelte.d.ts +20 -0
- package/dist/animated/GraphLife.svelte +312 -0
- package/dist/animated/GraphLife.svelte.d.ts +27 -0
- package/dist/animated/GraphMandala.svelte +262 -0
- package/dist/animated/GraphMandala.svelte.d.ts +24 -0
- package/dist/animated/GraphMandel.svelte +290 -0
- package/dist/animated/GraphMandel.svelte.d.ts +27 -0
- package/dist/animated/GraphPulse.svelte +248 -0
- package/dist/animated/GraphPulse.svelte.d.ts +24 -0
- package/dist/animated/GraphRain.svelte +280 -0
- package/dist/animated/GraphRain.svelte.d.ts +22 -0
- package/dist/animated/GraphScatter.svelte +395 -0
- package/dist/animated/GraphScatter.svelte.d.ts +31 -0
- package/dist/animated/GraphScope.svelte +197 -0
- package/dist/animated/GraphScope.svelte.d.ts +22 -0
- package/dist/animated/GraphSequence.svelte +247 -0
- package/dist/animated/GraphSequence.svelte.d.ts +18 -0
- package/dist/animated/GraphSpinners.svelte +109 -0
- package/dist/animated/GraphSpinners.svelte.d.ts +22 -0
- package/dist/animated/GraphState.svelte +270 -0
- package/dist/animated/GraphState.svelte.d.ts +14 -0
- package/dist/animated/GraphStream.svelte +223 -0
- package/dist/animated/GraphStream.svelte.d.ts +24 -0
- package/dist/animated/GraphSurya.svelte +268 -0
- package/dist/animated/GraphSurya.svelte.d.ts +22 -0
- package/dist/animated/GraphTerminal.svelte +238 -0
- package/dist/animated/GraphTerminal.svelte.d.ts +28 -0
- package/dist/animated/GraphTicker.svelte +163 -0
- package/dist/animated/GraphTicker.svelte.d.ts +21 -0
- package/dist/animated/GraphTypewriter.svelte +130 -0
- package/dist/animated/GraphTypewriter.svelte.d.ts +22 -0
- package/dist/animated/GraphWorkflow.svelte +570 -0
- package/dist/animated/GraphWorkflow.svelte.d.ts +29 -0
- package/dist/assets/favicon.svg +21 -0
- package/dist/diagram/AmplifierDiagram.svelte +206 -0
- package/dist/diagram/AmplifierDiagram.svelte.d.ts +12 -0
- package/dist/diagram/AsciiDiagram.svelte +212 -0
- package/dist/diagram/AsciiDiagram.svelte.d.ts +24 -0
- package/dist/diagram/DiagramEditor.svelte +414 -0
- package/dist/diagram/DiagramEditor.svelte.d.ts +3 -0
- package/dist/diagram/MetricsTableDiagram.svelte +125 -0
- package/dist/diagram/MetricsTableDiagram.svelte.d.ts +18 -0
- package/dist/diagram/NestedRadiiDiagram.svelte +218 -0
- package/dist/diagram/NestedRadiiDiagram.svelte.d.ts +12 -0
- package/dist/diagram/PromptLoopDiagram.svelte +331 -0
- package/dist/diagram/PromptLoopDiagram.svelte.d.ts +12 -0
- package/dist/diagram/ascii.d.ts +35 -0
- package/dist/diagram/ascii.js +83 -0
- package/dist/diagram/parser.d.ts +16 -0
- package/dist/diagram/parser.js +71 -0
- package/dist/frame/Graph.svelte +141 -0
- package/dist/frame/Graph.svelte.d.ts +14 -0
- package/dist/frame/GraphArrow.svelte +49 -0
- package/dist/frame/GraphArrow.svelte.d.ts +9 -0
- package/dist/frame/GraphBody.svelte +39 -0
- package/dist/frame/GraphBody.svelte.d.ts +10 -0
- package/dist/frame/GraphRule.svelte +21 -0
- package/dist/frame/GraphRule.svelte.d.ts +6 -0
- package/dist/frame/GraphTick.svelte +23 -0
- package/dist/frame/GraphTick.svelte.d.ts +9 -0
- package/dist/frame/GraphTrack.svelte +29 -0
- package/dist/frame/GraphTrack.svelte.d.ts +14 -0
- package/dist/frame/clock.d.ts +10 -0
- package/dist/frame/clock.js +62 -0
- package/dist/frame/glyphs.d.ts +22 -0
- package/dist/frame/glyphs.js +53 -0
- package/dist/frame/motion.css +27 -0
- package/dist/frame/motion.d.ts +24 -0
- package/dist/frame/motion.js +47 -0
- package/dist/frame/tone.d.ts +11 -0
- package/dist/frame/tone.js +64 -0
- package/dist/graphs/GraphActivity.svelte +387 -0
- package/dist/graphs/GraphActivity.svelte.d.ts +22 -0
- package/dist/graphs/GraphBars.svelte +194 -0
- package/dist/graphs/GraphBars.svelte.d.ts +20 -0
- package/dist/graphs/GraphBullet.svelte +208 -0
- package/dist/graphs/GraphBullet.svelte.d.ts +22 -0
- package/dist/graphs/GraphCalendar.svelte +203 -0
- package/dist/graphs/GraphCalendar.svelte.d.ts +20 -0
- package/dist/graphs/GraphCells.svelte +181 -0
- package/dist/graphs/GraphCells.svelte.d.ts +18 -0
- package/dist/graphs/GraphCompare.svelte +192 -0
- package/dist/graphs/GraphCompare.svelte.d.ts +18 -0
- package/dist/graphs/GraphCountdown.svelte +96 -0
- package/dist/graphs/GraphCountdown.svelte.d.ts +14 -0
- package/dist/graphs/GraphDiff.svelte +181 -0
- package/dist/graphs/GraphDiff.svelte.d.ts +18 -0
- package/dist/graphs/GraphFlow.svelte +137 -0
- package/dist/graphs/GraphFlow.svelte.d.ts +21 -0
- package/dist/graphs/GraphFunnel.svelte +190 -0
- package/dist/graphs/GraphFunnel.svelte.d.ts +21 -0
- package/dist/graphs/GraphGantt.svelte +258 -0
- package/dist/graphs/GraphGantt.svelte.d.ts +25 -0
- package/dist/graphs/GraphHeatmap.svelte +287 -0
- package/dist/graphs/GraphHeatmap.svelte.d.ts +22 -0
- package/dist/graphs/GraphInvoice.svelte +294 -0
- package/dist/graphs/GraphInvoice.svelte.d.ts +34 -0
- package/dist/graphs/GraphKpi.svelte +166 -0
- package/dist/graphs/GraphKpi.svelte.d.ts +17 -0
- package/dist/graphs/GraphLatency.svelte +340 -0
- package/dist/graphs/GraphLatency.svelte.d.ts +28 -0
- package/dist/graphs/GraphMeter.svelte +121 -0
- package/dist/graphs/GraphMeter.svelte.d.ts +16 -0
- package/dist/graphs/GraphPlot.svelte +226 -0
- package/dist/graphs/GraphPlot.svelte.d.ts +18 -0
- package/dist/graphs/GraphQuota.svelte +303 -0
- package/dist/graphs/GraphQuota.svelte.d.ts +27 -0
- package/dist/graphs/GraphRank.svelte +141 -0
- package/dist/graphs/GraphRank.svelte.d.ts +21 -0
- package/dist/graphs/GraphSlo.svelte +325 -0
- package/dist/graphs/GraphSlo.svelte.d.ts +31 -0
- package/dist/graphs/GraphSlope.svelte +197 -0
- package/dist/graphs/GraphSlope.svelte.d.ts +19 -0
- package/dist/graphs/GraphSpark.svelte +125 -0
- package/dist/graphs/GraphSpark.svelte.d.ts +15 -0
- package/dist/graphs/GraphSpec.svelte +69 -0
- package/dist/graphs/GraphSpec.svelte.d.ts +14 -0
- package/dist/graphs/GraphStack.svelte +266 -0
- package/dist/graphs/GraphStack.svelte.d.ts +25 -0
- package/dist/graphs/GraphStat.svelte +103 -0
- package/dist/graphs/GraphStat.svelte.d.ts +16 -0
- package/dist/graphs/GraphTable.svelte +133 -0
- package/dist/graphs/GraphTable.svelte.d.ts +15 -0
- package/dist/graphs/GraphTimeline.svelte +164 -0
- package/dist/graphs/GraphTimeline.svelte.d.ts +18 -0
- package/dist/graphs/GraphTimer.svelte +117 -0
- package/dist/graphs/GraphTimer.svelte.d.ts +15 -0
- package/dist/graphs/GraphTree.svelte +147 -0
- package/dist/graphs/GraphTree.svelte.d.ts +16 -0
- package/dist/graphs/GraphUptime.svelte +210 -0
- package/dist/graphs/GraphUptime.svelte.d.ts +18 -0
- package/dist/graphs/GraphWaffle.svelte +131 -0
- package/dist/graphs/GraphWaffle.svelte.d.ts +17 -0
- package/dist/graphs/GraphWaterfall.svelte +258 -0
- package/dist/graphs/GraphWaterfall.svelte.d.ts +22 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.js +87 -0
- package/dist/themes.css +54 -0
- package/package.json +67 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export interface GraphSequenceProps {
|
|
3
|
+
/** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
|
|
4
|
+
title: string;
|
|
5
|
+
/** Animate the message flow. Default true. Reduced motion freezes the first message as current. */
|
|
6
|
+
animated?: boolean;
|
|
7
|
+
/** Width of the scene in characters; at least 40 so four participants fit. Default 68. */
|
|
8
|
+
cols?: number;
|
|
9
|
+
/** Height of the scene in rows; at least 10 so header + 6 messages fit. Default 12. */
|
|
10
|
+
rows?: number;
|
|
11
|
+
/** Milliseconds per message. Default 600. */
|
|
12
|
+
speedMs?: number;
|
|
13
|
+
/** Short caption under the art; the diagram itself is the message. */
|
|
14
|
+
label?: string;
|
|
15
|
+
class?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Participant {
|
|
19
|
+
label: string;
|
|
20
|
+
col: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Message {
|
|
24
|
+
from: number;
|
|
25
|
+
to: number;
|
|
26
|
+
label: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface Seg {
|
|
30
|
+
text: string;
|
|
31
|
+
cls?: string;
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<script lang="ts">
|
|
36
|
+
import Graph from '../frame/Graph.svelte';
|
|
37
|
+
import GraphBody from '../frame/GraphBody.svelte';
|
|
38
|
+
import { overlayRow, type ArtCell } from '../diagram/ascii';
|
|
39
|
+
|
|
40
|
+
let {
|
|
41
|
+
title,
|
|
42
|
+
animated = true,
|
|
43
|
+
cols = 68,
|
|
44
|
+
rows = 12,
|
|
45
|
+
speedMs = 600,
|
|
46
|
+
label = 'who calls who, in order',
|
|
47
|
+
class: className = ''
|
|
48
|
+
}: GraphSequenceProps = $props();
|
|
49
|
+
|
|
50
|
+
// svelte-ignore state_referenced_locally
|
|
51
|
+
const width = Math.max(40, cols);
|
|
52
|
+
// svelte-ignore state_referenced_locally
|
|
53
|
+
const height = Math.max(10, rows);
|
|
54
|
+
|
|
55
|
+
// Four participants at 12% / 38% / 62% / 88% of the width so the labels
|
|
56
|
+
// sit centered on the lifelines and never collide with the side borders.
|
|
57
|
+
// svelte-ignore state_referenced_locally
|
|
58
|
+
const participants: Participant[] = (() => {
|
|
59
|
+
const labels = ['Browser', 'Server', 'DB', 'Worker'];
|
|
60
|
+
const stops = [0.12, 0.38, 0.62, 0.88];
|
|
61
|
+
return labels.map((l, i) => ({ label: l, col: Math.max(2, Math.floor(width * stops[i]!)) }));
|
|
62
|
+
})();
|
|
63
|
+
|
|
64
|
+
// A request that fans out to a DB, queues a job, and comes back.
|
|
65
|
+
const messages: Message[] = [
|
|
66
|
+
{ from: 0, to: 1, label: 'GET /api' },
|
|
67
|
+
{ from: 1, to: 2, label: 'query' },
|
|
68
|
+
{ from: 2, to: 3, label: 'enqueue' },
|
|
69
|
+
{ from: 3, to: 2, label: 'result' },
|
|
70
|
+
{ from: 2, to: 1, label: 'rows' },
|
|
71
|
+
{ from: 1, to: 0, label: '200 OK' }
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
// Each activation is a row-range during which a participant's lifeline is
|
|
75
|
+
// drawn as a filled bar (the participant is busy). Rows outside are `│`.
|
|
76
|
+
// svelte-ignore state_referenced_locally
|
|
77
|
+
const activations: { pIdx: number; startRow: number; endRow: number }[] = (() => {
|
|
78
|
+
const firstRow = 3;
|
|
79
|
+
const lastRow = firstRow + messages.length - 1;
|
|
80
|
+
return [
|
|
81
|
+
{ pIdx: 0, startRow: firstRow, endRow: lastRow },
|
|
82
|
+
{ pIdx: 1, startRow: firstRow, endRow: lastRow },
|
|
83
|
+
{ pIdx: 2, startRow: firstRow + 1, endRow: firstRow + 4 },
|
|
84
|
+
{ pIdx: 3, startRow: firstRow + 2, endRow: firstRow + 3 }
|
|
85
|
+
];
|
|
86
|
+
})();
|
|
87
|
+
|
|
88
|
+
const headerRow = 1;
|
|
89
|
+
const firstMsgRow = 3;
|
|
90
|
+
|
|
91
|
+
// svelte-ignore state_referenced_locally
|
|
92
|
+
let tick = $state(0);
|
|
93
|
+
|
|
94
|
+
const moving = $derived(
|
|
95
|
+
animated &&
|
|
96
|
+
(typeof window === 'undefined' ||
|
|
97
|
+
!window.matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
$effect(() => {
|
|
101
|
+
if (!moving) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const timer = window.setInterval(() => {
|
|
105
|
+
tick = (tick + 1) % messages.length;
|
|
106
|
+
}, speedMs);
|
|
107
|
+
return () => window.clearInterval(timer);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Pin the current message at the last one when the loop wraps.
|
|
111
|
+
const currentMsg = $derived(Math.min(tick, messages.length - 1));
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Build the three cells that draw a single arrow: a run of dashes, a
|
|
115
|
+
* centered label, and the head. The label is opaque so it sits on top of
|
|
116
|
+
* the dashes without us having to split the run.
|
|
117
|
+
*/
|
|
118
|
+
function arrowCells(c1: number, c2: number, isRight: boolean, lbl: string, cls: string): ArtCell[] {
|
|
119
|
+
const c1min = Math.min(c1, c2);
|
|
120
|
+
const c2max = Math.max(c1, c2);
|
|
121
|
+
const headX = isRight ? c2max - 1 : c1min + 1;
|
|
122
|
+
const dashStart = isRight ? c1min + 1 : headX + 1;
|
|
123
|
+
const dashEnd = isRight ? headX - 1 : c2max - 1;
|
|
124
|
+
const labelCenter = Math.round((dashStart + dashEnd) / 2);
|
|
125
|
+
const labelStart = Math.max(dashStart, labelCenter - Math.floor(lbl.length / 2));
|
|
126
|
+
const dashLen = Math.max(0, dashEnd - dashStart + 1);
|
|
127
|
+
return [
|
|
128
|
+
{ at: dashStart, text: '─'.repeat(dashLen), cls },
|
|
129
|
+
{ at: labelStart, text: lbl, cls },
|
|
130
|
+
{ at: headX, text: isRight ? '▶' : '◀', cls }
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const view = $derived.by((): Seg[][] => {
|
|
135
|
+
const lines: Seg[][] = [];
|
|
136
|
+
|
|
137
|
+
for (let y = 0; y < height; y++) {
|
|
138
|
+
const cells: ArtCell[] = [];
|
|
139
|
+
const isHeader = y === headerRow;
|
|
140
|
+
const msgIdx = y - firstMsgRow;
|
|
141
|
+
const inRange = msgIdx >= 0 && msgIdx < messages.length;
|
|
142
|
+
const m = inRange ? messages[msgIdx]! : null;
|
|
143
|
+
const isCurrent = m !== null && msgIdx === currentMsg;
|
|
144
|
+
|
|
145
|
+
// Lifelines — skip on the header row so the participant name
|
|
146
|
+
// can sit centered on the column without a `│` punching through.
|
|
147
|
+
if (!isHeader) {
|
|
148
|
+
for (let i = 0; i < participants.length; i++) {
|
|
149
|
+
const p = participants[i]!;
|
|
150
|
+
const act = activations.find((a) => a.pIdx === i);
|
|
151
|
+
const inActivation = !!act && y >= act.startRow && y <= act.endRow;
|
|
152
|
+
const isCurrentPart = isCurrent && m !== null && (i === m.from || i === m.to);
|
|
153
|
+
|
|
154
|
+
if (inActivation) {
|
|
155
|
+
cells.push({ at: p.col, text: '█', cls: isCurrentPart ? 'on' : 'active' });
|
|
156
|
+
} else {
|
|
157
|
+
cells.push({ at: p.col, text: '│', cls: 'lifeline' });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Participant labels on the header row.
|
|
163
|
+
if (isHeader) {
|
|
164
|
+
for (const p of participants) {
|
|
165
|
+
const start = p.col - Math.floor(p.label.length / 2);
|
|
166
|
+
cells.push({ at: start, text: p.label, cls: 'participant' });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Arrow row for the current message (if any).
|
|
171
|
+
if (m !== null) {
|
|
172
|
+
const p1 = participants[m.from]!;
|
|
173
|
+
const p2 = participants[m.to]!;
|
|
174
|
+
const isRight = p2.col > p1.col;
|
|
175
|
+
const cls = isCurrent ? 'on' : 'arrow';
|
|
176
|
+
cells.push(...arrowCells(p1.col, p2.col, isRight, m.label, cls));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
lines.push(overlayRow(width, cells));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return lines;
|
|
183
|
+
});
|
|
184
|
+
</script>
|
|
185
|
+
|
|
186
|
+
<Graph {title} class={className}>
|
|
187
|
+
<GraphBody>
|
|
188
|
+
<div class="sequence">
|
|
189
|
+
<div class="viewport">
|
|
190
|
+
<pre class="art" aria-hidden="true"><code>{#each view as line, i (i)}{#each line as seg, j (j)}{#if seg.cls}<span class={seg.cls}>{seg.text}</span>{:else}{seg.text}{/if}{/each}{#if i < view.length - 1}{'\n'}{/if}{/each}</code></pre>
|
|
191
|
+
</div>
|
|
192
|
+
{#if label}
|
|
193
|
+
<p class="caption">{label}</p>
|
|
194
|
+
{/if}
|
|
195
|
+
</div>
|
|
196
|
+
</GraphBody>
|
|
197
|
+
</Graph>
|
|
198
|
+
|
|
199
|
+
<style>
|
|
200
|
+
.sequence {
|
|
201
|
+
display: flex;
|
|
202
|
+
flex-direction: column;
|
|
203
|
+
gap: 1rem;
|
|
204
|
+
min-width: 0;
|
|
205
|
+
font-size: 0.875rem;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.viewport {
|
|
209
|
+
overflow-x: auto;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.art {
|
|
213
|
+
margin: 0;
|
|
214
|
+
font-size: 0.85rem;
|
|
215
|
+
line-height: 1.2;
|
|
216
|
+
color: var(--graph-foreground, oklch(0.93 0 0));
|
|
217
|
+
white-space: pre;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.participant {
|
|
221
|
+
color: var(--graph-foreground, oklch(0.93 0 0));
|
|
222
|
+
font-weight: 600;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.lifeline {
|
|
226
|
+
color: var(--graph-frame, oklch(0.6 0 0 / 0.5));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.active {
|
|
230
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.arrow {
|
|
234
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.on {
|
|
238
|
+
color: var(--graph-accent, oklch(0.78 0.17 155));
|
|
239
|
+
font-weight: 600;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.caption {
|
|
243
|
+
margin: 0;
|
|
244
|
+
font-size: 0.8rem;
|
|
245
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
246
|
+
}
|
|
247
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface GraphSequenceProps {
|
|
2
|
+
/** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
|
|
3
|
+
title: string;
|
|
4
|
+
/** Animate the message flow. Default true. Reduced motion freezes the first message as current. */
|
|
5
|
+
animated?: boolean;
|
|
6
|
+
/** Width of the scene in characters; at least 40 so four participants fit. Default 68. */
|
|
7
|
+
cols?: number;
|
|
8
|
+
/** Height of the scene in rows; at least 10 so header + 6 messages fit. Default 12. */
|
|
9
|
+
rows?: number;
|
|
10
|
+
/** Milliseconds per message. Default 600. */
|
|
11
|
+
speedMs?: number;
|
|
12
|
+
/** Short caption under the art; the diagram itself is the message. */
|
|
13
|
+
label?: string;
|
|
14
|
+
class?: string;
|
|
15
|
+
}
|
|
16
|
+
declare const GraphSequence: import("svelte").Component<GraphSequenceProps, {}, "">;
|
|
17
|
+
type GraphSequence = ReturnType<typeof GraphSequence>;
|
|
18
|
+
export default GraphSequence;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
/** Which built-in glyph spinner to show. */
|
|
3
|
+
export type SpinnerKind = 'slash' | 'dots' | 'bounce' | 'bar';
|
|
4
|
+
|
|
5
|
+
/** Built-in frame sets. All glyphs are inside the Geist Mono whitelist. */
|
|
6
|
+
const FRAME_SETS: Record<SpinnerKind, string[]> = {
|
|
7
|
+
slash: ['|', '/', '-', '\\'],
|
|
8
|
+
dots: ['·', '··', '···', '··'],
|
|
9
|
+
bounce: ['▁', '▃', '▄', '▅', '▆', '▇', '█', '▇', '▆', '▄', '▃'],
|
|
10
|
+
bar: ['▏', '▎', '▍', '▌', '▋', '▊', '▉', '█']
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export interface GraphSpinnersProps {
|
|
14
|
+
/** Caption drawn on the frame's top edge as `[ TITLE ]`. */
|
|
15
|
+
title: string;
|
|
16
|
+
/** Which glyph spinner to show. Default 'dots'. */
|
|
17
|
+
kind?: SpinnerKind;
|
|
18
|
+
/** Text shown after the spinner. Default 'loading'. */
|
|
19
|
+
label?: string;
|
|
20
|
+
/** Milliseconds per frame. Default 120. */
|
|
21
|
+
speedMs?: number;
|
|
22
|
+
/** Custom frame set. Overrides `kind` when non-empty. Padded to equal width. */
|
|
23
|
+
frames?: string[];
|
|
24
|
+
/** Animate the frames. Default true. Reduced motion holds frame 0. */
|
|
25
|
+
animated?: boolean;
|
|
26
|
+
/** Character at each corner of the frame. Default '+'. */
|
|
27
|
+
corner?: string;
|
|
28
|
+
class?: string;
|
|
29
|
+
}
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<script lang="ts">
|
|
33
|
+
import Graph from '../frame/Graph.svelte';
|
|
34
|
+
import GraphBody from '../frame/GraphBody.svelte';
|
|
35
|
+
|
|
36
|
+
let {
|
|
37
|
+
title,
|
|
38
|
+
kind = 'dots',
|
|
39
|
+
label = 'loading',
|
|
40
|
+
speedMs = 120,
|
|
41
|
+
frames,
|
|
42
|
+
animated = true,
|
|
43
|
+
corner,
|
|
44
|
+
class: className = ''
|
|
45
|
+
}: GraphSpinnersProps = $props();
|
|
46
|
+
|
|
47
|
+
let frame = $state(0);
|
|
48
|
+
|
|
49
|
+
const moving = $derived(
|
|
50
|
+
animated &&
|
|
51
|
+
(typeof window === 'undefined' ||
|
|
52
|
+
!window.matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
/** Active frames, padded with trailing spaces so nothing shifts between steps. */
|
|
56
|
+
const effFrames = $derived.by(() => {
|
|
57
|
+
const src = frames && frames.length > 0 ? frames : FRAME_SETS[kind];
|
|
58
|
+
const width = src.reduce((max, f) => Math.max(max, f.length), 1);
|
|
59
|
+
|
|
60
|
+
return src.map((f) => f + ' '.repeat(width - f.length));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
$effect(() => {
|
|
64
|
+
if (!moving || effFrames.length === 0) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const timer = window.setInterval(() => {
|
|
69
|
+
frame = (frame + 1) % effFrames.length;
|
|
70
|
+
}, Math.max(50, speedMs));
|
|
71
|
+
|
|
72
|
+
return () => window.clearInterval(timer);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Static or reduced motion: hold frame 0.
|
|
76
|
+
const shown = $derived(
|
|
77
|
+
moving && effFrames.length > 0 ? effFrames[frame % effFrames.length] : (effFrames[0] ?? '')
|
|
78
|
+
);
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<Graph {title} {corner} class={className}>
|
|
82
|
+
<GraphBody>
|
|
83
|
+
<div class="spinner">
|
|
84
|
+
<pre class="art"><code><span class="glyph">{shown}</span><span class="label"> {label}</span></code></pre>
|
|
85
|
+
</div>
|
|
86
|
+
</GraphBody>
|
|
87
|
+
</Graph>
|
|
88
|
+
|
|
89
|
+
<style>
|
|
90
|
+
.spinner {
|
|
91
|
+
min-width: 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.art {
|
|
95
|
+
margin: 0;
|
|
96
|
+
font-size: 0.85rem;
|
|
97
|
+
line-height: 1.35;
|
|
98
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
99
|
+
white-space: pre;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.glyph {
|
|
103
|
+
color: var(--graph-accent, oklch(0.78 0.17 155));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.label {
|
|
107
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
108
|
+
}
|
|
109
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Which built-in glyph spinner to show. */
|
|
2
|
+
export type SpinnerKind = 'slash' | 'dots' | 'bounce' | 'bar';
|
|
3
|
+
export interface GraphSpinnersProps {
|
|
4
|
+
/** Caption drawn on the frame's top edge as `[ TITLE ]`. */
|
|
5
|
+
title: string;
|
|
6
|
+
/** Which glyph spinner to show. Default 'dots'. */
|
|
7
|
+
kind?: SpinnerKind;
|
|
8
|
+
/** Text shown after the spinner. Default 'loading'. */
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Milliseconds per frame. Default 120. */
|
|
11
|
+
speedMs?: number;
|
|
12
|
+
/** Custom frame set. Overrides `kind` when non-empty. Padded to equal width. */
|
|
13
|
+
frames?: string[];
|
|
14
|
+
/** Animate the frames. Default true. Reduced motion holds frame 0. */
|
|
15
|
+
animated?: boolean;
|
|
16
|
+
/** Character at each corner of the frame. Default '+'. */
|
|
17
|
+
corner?: string;
|
|
18
|
+
class?: string;
|
|
19
|
+
}
|
|
20
|
+
declare const GraphSpinners: import("svelte").Component<GraphSpinnersProps, {}, "">;
|
|
21
|
+
type GraphSpinners = ReturnType<typeof GraphSpinners>;
|
|
22
|
+
export default GraphSpinners;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export interface GraphStateProps {
|
|
3
|
+
/** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
|
|
4
|
+
title: string;
|
|
5
|
+
/** Animate the state cycle. Default true. Reduced motion freezes on the start state. */
|
|
6
|
+
animated?: boolean;
|
|
7
|
+
/** Milliseconds per state step. Default 700. */
|
|
8
|
+
speedMs?: number;
|
|
9
|
+
/** Short caption under the art; the chart itself is the message. */
|
|
10
|
+
label?: string;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface State {
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
col: number;
|
|
18
|
+
row: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface Transition {
|
|
22
|
+
from: number;
|
|
23
|
+
to: number;
|
|
24
|
+
label: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface Seg {
|
|
28
|
+
text: string;
|
|
29
|
+
cls?: string;
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<script lang="ts">
|
|
34
|
+
import Graph from '../frame/Graph.svelte';
|
|
35
|
+
import GraphBody from '../frame/GraphBody.svelte';
|
|
36
|
+
import { dashEdge, overlayRow, type ArtCell } from '../diagram/ascii';
|
|
37
|
+
|
|
38
|
+
let {
|
|
39
|
+
title,
|
|
40
|
+
animated = true,
|
|
41
|
+
speedMs = 700,
|
|
42
|
+
label = 'state machine, stepping through',
|
|
43
|
+
class: className = ''
|
|
44
|
+
}: GraphStateProps = $props();
|
|
45
|
+
|
|
46
|
+
// Hardcoded 2×2 layout: 14-wide boxes, vertical and horizontal arrows in
|
|
47
|
+
// the gutters, 1-char gap between everything. W/H are scene dimensions
|
|
48
|
+
// in characters; the Graph frame adds one row/col on each side.
|
|
49
|
+
const W = 44;
|
|
50
|
+
const H = 15;
|
|
51
|
+
const BOX_W = 14;
|
|
52
|
+
const BOX_H = 3;
|
|
53
|
+
|
|
54
|
+
// svelte-ignore state_referenced_locally
|
|
55
|
+
const states: State[] = [
|
|
56
|
+
{ id: 'ready', label: 'ready', col: 2, row: 2 },
|
|
57
|
+
{ id: 'think', label: 'think', col: 24, row: 2 },
|
|
58
|
+
{ id: 'act', label: 'act', col: 24, row: 11 },
|
|
59
|
+
{ id: 'done', label: 'done', col: 2, row: 11 }
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
// svelte-ignore state_referenced_locally
|
|
63
|
+
const transitions: Transition[] = [
|
|
64
|
+
{ from: 0, to: 1, label: 'ask' },
|
|
65
|
+
{ from: 1, to: 2, label: 'plan' },
|
|
66
|
+
{ from: 2, to: 3, label: 'ok' },
|
|
67
|
+
{ from: 3, to: 0, label: 'reset' }
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// svelte-ignore state_referenced_locally
|
|
71
|
+
let tick = $state(0);
|
|
72
|
+
|
|
73
|
+
const moving = $derived(
|
|
74
|
+
animated &&
|
|
75
|
+
(typeof window === 'undefined' ||
|
|
76
|
+
!window.matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
$effect(() => {
|
|
80
|
+
if (!moving) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const timer = window.setInterval(() => {
|
|
84
|
+
tick = (tick + 1) % states.length;
|
|
85
|
+
}, speedMs);
|
|
86
|
+
return () => window.clearInterval(timer);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// The current state and its outgoing transition both glow. Under reduced
|
|
90
|
+
// motion this freezes on the start state with the first transition lit,
|
|
91
|
+
// which reads as "ready to begin".
|
|
92
|
+
const currentState = $derived(tick % states.length);
|
|
93
|
+
const currentTransition = $derived(transitions[currentState]!);
|
|
94
|
+
|
|
95
|
+
/** Build the three cells that draw one state box at (col, row). */
|
|
96
|
+
function boxRow(s: State, y: number, cls: string): ArtCell {
|
|
97
|
+
const top = dashEdge(BOX_W);
|
|
98
|
+
const inner = BOX_W - 2;
|
|
99
|
+
const pad = inner - s.label.length;
|
|
100
|
+
const left = Math.floor(pad / 2);
|
|
101
|
+
const right = pad - left;
|
|
102
|
+
const mid = '|' + ' '.repeat(left) + s.label + ' '.repeat(right) + '|';
|
|
103
|
+
const yOff = y - s.row;
|
|
104
|
+
// yOff: 0 = top edge, 1 = label row, 2 = bottom edge.
|
|
105
|
+
const text = yOff === 0 || yOff === 2 ? top : mid;
|
|
106
|
+
return { at: s.col, text, cls };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Build the cells that draw one horizontal arrow between two boxes. */
|
|
110
|
+
function hArrowCells(c1: number, c2: number, lbl: string, cls: string): ArtCell[] {
|
|
111
|
+
const isRight = c2 > c1;
|
|
112
|
+
if (isRight) {
|
|
113
|
+
const dashStart = c1 + BOX_W;
|
|
114
|
+
const dashEnd = c2 - 2;
|
|
115
|
+
const headX = c2 - 1;
|
|
116
|
+
const labelCenter = Math.round((dashStart + dashEnd) / 2);
|
|
117
|
+
const labelStart = Math.max(dashStart, labelCenter - Math.floor(lbl.length / 2));
|
|
118
|
+
const dashLen = Math.max(0, dashEnd - dashStart + 1);
|
|
119
|
+
return [
|
|
120
|
+
{ at: dashStart, text: '─'.repeat(dashLen), cls },
|
|
121
|
+
{ at: labelStart, text: lbl, cls },
|
|
122
|
+
{ at: headX, text: '▶', cls }
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
const dashStart = c2 + BOX_W + 1;
|
|
126
|
+
const dashEnd = c1 - 1;
|
|
127
|
+
const headX = c2 + BOX_W;
|
|
128
|
+
const labelCenter = Math.round((dashStart + dashEnd) / 2);
|
|
129
|
+
const labelStart = Math.max(dashStart, labelCenter - Math.floor(lbl.length / 2));
|
|
130
|
+
const dashLen = Math.max(0, dashEnd - dashStart + 1);
|
|
131
|
+
return [
|
|
132
|
+
{ at: headX, text: '◀', cls },
|
|
133
|
+
{ at: labelStart, text: lbl, cls },
|
|
134
|
+
{ at: dashStart, text: '─'.repeat(dashLen), cls }
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const view = $derived.by((): Seg[][] => {
|
|
139
|
+
const lines: Seg[][] = [];
|
|
140
|
+
|
|
141
|
+
for (let y = 0; y < H; y++) {
|
|
142
|
+
const cells: ArtCell[] = [];
|
|
143
|
+
|
|
144
|
+
// State boxes.
|
|
145
|
+
for (let i = 0; i < states.length; i++) {
|
|
146
|
+
const s = states[i]!;
|
|
147
|
+
const inBox = y >= s.row && y <= s.row + BOX_H - 1;
|
|
148
|
+
if (!inBox) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const isCurrent = i === currentState;
|
|
152
|
+
const cls = isCurrent ? 'on' : 'state';
|
|
153
|
+
cells.push(boxRow(s, y, cls));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Top horizontal: ready → think (row 3).
|
|
157
|
+
if (y === 3) {
|
|
158
|
+
const t = transitions[0]!;
|
|
159
|
+
const isCurrentT =
|
|
160
|
+
currentTransition.from === t.from && currentTransition.to === t.to;
|
|
161
|
+
cells.push(
|
|
162
|
+
...hArrowCells(
|
|
163
|
+
states[t.from]!.col,
|
|
164
|
+
states[t.to]!.col,
|
|
165
|
+
t.label,
|
|
166
|
+
isCurrentT ? 'on' : 'arrow'
|
|
167
|
+
)
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Bottom horizontal: act → done (row 12), right to left.
|
|
172
|
+
if (y === 12) {
|
|
173
|
+
const t = transitions[2]!;
|
|
174
|
+
const isCurrentT =
|
|
175
|
+
currentTransition.from === t.from && currentTransition.to === t.to;
|
|
176
|
+
cells.push(
|
|
177
|
+
...hArrowCells(
|
|
178
|
+
states[t.from]!.col,
|
|
179
|
+
states[t.to]!.col,
|
|
180
|
+
t.label,
|
|
181
|
+
isCurrentT ? 'on' : 'arrow'
|
|
182
|
+
)
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Right vertical: think → act (col 38, rows 5–10, head v at row 10).
|
|
187
|
+
if (y >= 5 && y <= 10) {
|
|
188
|
+
const t = transitions[1]!;
|
|
189
|
+
const isCurrentT =
|
|
190
|
+
currentTransition.from === t.from && currentTransition.to === t.to;
|
|
191
|
+
const cls = isCurrentT ? 'on' : 'arrow';
|
|
192
|
+
cells.push({ at: 38, text: y === 10 ? 'v' : '│', cls });
|
|
193
|
+
if (y === 7) {
|
|
194
|
+
cells.push({ at: 39, text: t.label, cls });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Left vertical: done → ready (col 16, rows 5–10, head ^ at row 5).
|
|
199
|
+
if (y >= 5 && y <= 10) {
|
|
200
|
+
const t = transitions[3]!;
|
|
201
|
+
const isCurrentT =
|
|
202
|
+
currentTransition.from === t.from && currentTransition.to === t.to;
|
|
203
|
+
const cls = isCurrentT ? 'on' : 'arrow';
|
|
204
|
+
cells.push({ at: 16, text: y === 5 ? '^' : '│', cls });
|
|
205
|
+
if (y === 8) {
|
|
206
|
+
cells.push({ at: 17, text: t.label, cls });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
lines.push(overlayRow(W, cells));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return lines;
|
|
214
|
+
});
|
|
215
|
+
</script>
|
|
216
|
+
|
|
217
|
+
<Graph {title} class={className}>
|
|
218
|
+
<GraphBody>
|
|
219
|
+
<div class="state">
|
|
220
|
+
<div class="viewport">
|
|
221
|
+
<pre class="art" aria-hidden="true"><code>{#each view as line, i (i)}{#each line as seg, j (j)}{#if seg.cls}<span class={seg.cls}>{seg.text}</span>{:else}{seg.text}{/if}{/each}{#if i < view.length - 1}{'\n'}{/if}{/each}</code></pre>
|
|
222
|
+
</div>
|
|
223
|
+
{#if label}
|
|
224
|
+
<p class="caption">{label}</p>
|
|
225
|
+
{/if}
|
|
226
|
+
</div>
|
|
227
|
+
</GraphBody>
|
|
228
|
+
</Graph>
|
|
229
|
+
|
|
230
|
+
<style>
|
|
231
|
+
.state {
|
|
232
|
+
display: flex;
|
|
233
|
+
flex-direction: column;
|
|
234
|
+
gap: 1rem;
|
|
235
|
+
min-width: 0;
|
|
236
|
+
font-size: 0.875rem;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.viewport {
|
|
240
|
+
overflow-x: auto;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.art {
|
|
244
|
+
margin: 0;
|
|
245
|
+
font-size: 0.85rem;
|
|
246
|
+
line-height: 1.2;
|
|
247
|
+
color: var(--graph-foreground, oklch(0.93 0 0));
|
|
248
|
+
white-space: pre;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* Per-class styling for the spans inside <pre>. */
|
|
252
|
+
:global(.state span.state) {
|
|
253
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
:global(.state span.on) {
|
|
257
|
+
color: var(--graph-accent, oklch(0.78 0.17 155));
|
|
258
|
+
font-weight: 600;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
:global(.state span.arrow) {
|
|
262
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.caption {
|
|
266
|
+
margin: 0;
|
|
267
|
+
font-size: 0.8rem;
|
|
268
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
269
|
+
}
|
|
270
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface GraphStateProps {
|
|
2
|
+
/** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
|
|
3
|
+
title: string;
|
|
4
|
+
/** Animate the state cycle. Default true. Reduced motion freezes on the start state. */
|
|
5
|
+
animated?: boolean;
|
|
6
|
+
/** Milliseconds per state step. Default 700. */
|
|
7
|
+
speedMs?: number;
|
|
8
|
+
/** Short caption under the art; the chart itself is the message. */
|
|
9
|
+
label?: string;
|
|
10
|
+
class?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const GraphState: import("svelte").Component<GraphStateProps, {}, "">;
|
|
13
|
+
type GraphState = ReturnType<typeof GraphState>;
|
|
14
|
+
export default GraphState;
|