diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index 3883537f4..1a7075a1f 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -20,7 +20,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }} DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() with: name: d2chaos diff --git a/README.md b/README.md index e465f01a5..12133f941 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A modern diagram scripting language that turns text to diagrams. -[Docs](https://d2lang.com) | [Cheat sheet](./docs/assets/cheat_sheet.pdf) | [Comparisons](https://text-to-diagram.com) | [Playground](https://play.d2lang.com) +[Docs](https://d2lang.com) | [Cheat sheet](./docs/assets/cheat_sheet.pdf) | [Comparisons](https://text-to-diagram.com) | [Playground](https://play.d2lang.com) | [IDE](https://app.terrastruct.com) [![ci](https://github.com/terrastruct/d2/actions/workflows/ci.yml/badge.svg)](https://github.com/terrastruct/d2/actions/workflows/ci.yml) [![daily](https://github.com/terrastruct/d2/actions/workflows/daily.yml/badge.svg)](https://github.com/terrastruct/d2/actions/workflows/daily.yml) @@ -16,6 +16,9 @@ D2 Playground button + +D2 Studio button + https://user-images.githubusercontent.com/3120367/206125010-bd1fea8e-248a-43e7-8f85-0bbfca0c6e2a.mp4 @@ -263,11 +266,13 @@ let us know and we'll be happy to include it here! - **Remark Plugin**: [https://github.com/mech-a/remark-d2](https://github.com/mech-a/remark-d2) - **VitePress Plugin**: [https://github.com/BadgerHobbs/vitepress-plugin-d2](https://github.com/BadgerHobbs/vitepress-plugin-d2) - **Zed extension**: [https://github.com/gabeidx/zed-d2](https://github.com/gabeidx/zed-d2) +- **Hexo blog extension**: [https://github.com/leverimmy/hexo-d2](https://github.com/leverimmy/hexo-d2) ### Misc - **Comparison site**: [https://github.com/terrastruct/text-to-diagram-site](https://github.com/terrastruct/text-to-diagram-site) - **Playground**: [https://github.com/terrastruct/d2-playground](https://github.com/terrastruct/d2-playground) +- **IDE (paid)**: [https://app.terrastruct.com](https://app.terrastruct.com) - **Language docs**: [https://github.com/terrastruct/d2-docs](https://github.com/terrastruct/d2-docs) - **Hosted icons**: [https://icons.terrastruct.com](https://icons.terrastruct.com) diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index f152d8600..c7fb38b55 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -1,7 +1,13 @@ #### Features ๐Ÿš€ +- Icons: connections can include icons [#12](https://github.com/terrastruct/d2/issues/12) + #### Improvements ๐Ÿงน d2js: Support additional render options (`themeID`, `darkThemeID`, `center`, `pad`, `scale` and `forceAppendix`). Support `d2-config`. [#2343](https://github.com/terrastruct/d2/pull/2343) #### Bugfixes โ›‘๏ธ + +- Compiler: + - fixes panic when `sql_shape` shape value had mixed casing [#2349](https://github.com/terrastruct/d2/pull/2349) + - fixes support for `center` in `d2-config` [#2360](https://github.com/terrastruct/d2/pull/2360) diff --git a/d2cli/main.go b/d2cli/main.go index 3e9475149..dbf1ae2c4 100644 --- a/d2cli/main.go +++ b/d2cli/main.go @@ -50,7 +50,7 @@ import ( func Run(ctx context.Context, ms *xmain.State) (err error) { ctx = log.WithDefault(ctx) // These should be kept up-to-date with the d2 man page - watchFlag, err := ms.Opts.Bool("D2_WATCH", "watch", "w", false, "watch for changes to input and live reload. Use $HOST and $PORT to specify the listening address.\n(default localhost:0, which is will open on a randomly available local port).") + watchFlag, err := ms.Opts.Bool("D2_WATCH", "watch", "w", false, "watch for changes to input and live reload. Use $HOST and $PORT to specify the listening address.\n(default localhost:0, which will open on a randomly available local port).") if err != nil { return err } diff --git a/d2compiler/compile.go b/d2compiler/compile.go index 1fa28054e..e8af01b14 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -514,13 +514,14 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) { c.compileLabel(attrs, f) c.compilePosition(attrs, f) case "shape": - in := d2target.IsShape(scalar.ScalarString()) - _, isArrowhead := d2target.Arrowheads[scalar.ScalarString()] + shapeVal := strings.ToLower(scalar.ScalarString()) + in := d2target.IsShape(shapeVal) + _, isArrowhead := d2target.Arrowheads[shapeVal] if !in && !isArrowhead { c.errorf(scalar, "unknown shape %q", scalar.ScalarString()) return } - attrs.Shape.Value = scalar.ScalarString() + attrs.Shape.Value = shapeVal if strings.EqualFold(attrs.Shape.Value, d2target.ShapeCode) { // Explicit code shape is plaintext. attrs.Language = d2target.ShapeText @@ -596,11 +597,12 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) { attrs.Link.MapKey = f.LastPrimaryKey() case "direction": dirs := []string{"up", "down", "right", "left"} - if !go2.Contains(dirs, scalar.ScalarString()) { + val := strings.ToLower(scalar.ScalarString()) + if !go2.Contains(dirs, val) { c.errorf(scalar, `direction must be one of %v, got %q`, strings.Join(dirs, ", "), scalar.ScalarString()) return } - attrs.Direction.Value = scalar.ScalarString() + attrs.Direction.Value = val attrs.Direction.MapKey = f.LastPrimaryKey() case "constraint": if _, ok := scalar.(d2ast.String); !ok { @@ -1453,6 +1455,12 @@ func compileConfig(ir *d2ir.Map) (*d2target.Config, error) { config.LayoutEngine = go2.Pointer(f.Primary().Value.ScalarString()) } + f = configMap.GetField(d2ast.FlatUnquotedString("center")) + if f != nil { + val, _ := strconv.ParseBool(f.Primary().Value.ScalarString()) + config.Center = &val + } + f = configMap.GetField(d2ast.FlatUnquotedString("theme-overrides")) if f != nil { overrides, err := compileThemeOverrides(f.Map()) diff --git a/d2exporter/export.go b/d2exporter/export.go index 8b676e554..672bae188 100644 --- a/d2exporter/export.go +++ b/d2exporter/export.go @@ -8,6 +8,7 @@ import ( "oss.terrastruct.com/util-go/go2" + "oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2parser" "oss.terrastruct.com/d2/d2renderers/d2fonts" @@ -15,6 +16,7 @@ import ( "oss.terrastruct.com/d2/d2themes" "oss.terrastruct.com/d2/lib/color" "oss.terrastruct.com/d2/lib/geo" + "oss.terrastruct.com/d2/lib/label" ) func Export(ctx context.Context, g *d2graph.Graph, fontFamily *d2fonts.FontFamily) (*d2target.Diagram, error) { @@ -335,7 +337,14 @@ func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection if edge.Tooltip != nil { connection.Tooltip = edge.Tooltip.Value } - connection.Icon = edge.Icon + if edge.Icon != nil { + connection.Icon = edge.Icon + if edge.IconPosition != nil { + connection.IconPosition = (d2ast.LabelPositionsMapping[edge.IconPosition.Value]).String() + } else { + connection.IconPosition = label.InsideMiddleCenter.String() + } + } if edge.Style.Italic != nil { connection.Italic, _ = strconv.ParseBool(edge.Style.Italic.Value) diff --git a/d2ir/compile.go b/d2ir/compile.go index 156f7a41c..60a35cf38 100644 --- a/d2ir/compile.go +++ b/d2ir/compile.go @@ -1070,11 +1070,6 @@ func (c *compiler) compileLink(f *Field, refctx *RefContext) { return } - if linkIDA[0].ScalarString() == "root" && linkIDA[0].IsUnquoted() { - c.errorf(refctx.Key.Key, "cannot refer to root in link") - return - } - if !linkIDA[0].IsUnquoted() { return } diff --git a/d2oracle/edit_test.go b/d2oracle/edit_test.go index 543440411..0c51d26da 100644 --- a/d2oracle/edit_test.go +++ b/d2oracle/edit_test.go @@ -6115,6 +6115,28 @@ y exp: `y a -> b c -> d +`, + }, + { + name: "underscore_linked", + text: `k + +layers: { + x: { + a + b: {link: _} + } +} +`, + key: `b`, + boardPath: []string{"x"}, + exp: `k + +layers: { + x: { + a + } +} `, }, { diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index f4ece3b24..153b7b345 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -587,16 +587,51 @@ func drawConnection(writer io.Writer, diagramHash string, connection d2target.Co markerEnd = fmt.Sprintf(`marker-end="url(#%s)" `, id) } + if connection.Icon != nil { + iconPos := connection.GetIconPosition() + if iconPos != nil { + fmt.Fprintf(writer, ``, + html.EscapeString(connection.Icon.String()), + iconPos.X, + iconPos.Y, + d2target.DEFAULT_ICON_SIZE, + d2target.DEFAULT_ICON_SIZE, + ) + } + } + var labelTL *geo.Point if connection.Label != "" { labelTL = connection.GetLabelTopLeft() labelTL.X = math.Round(labelTL.X) labelTL.Y = math.Round(labelTL.Y) + maskTL := labelTL.Copy() + width := connection.LabelWidth + height := connection.LabelHeight + + if connection.Icon != nil { + width += d2target.CONNECTION_ICON_LABEL_GAP + d2target.DEFAULT_ICON_SIZE + maskTL.X -= float64(d2target.CONNECTION_ICON_LABEL_GAP + d2target.DEFAULT_ICON_SIZE) + } + if label.FromString(connection.LabelPosition).IsOnEdge() { - labelMask = makeLabelMask(labelTL, connection.LabelWidth, connection.LabelHeight, 1) + labelMask = makeLabelMask(maskTL, width, height, 1) } else { - labelMask = makeLabelMask(labelTL, connection.LabelWidth, connection.LabelHeight, 0.75) + labelMask = makeLabelMask(maskTL, width, height, 0.75) + } + } else if connection.Icon != nil { + iconPos := connection.GetIconPosition() + if iconPos != nil { + maskTL := &geo.Point{ + X: iconPos.X, + Y: iconPos.Y, + } + if label.FromString(connection.IconPosition).IsOnEdge() { + labelMask = makeLabelMask(maskTL, d2target.DEFAULT_ICON_SIZE, d2target.DEFAULT_ICON_SIZE, 1) + } else { + labelMask = makeLabelMask(maskTL, d2target.DEFAULT_ICON_SIZE, d2target.DEFAULT_ICON_SIZE, 0.75) + } } } diff --git a/d2target/d2target.go b/d2target/d2target.go index 54c1f2bb4..266e0f184 100644 --- a/d2target/d2target.go +++ b/d2target/d2target.go @@ -34,6 +34,8 @@ const ( MIN_ARROWHEAD_STROKE_WIDTH = 2 ARROWHEAD_PADDING = 2. + + CONNECTION_ICON_LABEL_GAP = 8 ) var BorderOffset = geo.NewVector(5, 5) @@ -609,13 +611,40 @@ type Connection struct { Route []*geo.Point `json:"route"` IsCurve bool `json:"isCurve,omitempty"` - Animated bool `json:"animated"` - Tooltip string `json:"tooltip"` - Icon *url.URL `json:"icon"` + Animated bool `json:"animated"` + Tooltip string `json:"tooltip"` + Icon *url.URL `json:"icon"` + IconPosition string `json:"iconPosition,omitempty"` ZIndex int `json:"zIndex"` } +func (c *Connection) GetIconPosition() *geo.Point { + if c.Icon == nil { + return nil + } + + if c.Label != "" { + labelTL := c.GetLabelTopLeft() + if labelTL != nil { + // Position icon to the left of the label with a small gap + return &geo.Point{ + X: labelTL.X - CONNECTION_ICON_LABEL_GAP - DEFAULT_ICON_SIZE, + Y: labelTL.Y + float64(c.LabelHeight)/2 - DEFAULT_ICON_SIZE/2, + } + } + } + + point, _ := label.FromString(c.IconPosition).GetPointOnRoute( + c.Route, + float64(c.StrokeWidth), + -1, + float64(DEFAULT_ICON_SIZE), + float64(DEFAULT_ICON_SIZE), + ) + return point +} + func BaseConnection() *Connection { return &Connection{ SrcArrow: NoArrowhead, diff --git a/docs/assets/playground_button.png b/docs/assets/playground_button.png index b08fe380d..a0cd9a212 100644 Binary files a/docs/assets/playground_button.png and b/docs/assets/playground_button.png differ diff --git a/docs/assets/studio_button.png b/docs/assets/studio_button.png new file mode 100644 index 000000000..12e65821a Binary files /dev/null and b/docs/assets/studio_button.png differ diff --git a/docs/examples/wcc/wcc.d2 b/docs/examples/wcc/wcc.d2 index f47593792..689f18b5b 100644 --- a/docs/examples/wcc/wcc.d2 +++ b/docs/examples/wcc/wcc.d2 @@ -34,7 +34,7 @@ layers: { link: steps.1 style.font-size: 24 } - + steps: { 1: { titled: Earn pre-requisite titles (IM) @@ -150,7 +150,7 @@ layers: { best of 14 games -> tiebreaks: if needed tiebreaks.link: layers.tiebreaks - + layers: { tiebreaks: { description: |md diff --git a/e2etests/testdata/txtar/connection-icons/dagre/board.exp.json b/e2etests/testdata/txtar/connection-icons/dagre/board.exp.json new file mode 100644 index 000000000..96c74348f --- /dev/null +++ b/e2etests/testdata/txtar/connection-icons/dagre/board.exp.json @@ -0,0 +1,306 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "a", + "type": "rectangle", + "pos": { + "x": 0, + "y": 0 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "b", + "type": "rectangle", + "pos": { + "x": 186, + "y": 0 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "b", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "c", + "type": "rectangle", + "pos": { + "x": 339, + "y": 0 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "c", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(a -> b)[0]", + "src": "a", + "srcArrow": "none", + "dst": "b", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "hello", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 33, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 52.5, + "y": 33 + }, + { + "x": 106.0999984741211, + "y": 33 + }, + { + "x": 132.89999389648438, + "y": 33 + }, + { + "x": 186.5, + "y": 33 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0 + }, + { + "id": "(b -> c)[0]", + "src": "b", + "srcArrow": "none", + "dst": "c", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 239, + "y": 33 + }, + { + "x": 279, + "y": 33 + }, + { + "x": 299, + "y": 33 + }, + { + "x": 339, + "y": 33 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0 + } + ], + "root": { + "id": "", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 0, + "height": 0, + "opacity": 0, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "N7", + "stroke": "", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 0 + } +} diff --git a/e2etests/testdata/txtar/connection-icons/dagre/sketch.exp.svg b/e2etests/testdata/txtar/connection-icons/dagre/sketch.exp.svg new file mode 100644 index 000000000..5e302b9c2 --- /dev/null +++ b/e2etests/testdata/txtar/connection-icons/dagre/sketch.exp.svg @@ -0,0 +1,106 @@ +abc hello + + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/txtar/connection-icons/elk/board.exp.json b/e2etests/testdata/txtar/connection-icons/elk/board.exp.json new file mode 100644 index 000000000..18f1b8346 --- /dev/null +++ b/e2etests/testdata/txtar/connection-icons/elk/board.exp.json @@ -0,0 +1,288 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "a", + "type": "rectangle", + "pos": { + "x": 12, + "y": 12 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "b", + "type": "rectangle", + "pos": { + "x": 238, + "y": 12 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "b", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "c", + "type": "rectangle", + "pos": { + "x": 361, + "y": 12 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "c", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(a -> b)[0]", + "src": "a", + "srcArrow": "none", + "dst": "b", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "hello", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 33, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 65, + "y": 45 + }, + { + "x": 238, + "y": 45 + } + ], + "animated": false, + "tooltip": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0 + }, + { + "id": "(b -> c)[0]", + "src": "b", + "srcArrow": "none", + "dst": "c", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 291, + "y": 45 + }, + { + "x": 361, + "y": 45 + } + ], + "animated": false, + "tooltip": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0 + } + ], + "root": { + "id": "", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 0, + "height": 0, + "opacity": 0, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "N7", + "stroke": "", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 0 + } +} diff --git a/e2etests/testdata/txtar/connection-icons/elk/sketch.exp.svg b/e2etests/testdata/txtar/connection-icons/elk/sketch.exp.svg new file mode 100644 index 000000000..be28cc05d --- /dev/null +++ b/e2etests/testdata/txtar/connection-icons/elk/sketch.exp.svg @@ -0,0 +1,106 @@ +abc hello + + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/txtar/sql-casing-panic/dagre/board.exp.json b/e2etests/testdata/txtar/sql-casing-panic/dagre/board.exp.json new file mode 100644 index 000000000..4832c3200 --- /dev/null +++ b/e2etests/testdata/txtar/sql-casing-panic/dagre/board.exp.json @@ -0,0 +1,130 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "asdf", + "type": "sql_table", + "pos": { + "x": 0, + "y": 0 + }, + "width": 87, + "height": 72, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N1", + "stroke": "N7", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "zxcv", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 37, + "labelHeight": 26 + }, + "type": { + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0 + }, + "constraint": null, + "reference": "" + } + ], + "label": "asdf", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 46, + "labelHeight": 31, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "B2", + "secondaryAccentColor": "AA2", + "neutralAccentColor": "N2" + } + ], + "connections": [], + "root": { + "id": "", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 0, + "height": 0, + "opacity": 0, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "N7", + "stroke": "", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 0 + } +} diff --git a/e2etests/testdata/txtar/sql-casing-panic/dagre/sketch.exp.svg b/e2etests/testdata/txtar/sql-casing-panic/dagre/sketch.exp.svg new file mode 100644 index 000000000..b0fe23859 --- /dev/null +++ b/e2etests/testdata/txtar/sql-casing-panic/dagre/sketch.exp.svg @@ -0,0 +1,95 @@ +asdfzxcv + + + \ No newline at end of file diff --git a/e2etests/testdata/txtar/sql-casing-panic/elk/board.exp.json b/e2etests/testdata/txtar/sql-casing-panic/elk/board.exp.json new file mode 100644 index 000000000..ea369be7b --- /dev/null +++ b/e2etests/testdata/txtar/sql-casing-panic/elk/board.exp.json @@ -0,0 +1,130 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "asdf", + "type": "sql_table", + "pos": { + "x": 12, + "y": 12 + }, + "width": 87, + "height": 72, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N1", + "stroke": "N7", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "zxcv", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 37, + "labelHeight": 26 + }, + "type": { + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0 + }, + "constraint": null, + "reference": "" + } + ], + "label": "asdf", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 46, + "labelHeight": 31, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "B2", + "secondaryAccentColor": "AA2", + "neutralAccentColor": "N2" + } + ], + "connections": [], + "root": { + "id": "", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 0, + "height": 0, + "opacity": 0, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "N7", + "stroke": "", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 0 + } +} diff --git a/e2etests/testdata/txtar/sql-casing-panic/elk/sketch.exp.svg b/e2etests/testdata/txtar/sql-casing-panic/elk/sketch.exp.svg new file mode 100644 index 000000000..0b3e91c06 --- /dev/null +++ b/e2etests/testdata/txtar/sql-casing-panic/elk/sketch.exp.svg @@ -0,0 +1,95 @@ +asdfzxcv + + + \ No newline at end of file diff --git a/e2etests/txtar.txt b/e2etests/txtar.txt index 46e7e9dfe..fcc6a7619 100644 --- a/e2etests/txtar.txt +++ b/e2etests/txtar.txt @@ -759,3 +759,19 @@ b: { a.b -> b.c b.c -> a.a: {style.font-color: red; style.stroke: red; style.fill: mistyrose} + +-- sql-casing-panic -- + +asdf:{ + shape:sQl_table + zxcv +} + +-- connection-icons -- +direction: right +a -> b: hello { + icon: https://icons.terrastruct.com/essentials%2F213-alarm.svg +} +b -> c: { + icon: https://icons.terrastruct.com/essentials%2F213-alarm.svg +} diff --git a/go.mod b/go.mod index c1229c23d..2fa9a3620 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( golang.org/x/tools v0.25.0 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da gonum.org/v1/plot v0.14.0 - oss.terrastruct.com/util-go v0.0.0-20241005222610-44c011a04896 + oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a ) require ( diff --git a/go.sum b/go.sum index 1cb060641..3aeb231d1 100644 --- a/go.sum +++ b/go.sum @@ -205,7 +205,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -oss.terrastruct.com/util-go v0.0.0-20241005222610-44c011a04896 h1:g752s1ECv9FD8GunFOZRGWzjeR0cr/TZdSsjAnFEmL8= -oss.terrastruct.com/util-go v0.0.0-20241005222610-44c011a04896/go.mod h1:eMWv0sOtD9T2RUl90DLWfuShZCYp4NrsqNpI8eqO6U4= +oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a h1:UXF/Z9i9tOx/wqGUOn/T12wZeez1Gg0sAVKKl7YUDwM= +oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a/go.mod h1:eMWv0sOtD9T2RUl90DLWfuShZCYp4NrsqNpI8eqO6U4= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/testdata/d2compiler/TestCompile2/vars/basic/double-border.exp.json b/testdata/d2compiler/TestCompile2/vars/basic/double-border.exp.json index 8b7f30d5d..c2ef1281b 100644 --- a/testdata/d2compiler/TestCompile2/vars/basic/double-border.exp.json +++ b/testdata/d2compiler/TestCompile2/vars/basic/double-border.exp.json @@ -219,7 +219,7 @@ }, "near_key": null, "shape": { - "value": "Circle" + "value": "circle" }, "direction": { "value": "" diff --git a/testdata/d2oracle/TestDelete/underscore_linked.exp.json b/testdata/d2oracle/TestDelete/underscore_linked.exp.json new file mode 100644 index 000000000..5ff160e38 --- /dev/null +++ b/testdata/d2oracle/TestDelete/underscore_linked.exp.json @@ -0,0 +1,292 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-7:0:32", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1", + "key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1", + "value": [ + { + "string": "k", + "raw_string": "k" + } + ] + } + } + ] + }, + "primary": {}, + "value": {} + } + }, + { + "map_key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,2:0:3-6:1:31", + "key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,2:0:3-2:6:9", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,2:0:3-2:6:9", + "value": [ + { + "string": "layers", + "raw_string": "layers" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,2:8:11-6:1:31", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,3:2:15-5:3:29", + "key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,3:2:15-3:3:16", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,3:2:15-3:3:16", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,3:5:18-5:3:29", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25", + "key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + } + ] + }, + "primary": {}, + "value": {} + } + } + ] + } + } + } + } + ] + } + } + } + } + ] + }, + "root": { + "id": "", + "id_val": "", + "attributes": { + "label": { + "value": "" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + "edges": null, + "objects": [ + { + "id": "k", + "id_val": "k", + "references": [ + { + "key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1", + "value": [ + { + "string": "k", + "raw_string": "k" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "k" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ], + "layers": [ + { + "name": "x", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-1:0:0", + "nodes": [ + { + "map_key": { + "range": ",0:0:0-0:0:0", + "key": { + "range": ",0:0:0-0:0:0", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + } + ] + }, + "primary": {}, + "value": {} + } + } + ] + }, + "root": { + "id": "", + "id_val": "", + "attributes": { + "label": { + "value": "" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + "edges": null, + "objects": [ + { + "id": "a", + "id_val": "a", + "references": [ + { + "key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "a" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ] + } + ] + }, + "err": "" +}