diff --git a/d2js/d2wasm/functions.go b/d2js/d2wasm/functions.go index f0a8b71e7..3b76f50e2 100644 --- a/d2js/d2wasm/functions.go +++ b/d2js/d2wasm/functions.go @@ -154,26 +154,6 @@ 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} @@ -192,8 +172,18 @@ func Compile(args []js.Value) (interface{}, error) { } compileOpts := &d2lib.CompileOptions{ - UTF16Pos: true, - LayoutResolver: layoutResolver(), + UTF16Pos: true, + } + + compileOpts.LayoutResolver = func(engine string) (d2graph.LayoutGraph, error) { + switch engine { + case "dagre": + return d2dagrelayout.DefaultLayout, nil + case "elk": + return d2elklayout.DefaultLayout, nil + default: + return nil, &WASMError{Message: fmt.Sprintf("layout option '%s' not recognized", engine), Code: 400} + } } var err error @@ -212,9 +202,11 @@ func Compile(args []js.Value) (interface{}, error) { } renderOpts := &d2svg.RenderOpts{} - if input.Opts != nil && input.Opts.Sketch != nil && *input.Opts.Sketch { - compileOpts.FontFamily = go2.Pointer(d2fonts.HandDrawn) + if input.Opts != nil && input.Opts.Sketch != nil { renderOpts.Sketch = input.Opts.Sketch + if *input.Opts.Sketch { + compileOpts.FontFamily = go2.Pointer(d2fonts.HandDrawn) + } } if input.Opts != nil && input.Opts.Pad != nil { renderOpts.Pad = input.Opts.Pad @@ -248,7 +240,7 @@ func Compile(args []js.Value) (interface{}, error) { FS: input.FS, Diagram: *diagram, Graph: *g, - Options: RenderOptions{ + RenderOptions: RenderOptions{ ThemeID: renderOpts.ThemeID, DarkThemeID: renderOpts.DarkThemeID, Sketch: renderOpts.Sketch, diff --git a/d2js/d2wasm/types.go b/d2js/d2wasm/types.go index 6346292a1..592facbf1 100644 --- a/d2js/d2wasm/types.go +++ b/d2js/d2wasm/types.go @@ -52,10 +52,10 @@ type CompileOptions struct { } type CompileResponse struct { - FS map[string]string `json:"fs"` - Diagram d2target.Diagram `json:"diagram"` - Graph d2graph.Graph `json:"graph"` - Options RenderOptions `json:"options"` + FS map[string]string `json:"fs"` + Diagram d2target.Diagram `json:"diagram"` + Graph d2graph.Graph `json:"graph"` + RenderOptions RenderOptions `json:"renderOptions"` } type CompletionResponse struct { diff --git a/d2js/js/README.md b/d2js/js/README.md index 4c474e85a..df290a82d 100644 --- a/d2js/js/README.md +++ b/d2js/js/README.md @@ -45,7 +45,7 @@ const result = await d2.compile('x -> y'); const svg = await d2.render(result.diagram, result.options); ``` -Additional Configuration: +Configuring render options (see [CompileOptions](#compileoptions) for all available options): ```javascript import { D2 } from '@terrastruct/d2'; @@ -53,9 +53,9 @@ import { D2 } from '@terrastruct/d2'; const d2 = new D2(); const result = await d2.compile('x -> y', { - sketch = true, + sketch: true, }); -const svg = await d2.render(result.diagram, result.options); +const svg = await d2.render(result.diagram, result.renderOptions); ``` ## API Reference @@ -74,7 +74,7 @@ Renders a compiled diagram to SVG. ### `CompileOptions` -All `RenderOptions` properties in addition to: +All [RenderOptions](#renderoptions) properties in addition to: - `layout`: Layout engine to use ('dagre' | 'elk') [default: 'dagre'] diff --git a/d2js/js/examples/customizable.html b/d2js/js/examples/customizable.html index c0947ea5f..a7832bd7f 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.options); + const svg = await d2.render(result.diagram, result.renderOptions); 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 6a00c59ca..4d6421246 100644 --- a/d2js/js/src/worker.browser.js +++ b/d2js/js/src/worker.browser.js @@ -30,6 +30,14 @@ 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 the layout option has not been set, we generate the elk layout now + // anyway to support `layout-engine: elk` in d2-config vars + if (data.options.layout === "elk" || data.options.layout == null) { + 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); diff --git a/d2js/js/src/worker.node.js b/d2js/js/src/worker.node.js index 427fbfffb..6901d957f 100644 --- a/d2js/js/src/worker.node.js +++ b/d2js/js/src/worker.node.js @@ -27,10 +27,12 @@ export function setupMessageHandler(isNode, port, initWasm) { case "compile": try { - const elkGraph = await d2.getELKGraph(JSON.stringify(data)); - const elkGraph2 = JSON.parse(elkGraph).data; - const layout = await elk.layout(elkGraph2); - globalThis.elkResult = layout; + if (data.options.layout === "elk" || data.options.layout == null) { + 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/test/unit/basic.test.js b/d2js/js/test/unit/basic.test.js index 985855988..26c3550b4 100644 --- a/d2js/js/test/unit/basic.test.js +++ b/d2js/js/test/unit/basic.test.js @@ -25,6 +25,64 @@ describe("D2 Unit Tests", () => { await d2.worker.terminate(); }, 20000); + test("d2-config read correctly", async () => { + const d2 = new D2(); + const result = await d2.compile( + ` +vars: { + d2-config: { + theme-id: 4 + dark-theme-id: 200 + pad: 10 + center: true + sketch: true + layout-engine: elk + } +} +x -> y +` + ); + expect(result.renderOptions.sketch).toBe(true); + expect(result.renderOptions.themeID).toBe(4); + expect(result.renderOptions.darkThemeID).toBe(200); + // expect(result.renderOptions.center).toBe(true); + expect(result.renderOptions.pad).toBe(10); + await d2.worker.terminate(); + }, 20000); + + test("render options take priority", async () => { + const d2 = new D2(); + const result = await d2.compile( + ` +vars: { + d2-config: { + theme-id: 4 + dark-theme-id: 200 + pad: 10 + center: true + sketch: true + layout-engine: elk + } +} +x -> y +`, + { + sketch: false, + themeID: 100, + darkThemeID: 300, + center: false, + pad: 0, + layout: "dagre", + } + ); + expect(result.renderOptions.sketch).toBe(false); + expect(result.renderOptions.themeID).toBe(100); + expect(result.renderOptions.darkThemeID).toBe(300); + expect(result.renderOptions.center).toBe(false); + expect(result.renderOptions.pad).toBe(0); + await d2.worker.terminate(); + }, 20000); + test("sketch render works", async () => { const d2 = new D2(); const result = await d2.compile("x -> y", { sketch: true });