-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: add Excalidraw diagram editor widget #3479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| // Copyright 2026, Command Line Inc. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package cmd | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| "io" | ||
| "os" | ||
| "path/filepath" | ||
| "time" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| "github.com/wavetermdev/waveterm/pkg/waveobj" | ||
| "github.com/wavetermdev/waveterm/pkg/wshrpc" | ||
| "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" | ||
| ) | ||
|
|
||
| var excalidrawMagnified bool | ||
|
|
||
| var excalidrawCmd = &cobra.Command{ | ||
| Use: "excalidraw [file]", | ||
| Short: "open an Excalidraw diagram", | ||
| Args: cobra.MaximumNArgs(1), | ||
| RunE: excalidrawRun, | ||
| PreRunE: preRunSetupRpcClient, | ||
| } | ||
|
|
||
| var excalidrawPushCmd = &cobra.Command{ | ||
| Use: "push <blockid> [file]", | ||
| Short: "push Excalidraw JSON into a block's scene", | ||
| Args: cobra.RangeArgs(1, 2), | ||
| RunE: excalidrawPushRun, | ||
| PreRunE: preRunSetupRpcClient, | ||
| } | ||
|
|
||
| var excalidrawMermaidCmd = &cobra.Command{ | ||
| Use: "mermaid [blockid] [file]", | ||
| Short: "open or push a Mermaid diagram as Excalidraw", | ||
| Args: cobra.RangeArgs(0, 2), | ||
| RunE: excalidrawMermaidRun, | ||
| PreRunE: preRunSetupRpcClient, | ||
| } | ||
|
|
||
| func init() { | ||
| excalidrawCmd.Flags().BoolVarP(&excalidrawMagnified, "magnified", "m", false, "open in magnified mode") | ||
| excalidrawCmd.AddCommand(excalidrawPushCmd) | ||
| excalidrawCmd.AddCommand(excalidrawMermaidCmd) | ||
| rootCmd.AddCommand(excalidrawCmd) | ||
| } | ||
|
|
||
| func excalidrawRun(cmd *cobra.Command, args []string) (rtnErr error) { | ||
| defer func() { | ||
| sendActivity("excalidraw", rtnErr == nil) | ||
| }() | ||
| tabId := getTabIdFromEnv() | ||
| if tabId == "" { | ||
| return fmt.Errorf("no WAVETERM_TABID env var set") | ||
| } | ||
| meta := map[string]any{ | ||
| waveobj.MetaKey_View: "excalidraw", | ||
| } | ||
| if len(args) > 0 { | ||
| absFile, err := filepath.Abs(args[0]) | ||
| if err != nil { | ||
| return fmt.Errorf("getting absolute path: %w", err) | ||
| } | ||
| meta[waveobj.MetaKey_File] = absFile | ||
| } | ||
| wshCmd := &wshrpc.CommandCreateBlockData{ | ||
| TabId: tabId, | ||
| BlockDef: &waveobj.BlockDef{ | ||
| Meta: meta, | ||
| }, | ||
| Magnified: excalidrawMagnified, | ||
| Focused: true, | ||
| } | ||
| _, err := wshclient.CreateBlockCommand(RpcClient, *wshCmd, &wshrpc.RpcOpts{Timeout: 2000}) | ||
| if err != nil { | ||
| return fmt.Errorf("creating excalidraw block: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func excalidrawPushRun(cmd *cobra.Command, args []string) (rtnErr error) { | ||
| defer func() { | ||
| sendActivity("excalidraw:push", rtnErr == nil) | ||
| }() | ||
| blockId := args[0] | ||
| var jsonData []byte | ||
| var err error | ||
| if len(args) > 1 { | ||
| jsonData, err = os.ReadFile(args[1]) | ||
| } else { | ||
| jsonData, err = io.ReadAll(os.Stdin) | ||
| } | ||
| if err != nil { | ||
| return fmt.Errorf("reading input: %w", err) | ||
| } | ||
| var sceneData any | ||
| if err := json.Unmarshal(jsonData, &sceneData); err != nil { | ||
| return fmt.Errorf("invalid JSON: %w", err) | ||
| } | ||
| pushData := wshrpc.CommandExcalidrawPushData{ | ||
| BlockId: blockId, | ||
| SceneData: sceneData, | ||
| } | ||
| err = wshclient.ExcalidrawPushCommand(RpcClient, pushData, &wshrpc.RpcOpts{Timeout: 5000}) | ||
| if err != nil { | ||
| return fmt.Errorf("push failed: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func excalidrawMermaidRun(cmd *cobra.Command, args []string) (rtnErr error) { | ||
| defer func() { | ||
| sendActivity("excalidraw:mermaid", rtnErr == nil) | ||
| }() | ||
| tabId := getTabIdFromEnv() | ||
| if tabId == "" { | ||
| return fmt.Errorf("no WAVETERM_TABID env var set") | ||
| } | ||
|
Comment on lines
+120
to
+123
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Require
Move the tab ID lookup into the 🤖 Prompt for AI Agents |
||
| var blockId string | ||
| var mermaidData []byte | ||
| var err error | ||
| switch len(args) { | ||
| case 0: | ||
| mermaidData, err = io.ReadAll(os.Stdin) | ||
| if err != nil { | ||
| return fmt.Errorf("reading stdin: %w", err) | ||
| } | ||
| case 1: | ||
| mermaidData, err = os.ReadFile(args[0]) | ||
| if err != nil { | ||
| if !os.IsNotExist(err) { | ||
| return fmt.Errorf("reading file: %w", err) | ||
| } | ||
| blockId = args[0] | ||
| mermaidData, err = io.ReadAll(os.Stdin) | ||
| if err != nil { | ||
| return fmt.Errorf("reading stdin: %w", err) | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| case 2: | ||
| blockId = args[0] | ||
| mermaidData, err = os.ReadFile(args[1]) | ||
| if err != nil { | ||
| return fmt.Errorf("reading file: %w", err) | ||
| } | ||
| } | ||
| if blockId == "" { | ||
| createData := &wshrpc.CommandCreateBlockData{ | ||
| TabId: tabId, | ||
| BlockDef: &waveobj.BlockDef{ | ||
| Meta: map[string]any{ | ||
| waveobj.MetaKey_View: "excalidraw", | ||
| }, | ||
| }, | ||
| Magnified: excalidrawMagnified, | ||
| Focused: true, | ||
| } | ||
| oref, err := wshclient.CreateBlockCommand(RpcClient, *createData, &wshrpc.RpcOpts{Timeout: 2000}) | ||
| if err != nil { | ||
| return fmt.Errorf("creating excalidraw block: %w", err) | ||
| } | ||
| blockId = oref.OID | ||
| time.Sleep(500 * time.Millisecond) | ||
| } | ||
| pushData := wshrpc.CommandExcalidrawPushData{ | ||
| BlockId: blockId, | ||
| SceneData: string(mermaidData), | ||
| Format: "mermaid", | ||
| } | ||
| err = wshclient.ExcalidrawPushCommand(RpcClient, pushData, &wshrpc.RpcOpts{Timeout: 5000}) | ||
|
Comment on lines
+163
to
+175
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 5 'func .*Publish|type WaveEvent|Persist|persist' pkg/wps
rg -n -C 5 'waveEventSubscribeSingle|excalidraw:pushscene' \
frontend/app/store/wps.ts frontend/app/view/excalidraw/excalidraw-model.tsRepository: wavetermdev/waveterm Length of output: 8449 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- broker publish and replay paths ---'
sed -n '120,270p' pkg/wps/wps.go
sed -n '1,130p' pkg/wps/wpstypes.go
printf '%s\n' '--- frontend subscription and event handling ---'
sed -n '1,180p' frontend/app/store/wps.ts
sed -n '70,145p' frontend/app/view/excalidraw/excalidraw-model.ts
printf '%s\n' '--- Excalidraw push event construction and command flow ---'
rg -n -C 8 'Event_ExcalidrawPushScene|excalidraw:pushscene|ExcalidrawPushCommand|excalidrawMermaidRun|CreateBlockCommand' --glob '*.go' --glob '*.ts' --glob '*.tsx' .Repository: wavetermdev/waveterm Length of output: 50376 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- subscription replay behavior ---'
rg -n -C 12 'EventSubCommand|ReadEventHistory|Subscribe\(' pkg/wshrpc pkg/wps
rg -n -C 8 'handleWaveEvent\(' frontend/app frontend
printf '%s\n' '--- exact event scope and block lifecycle ---'
sed -n '116,180p' cmd/wsh/cmd/wshcmd-excalidraw.go
sed -n '1560,1605p' pkg/wshrpc/wshserver/wshserver.go
sed -n '1,125p' pkg/wps/wps.goRepository: wavetermdev/waveterm Length of output: 26577 Replace the fixed delay with reliable scene delivery.
🤖 Prompt for AI Agents |
||
| if err != nil { | ||
| return fmt.Errorf("mermaid push failed: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.