silvery 0.3.0 → 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/README.md +41 -145
- package/package.json +64 -12
- package/src/index.ts +67 -1
- package/src/runtime.ts +4 -0
- package/src/theme.ts +4 -0
- package/src/ui/animation.ts +2 -0
- package/src/ui/ansi.ts +2 -0
- package/src/ui/cli.ts +2 -0
- package/src/ui/display.ts +2 -0
- package/src/ui/image.ts +2 -0
- package/src/ui/input.ts +2 -0
- package/src/ui/progress.ts +2 -0
- package/src/ui/react.ts +2 -0
- package/src/ui/utils.ts +2 -0
- package/src/ui/wrappers.ts +2 -0
- package/src/ui.ts +4 -0
- package/examples/CLAUDE.md +0 -75
- package/examples/_banner.tsx +0 -60
- package/examples/cli.ts +0 -228
- package/examples/index.md +0 -101
- package/examples/inline/inline-nontty.tsx +0 -98
- package/examples/inline/inline-progress.tsx +0 -79
- package/examples/inline/inline-simple.tsx +0 -63
- package/examples/inline/scrollback.tsx +0 -185
- package/examples/interactive/_input-debug.tsx +0 -110
- package/examples/interactive/_stdin-test.ts +0 -71
- package/examples/interactive/_textarea-bare.tsx +0 -45
- package/examples/interactive/aichat/components.tsx +0 -468
- package/examples/interactive/aichat/index.tsx +0 -207
- package/examples/interactive/aichat/script.ts +0 -460
- package/examples/interactive/aichat/state.ts +0 -326
- package/examples/interactive/aichat/types.ts +0 -19
- package/examples/interactive/app-todo.tsx +0 -198
- package/examples/interactive/async-data.tsx +0 -208
- package/examples/interactive/cli-wizard.tsx +0 -332
- package/examples/interactive/clipboard.tsx +0 -183
- package/examples/interactive/components.tsx +0 -463
- package/examples/interactive/data-explorer.tsx +0 -506
- package/examples/interactive/dev-tools.tsx +0 -379
- package/examples/interactive/explorer.tsx +0 -747
- package/examples/interactive/gallery.tsx +0 -652
- package/examples/interactive/inline-bench.tsx +0 -136
- package/examples/interactive/kanban.tsx +0 -267
- package/examples/interactive/layout-ref.tsx +0 -185
- package/examples/interactive/outline.tsx +0 -171
- package/examples/interactive/paste-demo.tsx +0 -198
- package/examples/interactive/scroll.tsx +0 -77
- package/examples/interactive/search-filter.tsx +0 -240
- package/examples/interactive/task-list.tsx +0 -279
- package/examples/interactive/terminal.tsx +0 -798
- package/examples/interactive/textarea.tsx +0 -103
- package/examples/interactive/theme.tsx +0 -336
- package/examples/interactive/transform.tsx +0 -256
- package/examples/interactive/virtual-10k.tsx +0 -413
- package/examples/kitty/canvas.tsx +0 -519
- package/examples/kitty/generate-samples.ts +0 -236
- package/examples/kitty/image-component.tsx +0 -273
- package/examples/kitty/images.tsx +0 -604
- package/examples/kitty/input.tsx +0 -371
- package/examples/kitty/keys.tsx +0 -378
- package/examples/kitty/paint.tsx +0 -1017
- package/examples/layout/dashboard.tsx +0 -551
- package/examples/layout/live-resize.tsx +0 -290
- package/examples/layout/overflow.tsx +0 -51
- package/examples/playground/README.md +0 -69
- package/examples/playground/build.ts +0 -61
- package/examples/playground/index.html +0 -420
- package/examples/playground/playground-app.tsx +0 -416
- package/examples/runtime/elm-counter.tsx +0 -206
- package/examples/runtime/hello-runtime.tsx +0 -73
- package/examples/runtime/pipe-composition.tsx +0 -184
- package/examples/runtime/run-counter.tsx +0 -78
- package/examples/runtime/runtime-counter.tsx +0 -197
- package/examples/screenshots/generate.tsx +0 -563
- package/examples/scrollback-perf.tsx +0 -230
- package/examples/viewer.tsx +0 -654
- package/examples/web/build.ts +0 -365
- package/examples/web/canvas-app.tsx +0 -80
- package/examples/web/canvas.html +0 -89
- package/examples/web/dom-app.tsx +0 -81
- package/examples/web/dom.html +0 -113
- package/examples/web/showcase-app.tsx +0 -107
- package/examples/web/showcase.html +0 -34
- package/examples/web/showcases/index.tsx +0 -56
- package/examples/web/viewer-app.tsx +0 -555
- package/examples/web/viewer.html +0 -30
- package/examples/web/xterm-app.tsx +0 -105
- package/examples/web/xterm.html +0 -118
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canvas Playground App
|
|
3
|
-
*
|
|
4
|
-
* Interactive demo showcasing silvery's Canvas adapter with multiple preset examples,
|
|
5
|
-
* live resize, and theme controls. Communicates with the host page via window messages.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React, { useState, useEffect, useCallback } from "react"
|
|
9
|
-
import { renderToCanvas, Box, Text, useContentRect } from "../../src/canvas/index.js"
|
|
10
|
-
|
|
11
|
-
// ============================================================================
|
|
12
|
-
// Shared components
|
|
13
|
-
// ============================================================================
|
|
14
|
-
|
|
15
|
-
function SizeDisplay() {
|
|
16
|
-
const { width, height } = useContentRect()
|
|
17
|
-
return (
|
|
18
|
-
<Text color="$success">
|
|
19
|
-
{Math.round(width)}px x {Math.round(height)}px
|
|
20
|
-
</Text>
|
|
21
|
-
)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function Divider({ color = "$muted" }: { color?: string }) {
|
|
25
|
-
const { width } = useContentRect()
|
|
26
|
-
const line = "─".repeat(Math.max(1, Math.floor(width / 8)))
|
|
27
|
-
return <Text color={color}>{line}</Text>
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// ============================================================================
|
|
31
|
-
// Preset: Hello World
|
|
32
|
-
// ============================================================================
|
|
33
|
-
|
|
34
|
-
function HelloWorld() {
|
|
35
|
-
return (
|
|
36
|
-
<Box flexDirection="column" padding={1}>
|
|
37
|
-
<Box borderStyle="single" borderColor="$info" padding={1}>
|
|
38
|
-
<Box flexDirection="column">
|
|
39
|
-
<Text bold color="$info">
|
|
40
|
-
Hello from silvery!
|
|
41
|
-
</Text>
|
|
42
|
-
<Text color="$muted">React components rendered to HTML5 Canvas</Text>
|
|
43
|
-
<SizeDisplay />
|
|
44
|
-
</Box>
|
|
45
|
-
</Box>
|
|
46
|
-
</Box>
|
|
47
|
-
)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ============================================================================
|
|
51
|
-
// Preset: Text Styles
|
|
52
|
-
// ============================================================================
|
|
53
|
-
|
|
54
|
-
function TextStyles() {
|
|
55
|
-
return (
|
|
56
|
-
<Box flexDirection="column" padding={1}>
|
|
57
|
-
<Text bold color="$warning">
|
|
58
|
-
Text Styles
|
|
59
|
-
</Text>
|
|
60
|
-
<Divider />
|
|
61
|
-
<Box flexDirection="column" marginTop={1}>
|
|
62
|
-
<Box flexDirection="row" gap={2}>
|
|
63
|
-
<Text>Normal</Text>
|
|
64
|
-
<Text bold>Bold</Text>
|
|
65
|
-
<Text italic>Italic</Text>
|
|
66
|
-
<Text bold italic>
|
|
67
|
-
Bold Italic
|
|
68
|
-
</Text>
|
|
69
|
-
</Box>
|
|
70
|
-
<Box flexDirection="row" gap={2} marginTop={1}>
|
|
71
|
-
<Text underline>Underline</Text>
|
|
72
|
-
<Text strikethrough>Strikethrough</Text>
|
|
73
|
-
<Text dim>Dim</Text>
|
|
74
|
-
</Box>
|
|
75
|
-
<Box flexDirection="row" gap={2} marginTop={1}>
|
|
76
|
-
<Text underlineStyle="double" underline>
|
|
77
|
-
Double
|
|
78
|
-
</Text>
|
|
79
|
-
<Text underlineStyle="curly" underlineColor="$error" underline>
|
|
80
|
-
Curly Red
|
|
81
|
-
</Text>
|
|
82
|
-
<Text underlineStyle="dotted" underline>
|
|
83
|
-
Dotted
|
|
84
|
-
</Text>
|
|
85
|
-
<Text underlineStyle="dashed" underline>
|
|
86
|
-
Dashed
|
|
87
|
-
</Text>
|
|
88
|
-
</Box>
|
|
89
|
-
</Box>
|
|
90
|
-
</Box>
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// ============================================================================
|
|
95
|
-
// Preset: Colors & Backgrounds
|
|
96
|
-
// ============================================================================
|
|
97
|
-
|
|
98
|
-
function ColorsAndBackgrounds() {
|
|
99
|
-
const colors = [
|
|
100
|
-
{ bg: "red", fg: "white", label: "Red" },
|
|
101
|
-
{ bg: "green", fg: "black", label: "Green" },
|
|
102
|
-
{ bg: "blue", fg: "white", label: "Blue" },
|
|
103
|
-
{ bg: "yellow", fg: "black", label: "Yellow" },
|
|
104
|
-
{ bg: "magenta", fg: "white", label: "Magenta" },
|
|
105
|
-
{ bg: "cyan", fg: "black", label: "Cyan" },
|
|
106
|
-
]
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<Box flexDirection="column" padding={1}>
|
|
110
|
-
<Text bold color="$warning">
|
|
111
|
-
Colors and Backgrounds
|
|
112
|
-
</Text>
|
|
113
|
-
<Divider />
|
|
114
|
-
<Box flexDirection="row" gap={1} marginTop={1}>
|
|
115
|
-
{colors.map(({ bg, fg, label }) => (
|
|
116
|
-
<Box key={bg} backgroundColor={bg} padding={1}>
|
|
117
|
-
<Text color={fg}>{label}</Text>
|
|
118
|
-
</Box>
|
|
119
|
-
))}
|
|
120
|
-
</Box>
|
|
121
|
-
<Box flexDirection="column" marginTop={1}>
|
|
122
|
-
<Text color="red">Red text</Text>
|
|
123
|
-
<Text color="green">Green text</Text>
|
|
124
|
-
<Text color="blue">Blue text</Text>
|
|
125
|
-
<Text color="brightYellow">Bright yellow text</Text>
|
|
126
|
-
<Text color="brightCyan">Bright cyan text</Text>
|
|
127
|
-
<Text color="#ff6b6b">Custom hex #ff6b6b</Text>
|
|
128
|
-
<Text color="rgb(147, 130, 220)">Custom RGB</Text>
|
|
129
|
-
</Box>
|
|
130
|
-
</Box>
|
|
131
|
-
)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ============================================================================
|
|
135
|
-
// Preset: Flexbox Layout
|
|
136
|
-
// ============================================================================
|
|
137
|
-
|
|
138
|
-
function FlexboxLayout() {
|
|
139
|
-
return (
|
|
140
|
-
<Box flexDirection="column" padding={1}>
|
|
141
|
-
<Text bold color="$warning">
|
|
142
|
-
Flexbox Layout
|
|
143
|
-
</Text>
|
|
144
|
-
<Divider />
|
|
145
|
-
|
|
146
|
-
<Text color="$muted" marginTop={1}>
|
|
147
|
-
Row (gap=1):
|
|
148
|
-
</Text>
|
|
149
|
-
<Box flexDirection="row" gap={1}>
|
|
150
|
-
<Box borderStyle="single" borderColor="$error" padding={1} flexGrow={1}>
|
|
151
|
-
<Text color="$error">Col 1</Text>
|
|
152
|
-
</Box>
|
|
153
|
-
<Box borderStyle="single" borderColor="$success" padding={1} flexGrow={2}>
|
|
154
|
-
<Text color="$success">Col 2 (grow=2)</Text>
|
|
155
|
-
</Box>
|
|
156
|
-
<Box borderStyle="single" borderColor="$primary" padding={1} flexGrow={1}>
|
|
157
|
-
<Text color="$primary">Col 3</Text>
|
|
158
|
-
</Box>
|
|
159
|
-
</Box>
|
|
160
|
-
|
|
161
|
-
<Text color="$muted" marginTop={1}>
|
|
162
|
-
Nested columns:
|
|
163
|
-
</Text>
|
|
164
|
-
<Box flexDirection="row" gap={1}>
|
|
165
|
-
<Box borderStyle="round" borderColor="$accent" padding={1} flexGrow={1} flexDirection="column">
|
|
166
|
-
<Text bold color="$accent">
|
|
167
|
-
Panel A
|
|
168
|
-
</Text>
|
|
169
|
-
<Text>Item 1</Text>
|
|
170
|
-
<Text>Item 2</Text>
|
|
171
|
-
<Text>Item 3</Text>
|
|
172
|
-
</Box>
|
|
173
|
-
<Box borderStyle="round" borderColor="$info" padding={1} flexGrow={1} flexDirection="column">
|
|
174
|
-
<Text bold color="$info">
|
|
175
|
-
Panel B
|
|
176
|
-
</Text>
|
|
177
|
-
<Text>Item A</Text>
|
|
178
|
-
<Text>Item B</Text>
|
|
179
|
-
</Box>
|
|
180
|
-
</Box>
|
|
181
|
-
</Box>
|
|
182
|
-
)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ============================================================================
|
|
186
|
-
// Preset: Border Styles
|
|
187
|
-
// ============================================================================
|
|
188
|
-
|
|
189
|
-
function BorderStyles() {
|
|
190
|
-
const styles: Array<{ style: string; color: string }> = [
|
|
191
|
-
{ style: "single", color: "$info" },
|
|
192
|
-
{ style: "double", color: "$warning" },
|
|
193
|
-
{ style: "round", color: "$success" },
|
|
194
|
-
{ style: "bold", color: "$accent" },
|
|
195
|
-
]
|
|
196
|
-
|
|
197
|
-
return (
|
|
198
|
-
<Box flexDirection="column" padding={1}>
|
|
199
|
-
<Text bold color="$warning">
|
|
200
|
-
Border Styles
|
|
201
|
-
</Text>
|
|
202
|
-
<Divider />
|
|
203
|
-
<Box flexDirection="row" gap={1} marginTop={1}>
|
|
204
|
-
{styles.map(({ style, color }) => (
|
|
205
|
-
<Box key={style} borderStyle={style as any} borderColor={color} padding={1} flexGrow={1}>
|
|
206
|
-
<Box flexDirection="column">
|
|
207
|
-
<Text bold color={color}>
|
|
208
|
-
{style}
|
|
209
|
-
</Text>
|
|
210
|
-
<Text>border</Text>
|
|
211
|
-
</Box>
|
|
212
|
-
</Box>
|
|
213
|
-
))}
|
|
214
|
-
</Box>
|
|
215
|
-
</Box>
|
|
216
|
-
)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// ============================================================================
|
|
220
|
-
// Preset: Dashboard
|
|
221
|
-
// ============================================================================
|
|
222
|
-
|
|
223
|
-
function Dashboard() {
|
|
224
|
-
return (
|
|
225
|
-
<Box flexDirection="column" padding={1}>
|
|
226
|
-
<Box borderStyle="single" borderColor="$info" padding={1}>
|
|
227
|
-
<Text bold color="$info">
|
|
228
|
-
System Dashboard
|
|
229
|
-
</Text>
|
|
230
|
-
</Box>
|
|
231
|
-
<Box flexDirection="row" gap={1} marginTop={1}>
|
|
232
|
-
<Box borderStyle="round" borderColor="$success" padding={1} flexGrow={1} flexDirection="column">
|
|
233
|
-
<Text bold color="$success">
|
|
234
|
-
CPU
|
|
235
|
-
</Text>
|
|
236
|
-
<Text color="$success">|||||||....</Text>
|
|
237
|
-
<Text>65%</Text>
|
|
238
|
-
</Box>
|
|
239
|
-
<Box borderStyle="round" borderColor="$warning" padding={1} flexGrow={1} flexDirection="column">
|
|
240
|
-
<Text bold color="$warning">
|
|
241
|
-
Memory
|
|
242
|
-
</Text>
|
|
243
|
-
<Text color="$warning">|||||||||..</Text>
|
|
244
|
-
<Text>82%</Text>
|
|
245
|
-
</Box>
|
|
246
|
-
<Box borderStyle="round" borderColor="$error" padding={1} flexGrow={1} flexDirection="column">
|
|
247
|
-
<Text bold color="$error">
|
|
248
|
-
Disk
|
|
249
|
-
</Text>
|
|
250
|
-
<Text color="$error">||||||||||.</Text>
|
|
251
|
-
<Text>91%</Text>
|
|
252
|
-
</Box>
|
|
253
|
-
</Box>
|
|
254
|
-
<Box borderStyle="single" borderColor="$muted" padding={1} marginTop={1} flexDirection="column">
|
|
255
|
-
<Text bold>Recent Events</Text>
|
|
256
|
-
<Text color="$success"> OK api-server healthy</Text>
|
|
257
|
-
<Text color="$success"> OK database connected</Text>
|
|
258
|
-
<Text color="$warning"> WARN cache miss rate high</Text>
|
|
259
|
-
<Text color="$error"> ERR disk space low on /var</Text>
|
|
260
|
-
</Box>
|
|
261
|
-
</Box>
|
|
262
|
-
)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// ============================================================================
|
|
266
|
-
// Preset: Responsive
|
|
267
|
-
// ============================================================================
|
|
268
|
-
|
|
269
|
-
function Responsive() {
|
|
270
|
-
const { width } = useContentRect()
|
|
271
|
-
const isWide = width > 350
|
|
272
|
-
|
|
273
|
-
return (
|
|
274
|
-
<Box flexDirection="column" padding={1}>
|
|
275
|
-
<Text bold color="$warning">
|
|
276
|
-
Responsive Layout
|
|
277
|
-
</Text>
|
|
278
|
-
<Text color="$muted">Resize the canvas to see layout adapt ({Math.round(width)}px wide)</Text>
|
|
279
|
-
<Divider />
|
|
280
|
-
<Box flexDirection={isWide ? "row" : "column"} gap={1} marginTop={1}>
|
|
281
|
-
<Box borderStyle="single" borderColor="$info" padding={1} flexGrow={1} flexDirection="column">
|
|
282
|
-
<Text bold color="$info">
|
|
283
|
-
Main Content
|
|
284
|
-
</Text>
|
|
285
|
-
<Text>This panel takes available space.</Text>
|
|
286
|
-
<Text>Layout: {isWide ? "horizontal" : "vertical"}</Text>
|
|
287
|
-
<SizeDisplay />
|
|
288
|
-
</Box>
|
|
289
|
-
<Box
|
|
290
|
-
borderStyle="single"
|
|
291
|
-
borderColor="$accent"
|
|
292
|
-
padding={1}
|
|
293
|
-
flexDirection="column"
|
|
294
|
-
{...(isWide ? { width: 180 } : {})}
|
|
295
|
-
>
|
|
296
|
-
<Text bold color="$accent">
|
|
297
|
-
Sidebar
|
|
298
|
-
</Text>
|
|
299
|
-
<Text>Fixed width when wide,</Text>
|
|
300
|
-
<Text>full width when narrow.</Text>
|
|
301
|
-
</Box>
|
|
302
|
-
</Box>
|
|
303
|
-
</Box>
|
|
304
|
-
)
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// ============================================================================
|
|
308
|
-
// Presets Registry
|
|
309
|
-
// ============================================================================
|
|
310
|
-
|
|
311
|
-
const PRESETS: Record<string, { label: string; component: React.FC }> = {
|
|
312
|
-
hello: { label: "Hello World", component: HelloWorld },
|
|
313
|
-
text: { label: "Text Styles", component: TextStyles },
|
|
314
|
-
colors: { label: "Colors", component: ColorsAndBackgrounds },
|
|
315
|
-
flexbox: { label: "Flexbox", component: FlexboxLayout },
|
|
316
|
-
borders: { label: "Borders", component: BorderStyles },
|
|
317
|
-
dashboard: { label: "Dashboard", component: Dashboard },
|
|
318
|
-
responsive: { label: "Responsive", component: Responsive },
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// ============================================================================
|
|
322
|
-
// Root App
|
|
323
|
-
// ============================================================================
|
|
324
|
-
|
|
325
|
-
function App({ preset: initialPreset }: { preset: string }) {
|
|
326
|
-
const [currentPreset, setPreset] = useState(initialPreset)
|
|
327
|
-
|
|
328
|
-
useEffect(() => {
|
|
329
|
-
// Listen for preset changes from host page
|
|
330
|
-
function handleMessage(e: MessageEvent) {
|
|
331
|
-
if (e.data?.type === "set-preset" && PRESETS[e.data.preset]) {
|
|
332
|
-
setPreset(e.data.preset)
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
window.addEventListener("message", handleMessage)
|
|
336
|
-
return () => window.removeEventListener("message", handleMessage)
|
|
337
|
-
}, [])
|
|
338
|
-
|
|
339
|
-
const Component = PRESETS[currentPreset]?.component ?? HelloWorld
|
|
340
|
-
return <Component />
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// ============================================================================
|
|
344
|
-
// Mount
|
|
345
|
-
// ============================================================================
|
|
346
|
-
|
|
347
|
-
const canvas = document.getElementById("canvas") as HTMLCanvasElement
|
|
348
|
-
if (canvas) {
|
|
349
|
-
// Read initial preset from URL hash
|
|
350
|
-
const hash = window.location.hash.slice(1)
|
|
351
|
-
const initialPreset = PRESETS[hash] ? hash : "hello"
|
|
352
|
-
|
|
353
|
-
let instance = renderToCanvas(<App preset={initialPreset} />, canvas, {
|
|
354
|
-
fontSize: 14,
|
|
355
|
-
fontFamily: "monospace",
|
|
356
|
-
})
|
|
357
|
-
|
|
358
|
-
// Handle resize
|
|
359
|
-
function handleResize() {
|
|
360
|
-
const container = canvas.parentElement
|
|
361
|
-
if (!container) return
|
|
362
|
-
const rect = container.getBoundingClientRect()
|
|
363
|
-
const newWidth = Math.floor(rect.width)
|
|
364
|
-
const newHeight = Math.max(300, Math.floor(rect.height))
|
|
365
|
-
if (canvas.width !== newWidth || canvas.height !== newHeight) {
|
|
366
|
-
canvas.width = newWidth
|
|
367
|
-
canvas.height = newHeight
|
|
368
|
-
// Re-render with new dimensions
|
|
369
|
-
instance.unmount()
|
|
370
|
-
const hash = window.location.hash.slice(1)
|
|
371
|
-
const preset = PRESETS[hash] ? hash : "hello"
|
|
372
|
-
instance = renderToCanvas(<App preset={preset} />, canvas, {
|
|
373
|
-
fontSize: 14,
|
|
374
|
-
fontFamily: "monospace",
|
|
375
|
-
width: newWidth,
|
|
376
|
-
height: newHeight,
|
|
377
|
-
})
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Listen for preset changes and re-render
|
|
382
|
-
window.addEventListener("message", (e) => {
|
|
383
|
-
if (e.data?.type === "set-preset" && PRESETS[e.data.preset]) {
|
|
384
|
-
window.location.hash = e.data.preset
|
|
385
|
-
instance.unmount()
|
|
386
|
-
const container = canvas.parentElement
|
|
387
|
-
const w = container ? Math.floor(container.getBoundingClientRect().width) : canvas.width
|
|
388
|
-
const h = container ? Math.max(300, Math.floor(container.getBoundingClientRect().height)) : canvas.height
|
|
389
|
-
canvas.width = w
|
|
390
|
-
canvas.height = h
|
|
391
|
-
instance = renderToCanvas(<App preset={e.data.preset} />, canvas, {
|
|
392
|
-
fontSize: 14,
|
|
393
|
-
fontFamily: "monospace",
|
|
394
|
-
width: w,
|
|
395
|
-
height: h,
|
|
396
|
-
})
|
|
397
|
-
}
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
const resizeObserver = new ResizeObserver(handleResize)
|
|
401
|
-
const container = canvas.parentElement
|
|
402
|
-
if (container) resizeObserver.observe(container)
|
|
403
|
-
|
|
404
|
-
// Initial size
|
|
405
|
-
handleResize()
|
|
406
|
-
|
|
407
|
-
// Expose for debugging
|
|
408
|
-
;(window as any).silveryInstance = instance
|
|
409
|
-
;(window as any).PRESETS = Object.keys(PRESETS)
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// Export presets list for the host page
|
|
413
|
-
;(window as any).PRESET_LIST = Object.entries(PRESETS).map(([id, { label }]) => ({
|
|
414
|
-
id,
|
|
415
|
-
label,
|
|
416
|
-
}))
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Elm Counter - Layer 1 Example
|
|
3
|
-
*
|
|
4
|
-
* Demonstrates the Elm-style architecture:
|
|
5
|
-
* - User drives the event loop
|
|
6
|
-
* - Pure reducer for state updates
|
|
7
|
-
* - Pure view function for rendering
|
|
8
|
-
*
|
|
9
|
-
* Usage: bun examples/mode3-counter.tsx
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import React from "react"
|
|
13
|
-
import { Box, Text, H3, Muted } from "../../src/index.js"
|
|
14
|
-
import {
|
|
15
|
-
layout,
|
|
16
|
-
diff,
|
|
17
|
-
ensureLayoutEngine,
|
|
18
|
-
createTick,
|
|
19
|
-
merge,
|
|
20
|
-
map,
|
|
21
|
-
takeUntil,
|
|
22
|
-
type Buffer,
|
|
23
|
-
type Dims,
|
|
24
|
-
} from "@silvery/term/runtime"
|
|
25
|
-
import type { ExampleMeta } from "../_banner.js"
|
|
26
|
-
|
|
27
|
-
export const meta: ExampleMeta = {
|
|
28
|
-
name: "Elm Counter",
|
|
29
|
-
description: "Pure functional Elm-style: reducer + view, no hooks",
|
|
30
|
-
features: ["createRuntime()", "Elm architecture", "layout() + diff()"],
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// ============================================================================
|
|
34
|
-
// State
|
|
35
|
-
// ============================================================================
|
|
36
|
-
|
|
37
|
-
interface State {
|
|
38
|
-
count: number
|
|
39
|
-
running: boolean
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ============================================================================
|
|
43
|
-
// Events
|
|
44
|
-
// ============================================================================
|
|
45
|
-
|
|
46
|
-
type Event = { type: "tick" } | { type: "key"; key: string } | { type: "quit" }
|
|
47
|
-
|
|
48
|
-
// ============================================================================
|
|
49
|
-
// Reducer (pure function)
|
|
50
|
-
// ============================================================================
|
|
51
|
-
|
|
52
|
-
function reducer(state: State, event: Event): State {
|
|
53
|
-
switch (event.type) {
|
|
54
|
-
case "tick":
|
|
55
|
-
return { ...state, count: state.count + 1 }
|
|
56
|
-
case "key":
|
|
57
|
-
if (event.key === "q" || event.key === "\x03") {
|
|
58
|
-
// q or ctrl+c
|
|
59
|
-
return { ...state, running: false }
|
|
60
|
-
}
|
|
61
|
-
if (event.key === "r") {
|
|
62
|
-
return { ...state, count: 0 }
|
|
63
|
-
}
|
|
64
|
-
return state
|
|
65
|
-
case "quit":
|
|
66
|
-
return { ...state, running: false }
|
|
67
|
-
default:
|
|
68
|
-
return state
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ============================================================================
|
|
73
|
-
// View (pure function)
|
|
74
|
-
// ============================================================================
|
|
75
|
-
|
|
76
|
-
function view(state: State): React.ReactElement {
|
|
77
|
-
return (
|
|
78
|
-
<Box flexDirection="column" padding={1}>
|
|
79
|
-
<H3>Elm Counter Example</H3>
|
|
80
|
-
<Text> </Text>
|
|
81
|
-
<Text>
|
|
82
|
-
Count: <Text color="green">{state.count}</Text>
|
|
83
|
-
</Text>
|
|
84
|
-
<Text> </Text>
|
|
85
|
-
<Muted>Press 'r' to reset, 'q' to quit</Muted>
|
|
86
|
-
</Box>
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ============================================================================
|
|
91
|
-
// Event Sources
|
|
92
|
-
// ============================================================================
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Create a keyboard event source from stdin.
|
|
96
|
-
*/
|
|
97
|
-
function createKeyboardSource(signal: AbortSignal): AsyncIterable<Event> {
|
|
98
|
-
return {
|
|
99
|
-
async *[Symbol.asyncIterator]() {
|
|
100
|
-
const stdin = process.stdin
|
|
101
|
-
stdin.setRawMode(true)
|
|
102
|
-
stdin.resume()
|
|
103
|
-
stdin.setEncoding("utf8")
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
while (!signal.aborted) {
|
|
107
|
-
const key = await new Promise<string | null>((resolve) => {
|
|
108
|
-
const onData = (data: string) => {
|
|
109
|
-
stdin.off("data", onData)
|
|
110
|
-
resolve(data)
|
|
111
|
-
}
|
|
112
|
-
const onAbort = () => {
|
|
113
|
-
stdin.off("data", onData)
|
|
114
|
-
resolve(null)
|
|
115
|
-
}
|
|
116
|
-
stdin.on("data", onData)
|
|
117
|
-
signal.addEventListener("abort", onAbort, { once: true })
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
if (key === null || signal.aborted) break
|
|
121
|
-
yield { type: "key" as const, key }
|
|
122
|
-
}
|
|
123
|
-
} finally {
|
|
124
|
-
stdin.setRawMode(false)
|
|
125
|
-
stdin.pause()
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ============================================================================
|
|
132
|
-
// Main Loop
|
|
133
|
-
// ============================================================================
|
|
134
|
-
|
|
135
|
-
async function main() {
|
|
136
|
-
// Initialize layout engine
|
|
137
|
-
await ensureLayoutEngine()
|
|
138
|
-
|
|
139
|
-
// Get terminal dimensions
|
|
140
|
-
const dims: Dims = {
|
|
141
|
-
cols: process.stdout.columns || 80,
|
|
142
|
-
rows: process.stdout.rows || 24,
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Initial state
|
|
146
|
-
let state: State = { count: 0, running: true }
|
|
147
|
-
|
|
148
|
-
// Abort controller for cleanup
|
|
149
|
-
const controller = new AbortController()
|
|
150
|
-
|
|
151
|
-
// Create event sources
|
|
152
|
-
const ticks = map(createTick(100, controller.signal), () => ({
|
|
153
|
-
type: "tick" as const,
|
|
154
|
-
}))
|
|
155
|
-
const keys = createKeyboardSource(controller.signal)
|
|
156
|
-
|
|
157
|
-
// Merge all event sources
|
|
158
|
-
const events = takeUntil(merge(ticks, keys), controller.signal)
|
|
159
|
-
|
|
160
|
-
// Previous buffer for diffing
|
|
161
|
-
let prevBuffer: Buffer | null = null
|
|
162
|
-
|
|
163
|
-
// Clear screen and hide cursor
|
|
164
|
-
process.stdout.write("\x1b[2J\x1b[H\x1b[?25l")
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
// Initial render
|
|
168
|
-
const buffer = layout(view(state), dims)
|
|
169
|
-
const output = diff(null, buffer)
|
|
170
|
-
process.stdout.write(output)
|
|
171
|
-
prevBuffer = buffer
|
|
172
|
-
|
|
173
|
-
// Event loop
|
|
174
|
-
for await (const event of events) {
|
|
175
|
-
// Update state
|
|
176
|
-
const newState = reducer(state, event)
|
|
177
|
-
|
|
178
|
-
// Check if we should exit
|
|
179
|
-
if (!newState.running) {
|
|
180
|
-
break
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Only re-render if state changed
|
|
184
|
-
if (newState !== state) {
|
|
185
|
-
state = newState
|
|
186
|
-
|
|
187
|
-
// Render
|
|
188
|
-
const buffer = layout(view(state), dims)
|
|
189
|
-
const output = diff(prevBuffer, buffer)
|
|
190
|
-
process.stdout.write(output)
|
|
191
|
-
prevBuffer = buffer
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
} finally {
|
|
195
|
-
// Cleanup
|
|
196
|
-
controller.abort()
|
|
197
|
-
|
|
198
|
-
// Show cursor and reset
|
|
199
|
-
process.stdout.write("\x1b[?25h\x1b[0m\n")
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Run
|
|
204
|
-
if (import.meta.main) {
|
|
205
|
-
main().catch(console.error)
|
|
206
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hello Runtime - Minimal createRuntime Example
|
|
3
|
-
*
|
|
4
|
-
* The simplest possible example using the silvery-loop runtime.
|
|
5
|
-
* Shows basic setup, rendering, and cleanup.
|
|
6
|
-
*
|
|
7
|
-
* Usage: bun examples/hello-runtime.tsx
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import React from "react"
|
|
11
|
-
import { Box, Text, H1, Muted } from "../../src/index.js"
|
|
12
|
-
import { createRuntime, ensureLayoutEngine, layout, type Dims, type RenderTarget } from "@silvery/term/runtime"
|
|
13
|
-
import type { ExampleMeta } from "../_banner.js"
|
|
14
|
-
|
|
15
|
-
export const meta: ExampleMeta = {
|
|
16
|
-
name: "Hello Runtime",
|
|
17
|
-
description: "Simplest Layer 1 API: createRuntime(), layout(), Symbol.dispose",
|
|
18
|
-
features: ["createRuntime()", "layout()", "renderString()"],
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Simple terminal target
|
|
22
|
-
const termTarget: RenderTarget = {
|
|
23
|
-
write: (frame) => process.stdout.write(frame),
|
|
24
|
-
getDims: () => ({
|
|
25
|
-
cols: process.stdout.columns || 80,
|
|
26
|
-
rows: process.stdout.rows || 24,
|
|
27
|
-
}),
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Simple view
|
|
31
|
-
function HelloView({ name }: { name: string }): React.ReactElement {
|
|
32
|
-
return (
|
|
33
|
-
<Box flexDirection="column" padding={1}>
|
|
34
|
-
<H1 color="green">Hello, {name}!</H1>
|
|
35
|
-
<Muted>Welcome to silvery-loop</Muted>
|
|
36
|
-
</Box>
|
|
37
|
-
)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function main() {
|
|
41
|
-
// Initialize layout engine (required once)
|
|
42
|
-
await ensureLayoutEngine()
|
|
43
|
-
|
|
44
|
-
// Create runtime
|
|
45
|
-
const runtime = createRuntime({ target: termTarget })
|
|
46
|
-
|
|
47
|
-
// Render
|
|
48
|
-
const buffer = layout(<HelloView name="World" />, runtime.getDims())
|
|
49
|
-
runtime.render(buffer)
|
|
50
|
-
|
|
51
|
-
// Wait a moment to see the output
|
|
52
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
53
|
-
|
|
54
|
-
// Update with new content
|
|
55
|
-
const buffer2 = layout(<HelloView name="silvery-loop" />, runtime.getDims())
|
|
56
|
-
runtime.render(buffer2)
|
|
57
|
-
|
|
58
|
-
// Wait again
|
|
59
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
60
|
-
|
|
61
|
-
// Cleanup
|
|
62
|
-
runtime[Symbol.dispose]()
|
|
63
|
-
|
|
64
|
-
console.log("\nDone!")
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (import.meta.main) {
|
|
68
|
-
try {
|
|
69
|
-
await main()
|
|
70
|
-
} catch (e) {
|
|
71
|
-
console.error(e)
|
|
72
|
-
}
|
|
73
|
-
}
|