diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 4aa513a11..a312ab5be 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -1,5 +1,7 @@ #### Features ๐Ÿš€ +- Multi-board SVG outputs with internal links go to their output paths [#1116](https://github.com/terrastruct/d2/pull/1116) + #### Improvements ๐Ÿงน #### Bugfixes โ›‘๏ธ diff --git a/d2cli/main.go b/d2cli/main.go index ae2446c95..400d81aa5 100644 --- a/d2cli/main.go +++ b/d2cli/main.go @@ -358,6 +358,15 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende return pdf, true, nil } else { compileDur := time.Since(start) + if animateInterval <= 0 { + // Rename all the "root.layers.x" to the paths that the boards get output to + linkToOutput, err := resolveLinks("root", outputPath, diagram) + if err != nil { + return nil, false, err + } + relink(diagram, linkToOutput) + } + boards, err := render(ctx, ms, compileDur, plugin, renderOpts, inputPath, outputPath, bundle, forceAppendix, page, ruler, diagram) if err != nil { return nil, false, err @@ -382,6 +391,99 @@ func compile(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, rende } } +func resolveLinks(currDiagramPath, outputPath string, diagram *d2target.Diagram) (linkToOutput map[string]string, err error) { + if diagram.Name != "" { + ext := filepath.Ext(outputPath) + outputPath = strings.TrimSuffix(outputPath, ext) + outputPath = filepath.Join(outputPath, diagram.Name) + outputPath += ext + } + + boardOutputPath := outputPath + if len(diagram.Layers) > 0 || len(diagram.Scenarios) > 0 || len(diagram.Steps) > 0 { + ext := filepath.Ext(boardOutputPath) + boardOutputPath = strings.TrimSuffix(boardOutputPath, ext) + boardOutputPath = filepath.Join(boardOutputPath, "index") + boardOutputPath += ext + } + + layersOutputPath := outputPath + if len(diagram.Scenarios) > 0 || len(diagram.Steps) > 0 { + ext := filepath.Ext(layersOutputPath) + layersOutputPath = strings.TrimSuffix(layersOutputPath, ext) + layersOutputPath = filepath.Join(layersOutputPath, "layers") + layersOutputPath += ext + } + scenariosOutputPath := outputPath + if len(diagram.Layers) > 0 || len(diagram.Steps) > 0 { + ext := filepath.Ext(scenariosOutputPath) + scenariosOutputPath = strings.TrimSuffix(scenariosOutputPath, ext) + scenariosOutputPath = filepath.Join(scenariosOutputPath, "scenarios") + scenariosOutputPath += ext + } + stepsOutputPath := outputPath + if len(diagram.Layers) > 0 || len(diagram.Scenarios) > 0 { + ext := filepath.Ext(stepsOutputPath) + stepsOutputPath = strings.TrimSuffix(stepsOutputPath, ext) + stepsOutputPath = filepath.Join(stepsOutputPath, "steps") + stepsOutputPath += ext + } + + linkToOutput = map[string]string{currDiagramPath: boardOutputPath} + + for _, dl := range diagram.Layers { + m, err := resolveLinks(strings.Join([]string{currDiagramPath, "layers", dl.Name}, "."), layersOutputPath, dl) + if err != nil { + return nil, err + } + for k, v := range m { + linkToOutput[k] = v + } + } + for _, dl := range diagram.Scenarios { + m, err := resolveLinks(strings.Join([]string{currDiagramPath, "scenarios", dl.Name}, "."), scenariosOutputPath, dl) + if err != nil { + return nil, err + } + for k, v := range m { + linkToOutput[k] = v + } + } + for _, dl := range diagram.Steps { + m, err := resolveLinks(strings.Join([]string{currDiagramPath, "steps", dl.Name}, "."), stepsOutputPath, dl) + if err != nil { + return nil, err + } + for k, v := range m { + linkToOutput[k] = v + } + } + + return linkToOutput, nil +} + +func relink(d *d2target.Diagram, linkToOutput map[string]string) { + for i, shape := range d.Shapes { + if shape.Link != "" { + for k, v := range linkToOutput { + if shape.Link == k { + d.Shapes[i].Link = v + break + } + } + } + } + for _, board := range d.Layers { + relink(board, linkToOutput) + } + for _, board := range d.Scenarios { + relink(board, linkToOutput) + } + for _, board := range d.Steps { + relink(board, linkToOutput) + } +} + func render(ctx context.Context, ms *xmain.State, compileDur time.Duration, plugin d2plugin.Plugin, opts d2svg.RenderOpts, inputPath, outputPath string, bundle, forceAppendix bool, page playwright.Page, ruler *textmeasure.Ruler, diagram *d2target.Diagram) ([][]byte, error) { if diagram.Name != "" { ext := filepath.Ext(outputPath) diff --git a/e2etests-cli/main_test.go b/e2etests-cli/main_test.go index 402ce7f3e..6bad9de65 100644 --- a/e2etests-cli/main_test.go +++ b/e2etests-cli/main_test.go @@ -82,6 +82,33 @@ steps: { assert.Testdata(t, ".svg", svg) }, }, + { + name: "linked-path", + run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) { + writeFile(t, dir, "linked.d2", `cat: how does the cat go? { + link: layers.cat +} +layers: { + cat: { + home: { + link: _ + } + the cat -> meow: goes + + scenarios: { + big cat: { + the cat -> roar: goes + } + } + } +} +`) + err := runTestMain(t, ctx, dir, env, "linked.d2") + assert.Success(t, err) + + assert.TestdataDir(t, filepath.Join(dir, "linked")) + }, + }, { name: "with-font", run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) { diff --git a/e2etests-cli/testdata/TestCLI_E2E/internal_linked_pdf.exp.pdf b/e2etests-cli/testdata/TestCLI_E2E/internal_linked_pdf.exp.pdf index 5842a8b83..4f63dfc5b 100644 Binary files a/e2etests-cli/testdata/TestCLI_E2E/internal_linked_pdf.exp.pdf and b/e2etests-cli/testdata/TestCLI_E2E/internal_linked_pdf.exp.pdf differ diff --git a/e2etests-cli/testdata/TestCLI_E2E/linked-path/cat/big cat.exp.svg b/e2etests-cli/testdata/TestCLI_E2E/linked-path/cat/big cat.exp.svg new file mode 100644 index 000000000..9e77770f3 --- /dev/null +++ b/e2etests-cli/testdata/TestCLI_E2E/linked-path/cat/big cat.exp.svg @@ -0,0 +1,102 @@ +the catroar goes + + + diff --git a/e2etests-cli/testdata/TestCLI_E2E/linked-path/cat/index.exp.svg b/e2etests-cli/testdata/TestCLI_E2E/linked-path/cat/index.exp.svg new file mode 100644 index 000000000..0e66f755b --- /dev/null +++ b/e2etests-cli/testdata/TestCLI_E2E/linked-path/cat/index.exp.svg @@ -0,0 +1,117 @@ +home + + + + + + + + + + + +the catmeow goes + + + diff --git a/e2etests-cli/testdata/TestCLI_E2E/linked-path/index.exp.svg b/e2etests-cli/testdata/TestCLI_E2E/linked-path/index.exp.svg new file mode 100644 index 000000000..c5328ae3b --- /dev/null +++ b/e2etests-cli/testdata/TestCLI_E2E/linked-path/index.exp.svg @@ -0,0 +1,110 @@ +how does the cat go? + + + + + + + + + + + + + + +