init
This commit is contained in:
parent
d807b8cfad
commit
a541c14e0e
4 changed files with 302 additions and 0 deletions
30
d2js/README.md
Normal file
30
d2js/README.md
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# D2 as a Javascript library
|
||||||
|
|
||||||
|
D2 is runnable as a Javascript library, on both the client and server side. This means you
|
||||||
|
can run D2 entirely on the browser.
|
||||||
|
|
||||||
|
This is achieved by a JS wrapper around a WASM file.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
### NPM
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install @terrastruct/d2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Yarn
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yarn add @terrastruct/d2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
GOOS=js GOARCH=wasm go build -ldflags='-s -w' -trimpath -o main.wasm ./d2js
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
todo
|
||||||
259
d2js/js.go
Normal file
259
d2js/js.go
Normal file
|
|
@ -0,0 +1,259 @@
|
||||||
|
//go:build wasm
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"strings"
|
||||||
|
"syscall/js"
|
||||||
|
|
||||||
|
"oss.terrastruct.com/d2/d2ast"
|
||||||
|
"oss.terrastruct.com/d2/d2compiler"
|
||||||
|
"oss.terrastruct.com/d2/d2format"
|
||||||
|
"oss.terrastruct.com/d2/d2oracle"
|
||||||
|
"oss.terrastruct.com/d2/d2parser"
|
||||||
|
"oss.terrastruct.com/d2/d2target"
|
||||||
|
"oss.terrastruct.com/d2/lib/urlenc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
js.Global().Set("d2GetObjOrder", js.FuncOf(jsGetObjOrder))
|
||||||
|
js.Global().Set("d2GetRefRanges", js.FuncOf(jsGetRefRanges))
|
||||||
|
js.Global().Set("d2Compile", js.FuncOf(jsCompile))
|
||||||
|
js.Global().Set("d2Parse", js.FuncOf(jsParse))
|
||||||
|
js.Global().Set("d2Encode", js.FuncOf(jsEncode))
|
||||||
|
js.Global().Set("d2Decode", js.FuncOf(jsDecode))
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsObjOrder struct {
|
||||||
|
Order []string `json:"order"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsGetObjOrder(this js.Value, args []js.Value) interface{} {
|
||||||
|
dsl := args[0].String()
|
||||||
|
|
||||||
|
g, err := d2compiler.Compile("", strings.NewReader(dsl), &d2compiler.CompileOptions{
|
||||||
|
UTF16: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ret := jsObjOrder{Error: err.Error()}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := jsObjOrder{
|
||||||
|
Order: d2oracle.GetObjOrder(g),
|
||||||
|
}
|
||||||
|
|
||||||
|
str, _ := json.Marshal(resp)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsRefRanges struct {
|
||||||
|
Ranges []d2ast.Range `json:"ranges"`
|
||||||
|
ParseError string `json:"parseError"`
|
||||||
|
UserError string `json:"userError"`
|
||||||
|
D2Error string `json:"d2Error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsGetRefRanges(this js.Value, args []js.Value) interface{} {
|
||||||
|
dsl := args[0].String()
|
||||||
|
key := args[1].String()
|
||||||
|
|
||||||
|
mk, err := d2parser.ParseMapKey(key)
|
||||||
|
if err != nil {
|
||||||
|
ret := jsRefRanges{D2Error: err.Error()}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
g, err := d2compiler.Compile("", strings.NewReader(dsl), &d2compiler.CompileOptions{
|
||||||
|
UTF16: true,
|
||||||
|
})
|
||||||
|
var pe *d2parser.ParseError
|
||||||
|
if err != nil {
|
||||||
|
if errors.As(err, &pe) {
|
||||||
|
serialized, _ := json.Marshal(err)
|
||||||
|
// TODO
|
||||||
|
ret := jsRefRanges{ParseError: string(serialized)}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
ret := jsRefRanges{D2Error: err.Error()}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ranges []d2ast.Range
|
||||||
|
if len(mk.Edges) == 1 {
|
||||||
|
edge := d2oracle.GetEdge(g, key)
|
||||||
|
if edge == nil {
|
||||||
|
ret := jsRefRanges{D2Error: "edge not found"}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ref := range edge.References {
|
||||||
|
ranges = append(ranges, ref.MapKey.Range)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj := d2oracle.GetObj(g, key)
|
||||||
|
if obj == nil {
|
||||||
|
ret := jsRefRanges{D2Error: "obj not found"}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ref := range obj.References {
|
||||||
|
ranges = append(ranges, ref.Key.Range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := jsRefRanges{
|
||||||
|
Ranges: ranges,
|
||||||
|
}
|
||||||
|
|
||||||
|
str, _ := json.Marshal(resp)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsObject struct {
|
||||||
|
Result string `json:"result"`
|
||||||
|
UserError string `json:"userError"`
|
||||||
|
D2Error string `json:"d2Error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsParseResponse struct {
|
||||||
|
DSL string `json:"dsl"`
|
||||||
|
Texts []*d2target.MText `json:"texts"`
|
||||||
|
ParseError string `json:"parseError"`
|
||||||
|
UserError string `json:"userError"`
|
||||||
|
D2Error string `json:"d2Error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type blockFS struct{}
|
||||||
|
|
||||||
|
func (blockFS blockFS) Open(name string) (fs.File, error) {
|
||||||
|
return nil, errors.New("import statements not currently implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsParse(this js.Value, args []js.Value) interface{} {
|
||||||
|
dsl := args[0].String()
|
||||||
|
themeID := args[1].Int()
|
||||||
|
|
||||||
|
g, err := d2compiler.Compile("", strings.NewReader(dsl), &d2compiler.CompileOptions{
|
||||||
|
UTF16: true,
|
||||||
|
FS: blockFS{},
|
||||||
|
})
|
||||||
|
var pe *d2parser.ParseError
|
||||||
|
if err != nil {
|
||||||
|
if errors.As(err, &pe) {
|
||||||
|
serialized, _ := json.Marshal(err)
|
||||||
|
ret := jsParseResponse{ParseError: string(serialized)}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
ret := jsParseResponse{D2Error: err.Error()}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(g.Layers) > 0 || len(g.Scenarios) > 0 || len(g.Steps) > 0 {
|
||||||
|
ret := jsParseResponse{UserError: "layers, scenarios, and steps are not yet supported. Coming soon."}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range g.Objects {
|
||||||
|
if (o.Attributes.Top == nil) != (o.Attributes.Left == nil) {
|
||||||
|
ret := jsParseResponse{UserError: `keywords "top" and "left" currently must be used together`}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = g.ApplyTheme(int64(themeID))
|
||||||
|
if err != nil {
|
||||||
|
ret := jsParseResponse{D2Error: err.Error()}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := jsParseResponse{
|
||||||
|
Texts: g.Texts(),
|
||||||
|
}
|
||||||
|
|
||||||
|
newDSL := d2format.Format(g.AST)
|
||||||
|
if dsl != newDSL {
|
||||||
|
resp.DSL = newDSL
|
||||||
|
}
|
||||||
|
|
||||||
|
str, _ := json.Marshal(resp)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO error passing
|
||||||
|
// TODO recover panics
|
||||||
|
func jsCompile(this js.Value, args []js.Value) interface{} {
|
||||||
|
script := args[0].String()
|
||||||
|
|
||||||
|
g, err := d2compiler.Compile("", strings.NewReader(script), &d2compiler.CompileOptions{
|
||||||
|
UTF16: true,
|
||||||
|
})
|
||||||
|
var pe *d2parser.ParseError
|
||||||
|
if err != nil {
|
||||||
|
if errors.As(err, &pe) {
|
||||||
|
serialized, _ := json.Marshal(err)
|
||||||
|
ret := jsObject{UserError: string(serialized)}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
ret := jsObject{D2Error: err.Error()}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
newScript := d2format.Format(g.AST)
|
||||||
|
if script != newScript {
|
||||||
|
ret := jsObject{Result: newScript}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsEncode(this js.Value, args []js.Value) interface{} {
|
||||||
|
script := args[0].String()
|
||||||
|
|
||||||
|
encoded, err := urlenc.Encode(script)
|
||||||
|
// should never happen
|
||||||
|
if err != nil {
|
||||||
|
ret := jsObject{D2Error: err.Error()}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := jsObject{Result: encoded}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsDecode(this js.Value, args []js.Value) interface{} {
|
||||||
|
script := args[0].String()
|
||||||
|
|
||||||
|
script, err := urlenc.Decode(script)
|
||||||
|
if err != nil {
|
||||||
|
ret := jsObject{UserError: err.Error()}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := jsObject{Result: script}
|
||||||
|
str, _ := json.Marshal(ret)
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
//go:build !wasm
|
||||||
|
|
||||||
package d2latex
|
package d2latex
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
11
d2renderers/d2latex/latex_stub.go
Normal file
11
d2renderers/d2latex/latex_stub.go
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
//go:build wasm
|
||||||
|
|
||||||
|
package d2latex
|
||||||
|
|
||||||
|
func Render(s string) (_ string, err error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Measure(s string) (width, height int, err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue