From 53396aa806afb8d81d09a871b35c02c831507110 Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 6 Nov 2024 23:57:15 -0800 Subject: [PATCH] add a prompt for using . add the live graph demo --- canvas-prompt.md | 258 ++++++++++++++++++++++++++++++++++++ graph/graph-main.go | 309 ++++++++++++++++++++++++++++++++++++++++++++ graph/style.css | 23 ++++ prompt.md | 5 +- 4 files changed, 593 insertions(+), 2 deletions(-) create mode 100644 canvas-prompt.md create mode 100644 graph/graph-main.go create mode 100644 graph/style.css diff --git a/canvas-prompt.md b/canvas-prompt.md new file mode 100644 index 0000000..e9363a1 --- /dev/null +++ b/canvas-prompt.md @@ -0,0 +1,258 @@ +# VDOM Canvas Operations Guide + +The VDOM system provides a powerful way to interact with HTML Canvas elements through the `QueueRefOp` function. This guide explains how to use canvas operations effectively in your VDOM applications. + +## Basic Canvas Setup + +First, create a canvas element and get a reference to it: + +```go +canvasRef := vdom.UseVDomRef(ctx) +return vdom.E("canvas", + vdom.P("ref", canvasRef), + vdom.P("width", "300"), + vdom.P("height", "300"), + vdom.PStyle("width", 300), + vdom.PStyle("height", 300), +) +``` + +## Queuing Canvas Operations + +Use `QueueRefOp` to send operations to the canvas. Each operation maps directly to a Canvas 2D context method: + +```go +vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "fillStyle", + Params: []any{"#ff0000"}, +}) + +vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "fillRect", + Params: []any{0, 0, 100, 100}, +}) +``` + +## Reference Management + +The VDOM canvas system has two key uses for references: + +1. **Capturing Canvas API Return Values** +Some canvas operations return objects (like gradients) that cannot be directly serialized. Use the `Ref` field to capture these return values: + +```go +// Create and capture a gradient +vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "createLinearGradient", + Params: []any{0, 0, 200, 0}, + Ref: "myGradient", // Captures the returned gradient object +}) + +// Use the gradient in another operation +vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "fillStyle", + Params: []any{"#ref:myGradient"}, // Reference the captured gradient +}) +``` + +2. **Storing Complex Data** +You can also store and reuse any JSON-compatible data: + +```go +// Store configuration data +vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "addRef", + Ref: "pointsConfig", + Params: []any{[]float64{10, 20, 30, 40, 50, 60}}, +}) + +// Use stored data with spreadRef +vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "moveTo", + Params: []any{"#spreadRef:pointsConfig"}, +}) + +// Clean up when done +vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "dropRef", + Params: []any{"pointsConfig"}, +}) +``` + +Reference operations support: +- `#ref:id` - Use the referenced value directly (crucial for canvas-created objects) +- `#spreadRef:id` - Spread an array reference as multiple parameters + +## Common Canvas Operations + +Here are commonly used canvas operations: + +1. **Basic Drawing** +```go +// Clear canvas +vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "clearRect", + Params: []any{0, 0, width, height}, +}) + +// Set fill color +vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "fillStyle", + Params: []any{"rgba(255, 0, 0, 0.5)"}, +}) + +// Draw rectangle +vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "fillRect", + Params: []any{x, y, width, height}, +}) +``` + +2. **Path Operations** +```go +// Begin a new path +vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "beginPath", + Params: nil, +}) + +// Draw circle +vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "arc", + Params: []any{x, y, radius, 0, 2 * math.Pi}, +}) + +// Fill the path +vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "fill", + Params: nil, +}) +``` + +## Animation and Updates + +For smooth animations, use UseEffect with a render timestamp: + +```go +renderTs := vdom.UseRenderTs(ctx) +lastRenderTs := vdom.UseRef(ctx, int64(0)) + +vdom.UseEffect(ctx, func() func() { + if !canvasRef.HasCurrent { + return nil + } + // Limit frame rate + if renderTs-lastRenderTs.Current < 30 { + return nil + } + lastRenderTs.Current = renderTs + + // Queue drawing operations here + + // Schedule next frame + go func() { + time.Sleep(60 * time.Millisecond) + AppClient.SendAsyncInitiation() + }() + + return nil +}, []any{renderTs}) +``` + +## Best Practices + +1. **Performance** + - Batch related operations together + - Use `clearRect` instead of resizing the canvas + - Limit animation frame rates with render timestamps + - Clean up any animation loops in UseEffect cleanup functions + +2. **Reference Management** + - Clean up references with `dropRef` when no longer needed + - Use `#spreadRef` when passing array data to operations + - Store frequently used complex data as references + +3. **Error Handling** + - Check `canvasRef.HasCurrent` before operations + - Validate coordinates and dimensions + - Use proper cleanup in UseEffect returns + +## Complete Example: Particle System + +```go +type Particle struct { + X, Y float64 + VelocityX float64 + VelocityY float64 + Color string + Size float64 +} + +type CanvasUpdaterProps struct { + CanvasRef *vdom.VDomRef +} + +var CanvasUpdater = vdomclient.DefineComponent[CanvasUpdaterProps]( + Client, "CanvasUpdater", + func(ctx context.Context, props CanvasUpdaterProps) any { + particles, setParticles := vdom.UseState(ctx, initParticles(10)) + lastRenderTs := vdom.UseRef(ctx, int64(0)) + renderTs := vdom.UseRenderTs(ctx) + + vdom.UseEffect(ctx, func() func() { + if !props.CanvasRef.HasCurrent { + return nil + } + if renderTs-lastRenderTs.Current < 30 { + return nil + } + lastRenderTs.Current = renderTs + + // Clear canvas + vdom.QueueRefOp(ctx, props.CanvasRef, vdom.VDomRefOperation{ + Op: "clearRect", + Params: []any{0, 0, 300, 300}, + }) + + // Draw particles + for _, p := range particles { + // Set color + vdom.QueueRefOp(ctx, props.CanvasRef, vdom.VDomRefOperation{ + Op: "fillStyle", + Params: []any{p.Color}, + }) + + // Draw circle + vdom.QueueRefOp(ctx, props.CanvasRef, vdom.VDomRefOperation{ + Op: "beginPath", + Params: nil, + }) + vdom.QueueRefOp(ctx, props.CanvasRef, vdom.VDomRefOperation{ + Op: "arc", + Params: []any{p.X, p.Y, p.Size, 0, 2 * math.Pi}, + }) + vdom.QueueRefOp(ctx, props.CanvasRef, vdom.VDomRefOperation{ + Op: "fill", + Params: nil, + }) + } + + // Update positions for next frame + newParticles := updateParticles(particles) + setParticles(newParticles) + + // Schedule next frame + go func() { + time.Sleep(60 * time.Millisecond) + Client.SendAsyncInitiation() + }() + + return nil + }, []any{renderTs}) + + return nil + }, +) +``` + +This guide covers the essential patterns for working with Canvas in VDOM applications. Remember that while Canvas operations are powerful, they should be used judiciously in terminal-based applications to maintain good performance. \ No newline at end of file diff --git a/graph/graph-main.go b/graph/graph-main.go new file mode 100644 index 0000000..feb5a35 --- /dev/null +++ b/graph/graph-main.go @@ -0,0 +1,309 @@ +package main + +import ( + "bufio" + "context" + _ "embed" + "fmt" + "math" + "os" + "strconv" + "strings" + + "github.com/wavetermdev/waveterm/pkg/vdom" + "github.com/wavetermdev/waveterm/pkg/vdom/vdomclient" +) + +//go:embed style.css +var styleCSS []byte + +var AppClient = vdomclient.MakeClient(vdomclient.AppOpts{ + CloseOnCtrlC: true, + GlobalStyles: styleCSS, +}) + +type DataPoint struct { + Value float64 +} + +const ( + canvasWidth = 800 + canvasHeight = 400 + padding = 40 + pointRadius = 3 // Made slightly smaller +) + +func calculateStats(points []DataPoint) (count int, avg float64) { + count = len(points) + if count == 0 { + return count, 0 + } + + sum := 0.0 + for _, p := range points { + sum += p.Value + } + return count, sum / float64(count) +} + +var App = vdomclient.DefineComponent(AppClient, "App", + func(ctx context.Context, _ any) any { + // State for our data points and update counter + points, _, setPointsFn := vdom.UseStateWithFn(ctx, []DataPoint{}) + updateCount, _, setUpdateCountFn := vdom.UseStateWithFn(ctx, 0) + + // Reference for the canvas + canvasRef := vdom.UseVDomRef(ctx) + + // Reference for managing the data reading goroutine + readerState := vdom.UseRef(ctx, struct { + done chan bool + active bool + }{ + done: make(chan bool), + active: false, + }) + + // Function to draw the graph + drawGraph := func() { + if !canvasRef.HasCurrent || len(points) == 0 { + return + } + + // Clear canvas + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "clearRect", + Params: []any{0, 0, canvasWidth, canvasHeight}, + }) + + // Find max value for scaling + maxVal := points[0].Value + for _, p := range points { + if p.Value > maxVal { + maxVal = p.Value + } + } + maxVal = maxVal * 1.05 // Add 5% buffer + + // Calculate scales + xScale := float64(canvasWidth-2*padding) / math.Max(float64(len(points)-1), 1) + yScale := float64(canvasHeight-2*padding) / maxVal + + // Draw grid (new!) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "strokeStyle", + Params: []any{"#333333"}, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "lineWidth", + Params: []any{1}, + }) + + // Vertical grid lines + for i := 0; i < 10; i++ { + x := padding + (float64(i) * (canvasWidth - 2*padding) / 9) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "beginPath", + Params: nil, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "moveTo", + Params: []any{x, padding}, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "lineTo", + Params: []any{x, canvasHeight - padding}, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "stroke", + Params: nil, + }) + } + + // Horizontal grid lines + for i := 0; i < 10; i++ { + y := padding + (float64(i) * (canvasHeight - 2*padding) / 9) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "beginPath", + Params: nil, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "moveTo", + Params: []any{padding, y}, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "lineTo", + Params: []any{canvasWidth - padding, y}, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "stroke", + Params: nil, + }) + } + + // Draw axes + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "beginPath", + Params: nil, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "strokeStyle", + Params: []any{"#666666"}, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "lineWidth", + Params: []any{2}, + }) + + // Y axis + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "moveTo", + Params: []any{padding, padding}, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "lineTo", + Params: []any{padding, canvasHeight - padding}, + }) + + // X axis + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "moveTo", + Params: []any{padding, canvasHeight - padding}, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "lineTo", + Params: []any{canvasWidth - padding, canvasHeight - padding}, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "stroke", + Params: nil, + }) + + // Draw data line + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "beginPath", + Params: nil, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "strokeStyle", + Params: []any{"#4488ff"}, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "lineWidth", + Params: []any{2}, + }) + + // Draw lines connecting points + for i := 0; i < len(points); i++ { + x := padding + (float64(i) * xScale) + y := canvasHeight - padding - (points[i].Value * yScale) + + if i == 0 { + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "moveTo", + Params: []any{x, y}, + }) + } else { + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "lineTo", + Params: []any{x, y}, + }) + } + } + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "stroke", + Params: nil, + }) + + // Draw points + for i := 0; i < len(points); i++ { + x := padding + (float64(i) * xScale) + y := canvasHeight - padding - (points[i].Value * yScale) + + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "beginPath", + Params: nil, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "fillStyle", + Params: []any{"#4488ff"}, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "arc", + Params: []any{x, y, pointRadius, 0, 2 * math.Pi}, + }) + vdom.QueueRefOp(ctx, canvasRef, vdom.VDomRefOperation{ + Op: "fill", + Params: nil, + }) + } + } + + // Effect to start reading data + vdom.UseEffect(ctx, func() func() { + if readerState.Current.active { + return nil + } + + readerState.Current.active = true + go func() { + scanner := bufio.NewScanner(os.Stdin) + + for scanner.Scan() { + select { + case <-readerState.Current.done: + return + default: + text := strings.TrimSpace(scanner.Text()) + if value, err := strconv.ParseFloat(text, 64); err == nil { + setPointsFn(func(points []DataPoint) []DataPoint { + return append(points, DataPoint{Value: value}) + }) + setUpdateCountFn(func(updateCount int) int { + return updateCount + 1 + }) + AppClient.SendAsyncInitiation() + } + } + } + }() + + return func() { + close(readerState.Current.done) + readerState.Current.active = false + readerState.Current.done = make(chan bool) + } + }, []any{}) + + // Effect to handle drawing + vdom.UseEffect(ctx, func() func() { + drawGraph() + return nil + }, []any{updateCount, canvasRef.HasCurrent}) + + // Calculate stats + count, avg := calculateStats(points) + + return vdom.E("div", + vdom.Class("graph-container"), + vdom.E("h1", + vdom.Class("graph-title"), + "Live Data Graph", + ), + vdom.E("canvas", + vdom.Class("graph-canvas"), + vdom.P("ref", canvasRef), + vdom.P("width", canvasWidth), + vdom.P("height", canvasHeight), + ), + vdom.E("div", + vdom.Class("graph-stats"), + fmt.Sprintf("Points: %d Average: %.2f", count, avg), + ), + ) + }, +) + +func main() { + AppClient.RunMain() +} diff --git a/graph/style.css b/graph/style.css new file mode 100644 index 0000000..26fd5d0 --- /dev/null +++ b/graph/style.css @@ -0,0 +1,23 @@ +.graph-container { + padding: 24px; + background: #1a1a1a; + border-radius: 8px; + color: #ffffff; +} + +.graph-title { + margin: 0 0 16px 0; + font-size: 24px; + font-weight: 600; +} + +.graph-canvas { + background: #000000; + border-radius: 4px; + margin-bottom: 16px; +} + +.graph-stats { + font-family: monospace; + color: #888888; +} \ No newline at end of file diff --git a/prompt.md b/prompt.md index 6c4ce66..4d4226d 100644 --- a/prompt.md +++ b/prompt.md @@ -1,8 +1,8 @@ # VDOM System Guide -Wave Terminal includes a powerful VDOM (Virtual DOM) system that lets developers create rich HTML/React-based UI applications directly from Go code. The system translates Go components and elements into React components that are rendered within Wave Terminal's UI. +Wave Terminal includes a powerful VDOM (Virtual DOM) system that lets developers create rich HTML/React-based UI applications directly from Go code. The system translates Go components and elements into React components that are rendered within Wave Terminal's UI. It's particularly well-suited for administrative interfaces, monitoring dashboards, data visualization, configuration managers, and form-based applications where you want a graphical interface but don't need complex browser-side interactions. -This guide explains how to use the VDOM system to create interactive applications that run in Wave Terminal. While the patterns will feel familiar to React developers (components, props, hooks), the implementation is pure Go and takes advantage of Go's strengths like goroutines for async operations. +This guide explains how to use the VDOM system to create interactive applications that run in Wave Terminal. While the patterns will feel familiar to React developers (components, props, hooks), the implementation is pure Go and takes advantage of Go's strengths like goroutines for async operations. Note that complex browser-side interactions like drag-and-drop, rich text editing, or heavy JavaScript functionality are not supported - the framework is designed for straightforward, practical terminal-based applications. You'll learn how to: - Create and compose components @@ -602,5 +602,6 @@ type AppOpts struct { - Call SendAsyncInitiation() after async state updates - Provide keys when using ForEach() with lists - Consider cleanup functions in UseEffect() for async operations +-