diff --git a/d2js/d2wasm/functions.go b/d2js/d2wasm/functions.go index 45e3cae0c..f0a8b71e7 100644 --- a/d2js/d2wasm/functions.go +++ b/d2js/d2wasm/functions.go @@ -154,6 +154,26 @@ func GetELKGraph(args []js.Value) (interface{}, error) { return elk, nil } +func layoutResolver() func(engine string) (d2graph.LayoutGraph, error) { + cached := make(map[string]d2graph.LayoutGraph) + return func(engine string) (d2graph.LayoutGraph, error) { + if c, ok := cached[engine]; ok { + return c, nil + } + var layout d2graph.LayoutGraph + switch engine { + case "dagre": + layout = d2dagrelayout.DefaultLayout + case "elk": + layout = d2elklayout.DefaultLayout + default: + return nil, &WASMError{Message: fmt.Sprintf("layout option '%s' not recognized", engine), Code: 400} + } + cached[engine] = layout + return layout, nil + } +} + func Compile(args []js.Value) (interface{}, error) { if len(args) < 1 { return nil, &WASMError{Message: "missing JSON argument", Code: 400} @@ -171,35 +191,29 @@ func Compile(args []js.Value) (interface{}, error) { return nil, &WASMError{Message: "missing 'index' file in input fs", Code: 400} } - fs, err := memfs.New(input.FS) + compileOpts := &d2lib.CompileOptions{ + UTF16Pos: true, + LayoutResolver: layoutResolver(), + } + + var err error + compileOpts.FS, err = memfs.New(input.FS) if err != nil { return nil, &WASMError{Message: fmt.Sprintf("invalid fs input: %s", err.Error()), Code: 400} } - ruler, err := textmeasure.NewRuler() + compileOpts.Ruler, err = textmeasure.NewRuler() if err != nil { return nil, &WASMError{Message: fmt.Sprintf("text ruler cannot be initialized: %s", err.Error()), Code: 500} } - ctx := log.WithDefault(context.Background()) - layoutFunc := d2dagrelayout.DefaultLayout + if input.Opts != nil && input.Opts.Layout != nil { - switch *input.Opts.Layout { - case "dagre": - layoutFunc = d2dagrelayout.DefaultLayout - case "elk": - layoutFunc = d2elklayout.DefaultLayout - default: - return nil, &WASMError{Message: fmt.Sprintf("layout option '%s' not recognized", *input.Opts.Layout), Code: 400} - } - } - layoutResolver := func(engine string) (d2graph.LayoutGraph, error) { - return layoutFunc, nil + compileOpts.Layout = input.Opts.Layout } renderOpts := &d2svg.RenderOpts{} - var fontFamily *d2fonts.FontFamily if input.Opts != nil && input.Opts.Sketch != nil && *input.Opts.Sketch { - fontFamily = go2.Pointer(d2fonts.HandDrawn) + compileOpts.FontFamily = go2.Pointer(d2fonts.HandDrawn) renderOpts.Sketch = input.Opts.Sketch } if input.Opts != nil && input.Opts.Pad != nil { @@ -217,13 +231,9 @@ func Compile(args []js.Value) (interface{}, error) { if input.Opts != nil && input.Opts.Scale != nil { renderOpts.Scale = input.Opts.Scale } - diagram, g, err := d2lib.Compile(ctx, input.FS["index"], &d2lib.CompileOptions{ - UTF16Pos: true, - FS: fs, - Ruler: ruler, - LayoutResolver: layoutResolver, - FontFamily: fontFamily, - }, renderOpts) + + ctx := log.WithDefault(context.Background()) + diagram, g, err := d2lib.Compile(ctx, input.FS["index"], compileOpts, renderOpts) if err != nil { if pe, ok := err.(*d2parser.ParseError); ok { errs, _ := json.Marshal(pe.Errors) @@ -232,24 +242,22 @@ func Compile(args []js.Value) (interface{}, error) { return nil, &WASMError{Message: err.Error(), Code: 500} } - mergedRenderOpts := RenderOptions{ - ThemeID: renderOpts.ThemeID, - DarkThemeID: renderOpts.DarkThemeID, - Sketch: renderOpts.Sketch, - Pad: renderOpts.Pad, - Center: renderOpts.Center, - Scale: input.Opts.Scale, - ForceAppendix: input.Opts.ForceAppendix, - }; - input.FS["index"] = d2format.Format(g.AST) return CompileResponse{ FS: input.FS, Diagram: *diagram, Graph: *g, - RenderOpts: mergedRenderOpts, - }, nil + Options: RenderOptions{ + ThemeID: renderOpts.ThemeID, + DarkThemeID: renderOpts.DarkThemeID, + Sketch: renderOpts.Sketch, + Pad: renderOpts.Pad, + Center: renderOpts.Center, + Scale: renderOpts.Scale, + ForceAppendix: input.Opts.ForceAppendix, + }, + }, nil } func Render(args []js.Value) (interface{}, error) { diff --git a/d2js/d2wasm/types.go b/d2js/d2wasm/types.go index 2437924d8..6346292a1 100644 --- a/d2js/d2wasm/types.go +++ b/d2js/d2wasm/types.go @@ -33,7 +33,7 @@ type BoardPositionResponse struct { type CompileRequest struct { FS map[string]string `json:"fs"` - Opts *CompileOptions `json:"options"` + Opts *CompileOptions `json:"options"` } type RenderOptions struct { @@ -47,15 +47,15 @@ type RenderOptions struct { } type CompileOptions struct { - RenderOptions - Layout *string `json:"layout"` + RenderOptions + Layout *string `json:"layout"` } type CompileResponse struct { - FS map[string]string `json:"fs"` - Diagram d2target.Diagram `json:"diagram"` - Graph d2graph.Graph `json:"graph"` - RenderOpts RenderOptions `json:"renderOpts"` + FS map[string]string `json:"fs"` + Diagram d2target.Diagram `json:"diagram"` + Graph d2graph.Graph `json:"graph"` + Options RenderOptions `json:"options"` } type CompletionResponse struct { diff --git a/d2js/js/examples/customizable.html b/d2js/js/examples/customizable.html index 3855a1841..c0947ea5f 100644 --- a/d2js/js/examples/customizable.html +++ b/d2js/js/examples/customizable.html @@ -316,7 +316,7 @@ center, forceAppendix, }); - const svg = await d2.render(result.diagram, result.renderOpts); + const svg = await d2.render(result.diagram, result.options); document.getElementById("output").innerHTML = svg; } catch (err) { console.error(err); diff --git a/d2js/js/src/worker.browser.js b/d2js/js/src/worker.browser.js index 6dbb758ff..6a00c59ca 100644 --- a/d2js/js/src/worker.browser.js +++ b/d2js/js/src/worker.browser.js @@ -30,12 +30,10 @@ export function setupMessageHandler(isNode, port, initWasm) { // single-threaded WASM call cannot complete without giving control back // So we compute it, store it here, then during elk layout, instead // of computing again, we use this variable (and unset it for next call) - if (data.options.layout === "elk") { - const elkGraph = await d2.getELKGraph(JSON.stringify(data)); - const elkGraph2 = JSON.parse(elkGraph).data; - const layout = await elk.layout(elkGraph2); - globalThis.elkResult = layout; - } + const elkGraph = await d2.getELKGraph(JSON.stringify(data)); + const elkGraph2 = JSON.parse(elkGraph).data; + const layout = await elk.layout(elkGraph2); + globalThis.elkResult = layout; const result = await d2.compile(JSON.stringify(data)); const response = JSON.parse(result); diff --git a/d2js/js/src/worker.js b/d2js/js/src/worker.js index b615fd569..427fbfffb 100644 --- a/d2js/js/src/worker.js +++ b/d2js/js/src/worker.js @@ -27,12 +27,10 @@ export function setupMessageHandler(isNode, port, initWasm) { case "compile": try { - if (data.options.layout === "elk") { - const elkGraph = await d2.getELKGraph(JSON.stringify(data)); - const elkGraph2 = JSON.parse(elkGraph).data; - const layout = await elk.layout(elkGraph2); - globalThis.elkResult = layout; - } + const elkGraph = await d2.getELKGraph(JSON.stringify(data)); + const elkGraph2 = JSON.parse(elkGraph).data; + const layout = await elk.layout(elkGraph2); + globalThis.elkResult = layout; const result = await d2.compile(JSON.stringify(data)); const response = JSON.parse(result); if (response.error) throw new Error(response.error.message); diff --git a/d2js/js/src/worker.node.js b/d2js/js/src/worker.node.js index b615fd569..427fbfffb 100644 --- a/d2js/js/src/worker.node.js +++ b/d2js/js/src/worker.node.js @@ -27,12 +27,10 @@ export function setupMessageHandler(isNode, port, initWasm) { case "compile": try { - if (data.options.layout === "elk") { - const elkGraph = await d2.getELKGraph(JSON.stringify(data)); - const elkGraph2 = JSON.parse(elkGraph).data; - const layout = await elk.layout(elkGraph2); - globalThis.elkResult = layout; - } + const elkGraph = await d2.getELKGraph(JSON.stringify(data)); + const elkGraph2 = JSON.parse(elkGraph).data; + const layout = await elk.layout(elkGraph2); + globalThis.elkResult = layout; const result = await d2.compile(JSON.stringify(data)); const response = JSON.parse(result); if (response.error) throw new Error(response.error.message);