clone

a browser-first diagram editor with live svg previews, local drafts and encrypted, accountless sharing

main
diagramzip / renderers / edge / toolchains / d2 / source / worker.go
worker.go 2.9K
1//go:build js && wasm23package main45import (6 "bytes"7 "context"8 "encoding/json"9 "fmt"10 "runtime/debug"11 "syscall/js"1213 "oss.terrastruct.com/d2/d2graph"14 "oss.terrastruct.com/d2/d2layouts/d2dagrelayout"15 "oss.terrastruct.com/d2/d2lib"16 "oss.terrastruct.com/d2/d2renderers/d2animate"17 "oss.terrastruct.com/d2/d2renderers/d2svg"18 d2log "oss.terrastruct.com/d2/lib/log"19 "oss.terrastruct.com/d2/lib/textmeasure"20)2122func dagreLayoutResolver(engine string) (d2graph.LayoutGraph, error) {23 if engine != "dagre" {24 return nil, fmt.Errorf("unexpected layout %q", engine)25 }26 return func(ctx context.Context, graph *d2graph.Graph) error {27 return d2dagrelayout.Layout(ctx, graph, nil)28 }, nil29}3031type response struct {32 SVG string `json:"svg"`33 Boards []string `json:"boards"`34}35type renderOptions struct {36 AnimateInterval int64 `json:"animateInterval"`37}3839func render(this js.Value, args []js.Value) (result any) {40 defer func() {41 if recovered := recover(); recovered != nil {42 fmt.Printf("D2 renderer panic: %v\n%s\n", recovered, debug.Stack())43 result = js.ValueOf(fmt.Sprintf(`{"error":%q}`, fmt.Sprintf("D2 renderer panic: %v", recovered)))44 }45 }()46 if len(args) < 1 || len(args) > 2 {47 return js.ValueOf(`{"error":"expected source string"}`)48 }49 var inputOptions renderOptions50 if len(args) == 2 && args[1].String() != "" {51 if err := json.Unmarshal([]byte(args[1].String()), &inputOptions); err != nil {52 return js.ValueOf(fmt.Sprintf(`{"error":%q}`, err.Error()))53 }54 }55 ruler, err := textmeasure.NewRuler()56 if err != nil {57 return js.ValueOf(fmt.Sprintf(`{"error":%q}`, err.Error()))58 }59 layout := "dagre"60 pad := int64(100)61 ctx := d2log.WithDefault(context.Background())62 d, _, err := d2lib.Compile(ctx, args[0].String(), &d2lib.CompileOptions{63 Layout: &layout,64 Ruler: ruler,65 LayoutResolver: dagreLayoutResolver,66 }, &d2svg.RenderOpts{Pad: &pad})67 if err != nil {68 return js.ValueOf(fmt.Sprintf(`{"error":%q}`, err.Error()))69 }70 opts := &d2svg.RenderOpts{Pad: &pad}71 boards, err := d2svg.RenderMultiboard(d, opts)72 if err != nil {73 return js.ValueOf(fmt.Sprintf(`{"error":%q}`, err.Error()))74 }75 base := boards[0]76 if inputOptions.AnimateInterval > 0 {77 base, err = d2animate.Wrap(d, boards, *opts, int(inputOptions.AnimateInterval))78 if err != nil {79 return js.ValueOf(fmt.Sprintf(`{"error":%q}`, err.Error()))80 }81 // The wrapper is itself the XML document; nested board declarations would82 // otherwise be rejected by the shared SVG sanitizer as extra roots.83 base = bytes.ReplaceAll(base, []byte(`<?xml version="1.0" encoding="UTF-8" standalone="no"?>`), nil)84 base = bytes.ReplaceAll(base, []byte(`<?xml version="1.0" encoding="utf-8"?>`), nil)85 }86 out := response{SVG: string(base)}87 for _, b := range boards {88 out.Boards = append(out.Boards, string(b))89 }90 b, _ := json.Marshal(out)91 return js.ValueOf(string(b))92}9394func main() {95 js.Global().Set("d2CustomRender", js.FuncOf(render))96 select {}97}