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,258 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export interface GraphFlameProps {
|
|
3
|
+
/** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
|
|
4
|
+
title: string;
|
|
5
|
+
/** Animate the hot-path cycle. Default true. Reduced motion freezes on the first leaf. */
|
|
6
|
+
animated?: boolean;
|
|
7
|
+
/** Milliseconds per leaf. Default 600. */
|
|
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 Frame {
|
|
15
|
+
name: string;
|
|
16
|
+
width: number;
|
|
17
|
+
children?: Frame[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Positioned {
|
|
21
|
+
name: string;
|
|
22
|
+
width: number;
|
|
23
|
+
row: number;
|
|
24
|
+
startX: number;
|
|
25
|
+
endX: number;
|
|
26
|
+
inPath: boolean;
|
|
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
|
+
speedMs = 600,
|
|
44
|
+
label = 'flame graph, width = time',
|
|
45
|
+
class: className = ''
|
|
46
|
+
}: GraphFlameProps = $props();
|
|
47
|
+
|
|
48
|
+
// Scene width in characters. Each frame's width is its share of the
|
|
49
|
+
// parent's width; siblings sum to the parent. 60 chars reads cleanly
|
|
50
|
+
// even at the narrowest leaves.
|
|
51
|
+
const W = 60;
|
|
52
|
+
|
|
53
|
+
// svelte-ignore state_referenced_locally
|
|
54
|
+
const root: Frame = {
|
|
55
|
+
name: 'main',
|
|
56
|
+
width: W,
|
|
57
|
+
children: [
|
|
58
|
+
{
|
|
59
|
+
name: 'boot',
|
|
60
|
+
width: 18,
|
|
61
|
+
children: [
|
|
62
|
+
{
|
|
63
|
+
name: 'init',
|
|
64
|
+
width: 8,
|
|
65
|
+
children: [
|
|
66
|
+
{ name: 'load', width: 5 },
|
|
67
|
+
{ name: 'parse', width: 3 }
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
{ name: 'check', width: 10 }
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'run',
|
|
75
|
+
width: 30,
|
|
76
|
+
children: [
|
|
77
|
+
{
|
|
78
|
+
name: 'work',
|
|
79
|
+
width: 18,
|
|
80
|
+
children: [
|
|
81
|
+
{ name: 'step', width: 8 },
|
|
82
|
+
{ name: 'io', width: 10 }
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
{ name: 'tail', width: 12 }
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
{ name: 'exit', width: 12 }
|
|
89
|
+
]
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Each leaf has a path of ancestors from the root. One tick per leaf
|
|
93
|
+
// keeps the cycle short and the visual change easy to read.
|
|
94
|
+
// svelte-ignore state_referenced_locally
|
|
95
|
+
const paths: string[][] = [
|
|
96
|
+
['main', 'boot', 'init', 'load'],
|
|
97
|
+
['main', 'boot', 'init', 'parse'],
|
|
98
|
+
['main', 'run', 'work', 'step'],
|
|
99
|
+
['main', 'run', 'work', 'io']
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
// svelte-ignore state_referenced_locally
|
|
103
|
+
let tick = $state(0);
|
|
104
|
+
|
|
105
|
+
const moving = $derived(
|
|
106
|
+
animated &&
|
|
107
|
+
(typeof window === 'undefined' ||
|
|
108
|
+
!window.matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
$effect(() => {
|
|
112
|
+
if (!moving) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const timer = window.setInterval(() => {
|
|
116
|
+
tick = (tick + 1) % paths.length;
|
|
117
|
+
}, speedMs);
|
|
118
|
+
return () => window.clearInterval(timer);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Frozen on paths[0] under reduced motion — the load leaf and its
|
|
122
|
+
// chain, so the static frame already reads as "the hot path".
|
|
123
|
+
const currentPath = $derived(new Set(paths[tick % paths.length]!));
|
|
124
|
+
|
|
125
|
+
function maxDepth(frame: Frame, depth = 0): number {
|
|
126
|
+
if (!frame.children || frame.children.length === 0) {
|
|
127
|
+
return depth;
|
|
128
|
+
}
|
|
129
|
+
let best = depth;
|
|
130
|
+
for (const c of frame.children) {
|
|
131
|
+
const d = maxDepth(c, depth + 1);
|
|
132
|
+
if (d > best) {
|
|
133
|
+
best = d;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return best;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// svelte-ignore state_referenced_locally
|
|
140
|
+
const H = maxDepth(root) + 1;
|
|
141
|
+
|
|
142
|
+
function layout(
|
|
143
|
+
frame: Frame,
|
|
144
|
+
row: number,
|
|
145
|
+
startX: number,
|
|
146
|
+
path: Set<string>,
|
|
147
|
+
out: Positioned[]
|
|
148
|
+
): void {
|
|
149
|
+
out.push({
|
|
150
|
+
name: frame.name,
|
|
151
|
+
width: frame.width,
|
|
152
|
+
row,
|
|
153
|
+
startX,
|
|
154
|
+
endX: startX + frame.width,
|
|
155
|
+
inPath: path.has(frame.name)
|
|
156
|
+
});
|
|
157
|
+
if (frame.children) {
|
|
158
|
+
let cx = startX;
|
|
159
|
+
for (const child of frame.children) {
|
|
160
|
+
layout(child, row + 1, cx, path, out);
|
|
161
|
+
cx += child.width;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Draw one frame as a bar of glyphs. Narrow frames collapse to just the
|
|
168
|
+
* side borders; wider frames get a left-aligned label and a fill of
|
|
169
|
+
* shade glyphs. The fill density is the only thing that distinguishes
|
|
170
|
+
* a hot frame from a cool one in monochrome.
|
|
171
|
+
*/
|
|
172
|
+
function renderFrame(name: string, width: number, isHot: boolean): string {
|
|
173
|
+
if (width < 1) {
|
|
174
|
+
return '';
|
|
175
|
+
}
|
|
176
|
+
if (width === 1) {
|
|
177
|
+
return '│';
|
|
178
|
+
}
|
|
179
|
+
if (width === 2) {
|
|
180
|
+
return '││';
|
|
181
|
+
}
|
|
182
|
+
const interior = width - 2;
|
|
183
|
+
const labelLen = Math.min(name.length, interior);
|
|
184
|
+
const label = name.slice(0, labelLen);
|
|
185
|
+
const fillCount = interior - labelLen;
|
|
186
|
+
const fill = isHot ? '█' : '▒';
|
|
187
|
+
return '│' + label + fill.repeat(fillCount) + '│';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const view = $derived.by((): Seg[][] => {
|
|
191
|
+
const positioned: Positioned[] = [];
|
|
192
|
+
layout(root, 0, 0, currentPath, positioned);
|
|
193
|
+
|
|
194
|
+
const lines: Seg[][] = [];
|
|
195
|
+
for (let row = 0; row < H; row++) {
|
|
196
|
+
const cells: ArtCell[] = positioned
|
|
197
|
+
.filter((f) => f.row === row)
|
|
198
|
+
.map((f) => ({
|
|
199
|
+
at: f.startX,
|
|
200
|
+
text: renderFrame(f.name, f.width, f.inPath),
|
|
201
|
+
cls: f.inPath ? 'on' : 'flame'
|
|
202
|
+
}));
|
|
203
|
+
lines.push(overlayRow(W, cells));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return lines;
|
|
207
|
+
});
|
|
208
|
+
</script>
|
|
209
|
+
|
|
210
|
+
<Graph {title} class={className}>
|
|
211
|
+
<GraphBody>
|
|
212
|
+
<div class="flame">
|
|
213
|
+
<div class="viewport">
|
|
214
|
+
<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>
|
|
215
|
+
</div>
|
|
216
|
+
{#if label}
|
|
217
|
+
<p class="caption">{label}</p>
|
|
218
|
+
{/if}
|
|
219
|
+
</div>
|
|
220
|
+
</GraphBody>
|
|
221
|
+
</Graph>
|
|
222
|
+
|
|
223
|
+
<style>
|
|
224
|
+
.flame {
|
|
225
|
+
display: flex;
|
|
226
|
+
flex-direction: column;
|
|
227
|
+
gap: 1rem;
|
|
228
|
+
min-width: 0;
|
|
229
|
+
font-size: 0.875rem;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.viewport {
|
|
233
|
+
overflow-x: auto;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.art {
|
|
237
|
+
margin: 0;
|
|
238
|
+
font-size: 0.85rem;
|
|
239
|
+
line-height: 1.2;
|
|
240
|
+
color: var(--graph-foreground, oklch(0.93 0 0));
|
|
241
|
+
white-space: pre;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
:global(.flame span.flame) {
|
|
245
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
:global(.flame span.on) {
|
|
249
|
+
color: var(--graph-accent, oklch(0.78 0.17 155));
|
|
250
|
+
font-weight: 600;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.caption {
|
|
254
|
+
margin: 0;
|
|
255
|
+
font-size: 0.8rem;
|
|
256
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
257
|
+
}
|
|
258
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface GraphFlameProps {
|
|
2
|
+
/** Caption drawn on the top edge as `[ TITLE ]`. Uppercase, 1–2 words. */
|
|
3
|
+
title: string;
|
|
4
|
+
/** Animate the hot-path cycle. Default true. Reduced motion freezes on the first leaf. */
|
|
5
|
+
animated?: boolean;
|
|
6
|
+
/** Milliseconds per leaf. Default 600. */
|
|
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 GraphFlame: import("svelte").Component<GraphFlameProps, {}, "">;
|
|
13
|
+
type GraphFlame = ReturnType<typeof GraphFlame>;
|
|
14
|
+
export default GraphFlame;
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
/** One ordered step of a process. */
|
|
3
|
+
export interface FlowStep {
|
|
4
|
+
/** Short name shown beside the marker. */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Extra line revealed while the step is active. */
|
|
7
|
+
detail?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface GraphFlowPlayerProps {
|
|
11
|
+
/** Caption drawn on the frame's top edge as `[ TITLE ]`. */
|
|
12
|
+
title: string;
|
|
13
|
+
/** Steps in playback order. */
|
|
14
|
+
steps: FlowStep[];
|
|
15
|
+
/** Start playing on mount. Default false. Never autoplays under reduced motion. */
|
|
16
|
+
autoPlay?: boolean;
|
|
17
|
+
/** Milliseconds per step. Default 1600. */
|
|
18
|
+
speedMs?: number;
|
|
19
|
+
/** Show the playback controls. Default true. */
|
|
20
|
+
showControls?: boolean;
|
|
21
|
+
/** Animate transitions and the active marker pulse. Default true. */
|
|
22
|
+
animated?: boolean;
|
|
23
|
+
/** Character at each corner of the frame. Default '+'. */
|
|
24
|
+
corner?: string;
|
|
25
|
+
class?: string;
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<script lang="ts">
|
|
30
|
+
import Graph from '../frame/Graph.svelte';
|
|
31
|
+
import GraphBody from '../frame/GraphBody.svelte';
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
title,
|
|
35
|
+
steps,
|
|
36
|
+
autoPlay = false,
|
|
37
|
+
speedMs = 1600,
|
|
38
|
+
showControls = true,
|
|
39
|
+
animated = true,
|
|
40
|
+
corner,
|
|
41
|
+
class: className = ''
|
|
42
|
+
}: GraphFlowPlayerProps = $props();
|
|
43
|
+
|
|
44
|
+
const uid = $props.id();
|
|
45
|
+
|
|
46
|
+
interface Seg {
|
|
47
|
+
text: string;
|
|
48
|
+
cls?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let step = $state(0);
|
|
52
|
+
let playing = $state(false);
|
|
53
|
+
// svelte-ignore state_referenced_locally
|
|
54
|
+
let intervalTime = $state(speedMs);
|
|
55
|
+
|
|
56
|
+
const count = $derived(steps.length);
|
|
57
|
+
const active = $derived(count === 0 ? 0 : Math.min(step, count - 1));
|
|
58
|
+
const digits = $derived(Math.max(1, String(count).length));
|
|
59
|
+
|
|
60
|
+
const motionAllowed = $derived(
|
|
61
|
+
animated &&
|
|
62
|
+
(typeof window === 'undefined' ||
|
|
63
|
+
!window.matchMedia('(prefers-reduced-motion: reduce)').matches)
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Autoplay only when motion is allowed; the controls can always start playback.
|
|
67
|
+
$effect(() => {
|
|
68
|
+
if (autoPlay && motionAllowed) {
|
|
69
|
+
playing = true;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
$effect(() => {
|
|
74
|
+
if (!playing || count === 0) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const timer = window.setInterval(() => {
|
|
79
|
+
step = (active + 1) % count;
|
|
80
|
+
}, Math.max(100, intervalTime));
|
|
81
|
+
|
|
82
|
+
return () => window.clearInterval(timer);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
function handleTogglePlay() {
|
|
86
|
+
if (count === 0) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
playing = !playing;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function handleNext() {
|
|
94
|
+
if (count === 0) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
step = (active + 1) % count;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function handlePrev() {
|
|
102
|
+
if (count === 0) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
step = (active - 1 + count) % count;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Column of the `│` spine, under the digit of a `[ 1 ]` marker. */
|
|
110
|
+
const SPINE_COL = 2;
|
|
111
|
+
|
|
112
|
+
const art = $derived.by((): Seg[][] => {
|
|
113
|
+
if (count === 0) {
|
|
114
|
+
return [[{ text: '( no steps )', cls: 'detail' }]];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const lines: Seg[][] = [];
|
|
118
|
+
const labelCol = digits + 5;
|
|
119
|
+
|
|
120
|
+
steps.forEach((s, i) => {
|
|
121
|
+
if (i > 0) {
|
|
122
|
+
lines.push([
|
|
123
|
+
{
|
|
124
|
+
text: ' '.repeat(SPINE_COL) + '│',
|
|
125
|
+
cls: i <= active ? 'spine done' : 'spine'
|
|
126
|
+
}
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const isActive = i === active;
|
|
131
|
+
const isDone = i < active;
|
|
132
|
+
const num = String(i + 1).padStart(digits, ' ');
|
|
133
|
+
|
|
134
|
+
lines.push([
|
|
135
|
+
{
|
|
136
|
+
text: `[ ${num} ]`,
|
|
137
|
+
cls: isActive ? 'marker active' : isDone ? 'marker done' : 'marker'
|
|
138
|
+
},
|
|
139
|
+
{ text: ` ${s.label}`, cls: isActive ? 'label active' : isDone ? 'label done' : 'label' }
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
if (isActive && s.detail) {
|
|
143
|
+
lines.push([{ text: ' '.repeat(labelCol) }, { text: s.detail, cls: 'detail' }]);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return lines;
|
|
148
|
+
});
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
<Graph {title} {corner} class={className}>
|
|
152
|
+
<GraphBody>
|
|
153
|
+
<div class="player" class:anim={animated} class:playing>
|
|
154
|
+
<div class="viewport">
|
|
155
|
+
<pre class="art"><code>{#each art 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 < art.length - 1}{'\n'}{/if}{/each}</code></pre>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
{#if showControls}
|
|
159
|
+
<div class="controls">
|
|
160
|
+
<div class="buttons">
|
|
161
|
+
<button class="ctrl" type="button" onclick={handlePrev} aria-label="Previous step">◀</button>
|
|
162
|
+
<button class="ctrl primary" type="button" onclick={handleTogglePlay}>
|
|
163
|
+
{playing ? '❚❚ pause' : '▶ play'}
|
|
164
|
+
</button>
|
|
165
|
+
<button class="ctrl" type="button" onclick={handleNext} aria-label="Next step">▶</button>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div class="dots">
|
|
169
|
+
{#each steps as _step, i (i)}
|
|
170
|
+
<button
|
|
171
|
+
class="dot"
|
|
172
|
+
class:on={active === i}
|
|
173
|
+
type="button"
|
|
174
|
+
onclick={() => {
|
|
175
|
+
step = i;
|
|
176
|
+
playing = false;
|
|
177
|
+
}}
|
|
178
|
+
aria-label="Step {i + 1}"
|
|
179
|
+
></button>
|
|
180
|
+
{/each}
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<label class="speed" for="{uid}-speed">
|
|
184
|
+
Speed {(intervalTime / 1000).toFixed(1)}s
|
|
185
|
+
<input id="{uid}-speed" type="range" min="400" max="2500" step="100" bind:value={intervalTime} />
|
|
186
|
+
</label>
|
|
187
|
+
</div>
|
|
188
|
+
{/if}
|
|
189
|
+
</div>
|
|
190
|
+
</GraphBody>
|
|
191
|
+
</Graph>
|
|
192
|
+
|
|
193
|
+
<style>
|
|
194
|
+
.player {
|
|
195
|
+
display: flex;
|
|
196
|
+
flex-direction: column;
|
|
197
|
+
gap: 1rem;
|
|
198
|
+
min-width: 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.viewport {
|
|
202
|
+
overflow-x: auto;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.art {
|
|
206
|
+
margin: 0;
|
|
207
|
+
font-size: 0.85rem;
|
|
208
|
+
line-height: 1.35;
|
|
209
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
210
|
+
white-space: pre;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.marker {
|
|
214
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.marker.active {
|
|
218
|
+
color: var(--graph-foreground, oklch(0.93 0 0));
|
|
219
|
+
font-weight: 600;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.marker.done {
|
|
223
|
+
color: var(--graph-accent, oklch(0.78 0.17 155));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.label {
|
|
227
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.label.active {
|
|
231
|
+
color: var(--graph-foreground, oklch(0.93 0 0));
|
|
232
|
+
font-weight: 600;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.label.done {
|
|
236
|
+
color: var(--graph-accent, oklch(0.78 0.17 155));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.detail {
|
|
240
|
+
color: var(--graph-faint, oklch(0.3 0 0));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.spine {
|
|
244
|
+
color: var(--graph-faint, oklch(0.3 0 0));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.spine.done {
|
|
248
|
+
color: var(--graph-accent, oklch(0.78 0.17 155));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
252
|
+
.anim .marker,
|
|
253
|
+
.anim .label,
|
|
254
|
+
.anim .detail,
|
|
255
|
+
.anim .spine {
|
|
256
|
+
transition: color 0.25s ease;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.anim.playing .marker.active {
|
|
260
|
+
animation: pulse 1s infinite alternate;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@keyframes pulse {
|
|
264
|
+
from {
|
|
265
|
+
opacity: 0.7;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
to {
|
|
269
|
+
opacity: 1;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.controls {
|
|
275
|
+
display: flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
justify-content: space-between;
|
|
278
|
+
flex-wrap: wrap;
|
|
279
|
+
gap: 1rem;
|
|
280
|
+
padding-top: 0.875rem;
|
|
281
|
+
border-top: 1px dashed var(--graph-frame, oklch(0.6 0 0 / 0.5));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.buttons {
|
|
285
|
+
display: flex;
|
|
286
|
+
align-items: center;
|
|
287
|
+
gap: 0.35rem;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.ctrl {
|
|
291
|
+
padding: 0.25rem 0.6rem;
|
|
292
|
+
font-size: 0.75rem;
|
|
293
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
294
|
+
background: none;
|
|
295
|
+
border: 1px dashed var(--graph-frame, oklch(0.6 0 0 / 0.5));
|
|
296
|
+
border-radius: 0;
|
|
297
|
+
cursor: pointer;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.ctrl:hover {
|
|
301
|
+
color: var(--graph-foreground, oklch(0.93 0 0));
|
|
302
|
+
border-color: var(--graph-muted, oklch(0.62 0 0));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.primary {
|
|
306
|
+
color: var(--graph-accent, oklch(0.78 0.17 155));
|
|
307
|
+
border-color: var(--graph-accent, oklch(0.78 0.17 155));
|
|
308
|
+
font-weight: 600;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.dots {
|
|
312
|
+
display: flex;
|
|
313
|
+
align-items: center;
|
|
314
|
+
gap: 0.4rem;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.dot {
|
|
318
|
+
width: 8px;
|
|
319
|
+
height: 8px;
|
|
320
|
+
padding: 0;
|
|
321
|
+
background: var(--graph-faint, oklch(0.3 0 0));
|
|
322
|
+
border: none;
|
|
323
|
+
border-radius: 50%;
|
|
324
|
+
cursor: pointer;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.dot.on {
|
|
328
|
+
background: var(--graph-accent, oklch(0.78 0.17 155));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.speed {
|
|
332
|
+
display: flex;
|
|
333
|
+
align-items: center;
|
|
334
|
+
gap: 0.5rem;
|
|
335
|
+
font-size: 0.75rem;
|
|
336
|
+
color: var(--graph-muted, oklch(0.62 0 0));
|
|
337
|
+
white-space: nowrap;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.speed input {
|
|
341
|
+
width: 80px;
|
|
342
|
+
height: 4px;
|
|
343
|
+
accent-color: var(--graph-accent, oklch(0.78 0.17 155));
|
|
344
|
+
cursor: pointer;
|
|
345
|
+
}
|
|
346
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** One ordered step of a process. */
|
|
2
|
+
export interface FlowStep {
|
|
3
|
+
/** Short name shown beside the marker. */
|
|
4
|
+
label: string;
|
|
5
|
+
/** Extra line revealed while the step is active. */
|
|
6
|
+
detail?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface GraphFlowPlayerProps {
|
|
9
|
+
/** Caption drawn on the frame's top edge as `[ TITLE ]`. */
|
|
10
|
+
title: string;
|
|
11
|
+
/** Steps in playback order. */
|
|
12
|
+
steps: FlowStep[];
|
|
13
|
+
/** Start playing on mount. Default false. Never autoplays under reduced motion. */
|
|
14
|
+
autoPlay?: boolean;
|
|
15
|
+
/** Milliseconds per step. Default 1600. */
|
|
16
|
+
speedMs?: number;
|
|
17
|
+
/** Show the playback controls. Default true. */
|
|
18
|
+
showControls?: boolean;
|
|
19
|
+
/** Animate transitions and the active marker pulse. Default true. */
|
|
20
|
+
animated?: boolean;
|
|
21
|
+
/** Character at each corner of the frame. Default '+'. */
|
|
22
|
+
corner?: string;
|
|
23
|
+
class?: string;
|
|
24
|
+
}
|
|
25
|
+
declare const GraphFlowPlayer: import("svelte").Component<GraphFlowPlayerProps, {}, "">;
|
|
26
|
+
type GraphFlowPlayer = ReturnType<typeof GraphFlowPlayer>;
|
|
27
|
+
export default GraphFlowPlayer;
|