From 28e498aeaaecf733ce51337395605f14926401b2 Mon Sep 17 00:00:00 2001 From: delfino Date: Mon, 24 Feb 2025 23:22:18 +0000 Subject: [PATCH] d2js: updating target default + errors, readme --- ci/release/changelogs/next.md | 2 +- d2js/d2wasm/functions.go | 19 ++++++++++------ d2js/js/README.md | 2 +- d2js/js/test/unit/basic.test.js | 39 +++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 35f5b8b2a..0254037fc 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -4,7 +4,7 @@ #### Improvements 🧹 -- d2js: Support `d2-config`. Support additional render options: [#2343](https://github.com/terrastruct/d2/pull/2343) +- d2js: Support `d2-config`. Support additional options: [#2343](https://github.com/terrastruct/d2/pull/2343) - `themeID` - `darkThemeID` - `center` diff --git a/d2js/d2wasm/functions.go b/d2js/d2wasm/functions.go index 97834161c..192ac068e 100644 --- a/d2js/d2wasm/functions.go +++ b/d2js/d2wasm/functions.go @@ -271,20 +271,24 @@ func Render(args []js.Value) (interface{}, error) { return nil, &WASMError{Message: "missing 'diagram' field in input JSON", Code: 400} } + animateInterval := 0 + if input.Opts != nil && input.Opts.AnimateInterval != nil && *input.Opts.AnimateInterval > 0 { + animateInterval = int(*input.Opts.AnimateInterval) + } + var boardPath []string - var noChildren bool + noChildren := true if input.Opts.Target != nil { switch *input.Opts.Target { case "*": + noChildren = false case "": - noChildren = true default: target := *input.Opts.Target if strings.HasSuffix(target, ".*") { target = target[:len(target)-2] - } else { - noChildren = true + noChildren = false } key, err := d2parser.ParseKey(target) if err != nil { @@ -292,6 +296,9 @@ func Render(args []js.Value) (interface{}, error) { } boardPath = key.StringIDA() } + if !noChildren && animateInterval <= 0 { + return nil, &WASMError{Message: fmt.Sprintf("target '%s' only supported for animated SVGs", *input.Opts.Target), Code: 500} + } } diagram := input.Diagram.GetBoard(boardPath) @@ -310,9 +317,7 @@ func Render(args []js.Value) (interface{}, error) { renderOpts.Salt = input.Opts.Salt } - var animateInterval = 0 - if input.Opts != nil && input.Opts.AnimateInterval != nil && *input.Opts.AnimateInterval > 0 { - animateInterval = int(*input.Opts.AnimateInterval) + if animateInterval > 0 { masterID, err := diagram.HashID(renderOpts.Salt) if err != nil { return nil, &WASMError{Message: fmt.Sprintf("cannot process animate interval: %s", err.Error()), Code: 500} diff --git a/d2js/js/README.md b/d2js/js/README.md index e9fd6194c..2bb81cd50 100644 --- a/d2js/js/README.md +++ b/d2js/js/README.md @@ -87,7 +87,7 @@ All [RenderOptions](#renderoptions) properties in addition to: - `pad`: Pixels padded around the rendered diagram [default: 100] - `scale`: Scale the output. E.g., 0.5 to halve the default size. The default will render SVG's that will fit to screen. Setting to 1 turns off SVG fitting to screen. - `forceAppendix`: Adds an appendix for tooltips and links [default: false] -- `target`: Target board to render. Pass an empty string to target root board. If target ends with '*', it will be rendered with all of its scenarios, steps, and layers. Otherwise, only the target board will be rendered. E.g. --target='' to render root board only or --target='layers.x.*' to render layer 'x' with all of its children. +- `target`: Target board/s to render. If target ends with '*', it will be rendered with all of its scenarios, steps, and layers. Otherwise, only the target board will be rendered. Pass '*' to render all scenarios, steps, and layers. E.g. `target: 'layers.x.*'` to render layer 'x' with all of its children. Multi-board outputs are currently only supported for animated SVGs and so `animateInterval` must be set to a value greater than 0. - `animateInterval`: If given, multiple boards are packaged as 1 SVG which transitions through each board at the interval (in milliseconds). - `salt`: Add a salt value to ensure the output uses unique IDs. This is useful when generating multiple identical diagrams to be included in the same HTML doc, so that duplicate IDs do not cause invalid HTML. The salt value is a string that will be appended to IDs in the output. - `noXMLTag`: Omit XML tag `()` from output SVG files. Useful when generating SVGs for direct HTML embedding. diff --git a/d2js/js/test/unit/basic.test.js b/d2js/js/test/unit/basic.test.js index 949455c54..1ff5e9a64 100644 --- a/d2js/js/test/unit/basic.test.js +++ b/d2js/js/test/unit/basic.test.js @@ -121,6 +121,24 @@ x -> y await d2.worker.terminate(); }, 20000); + test("animated multi-board works", async () => { + const d2 = new D2(); + const source = ` +x -> y +layers: { + numbers: { + 1 -> 2 + } +} +`; + const options = { target: "*", animateInterval: 1000 }; + const result = await d2.compile(source, options); + const svg = await d2.render(result.diagram, result.renderOptions); + expect(svg).toContain(""); + await d2.worker.terminate(); + }, 20000); + test("latex works", async () => { const d2 = new D2(); const result = await d2.compile("x: |latex \\frac{f(x+h)-f(x)}{h} |"); @@ -141,4 +159,25 @@ x -> y } await d2.worker.terminate(); }, 20000); + + test("handles unanimated multi-board error correctly", async () => { + const d2 = new D2(); + const source = ` +x -> y +layers: { + numbers: { + 1 -> 2 + } +} +`; + const result = await d2.compile(source); + try { + await d2.render(result.diagram, { target: "*" }); + throw new Error("Should have thrown compile error"); + } catch (err) { + expect(err).toBeDefined(); + expect(err.message).not.toContain("Should have thrown compile error"); + } + await d2.worker.terminate(); + }, 20000); });