diff --git a/dev/cmd/shenzhen-go/main.go b/dev/cmd/shenzhen-go/main.go index b8bec65..14989c9 100644 --- a/dev/cmd/shenzhen-go/main.go +++ b/dev/cmd/shenzhen-go/main.go @@ -20,6 +20,7 @@ import ( "fmt" "io/ioutil" "log" + "net" "net/http" "os" "os/exec" @@ -27,6 +28,7 @@ import ( "time" "github.com/improbable-eng/grpc-web/go/grpcweb" + "github.com/zserge/webview" "google.golang.org/grpc" pb "github.com/google/shenzhen-go/dev/proto/go" @@ -36,9 +38,12 @@ import ( const pingMsg = "Pong!" -var uiAddr = flag.String("ui_addr", "localhost:8088", "Address to bind UI server to") +var ( + uiAddr = flag.String("ui_addr", "localhost:0", "Address to bind UI server to") + useDefaultBrowser = flag.Bool("use_browser", true, "Load in the system's default web browser instead of the inbuilt webview") +) -func open(url string) error { +func systemOpen(url string) error { switch runtime.GOOS { case "darwin": return exec.Command("open", url).Run() @@ -53,6 +58,10 @@ func open(url string) error { } } +func webviewOpen(url string) error { + return webview.Open("SHENZHEN GO", url, 1152, 720, true) +} + func isUp(base string) bool { resp, err := http.Get(base + "ping") if err != nil { @@ -66,15 +75,28 @@ func isUp(base string) bool { return string(msg) == pingMsg } -func openWhenUp(addr string) { +func openWhenUp(addr net.Addr, useBrowser bool) { base := fmt.Sprintf(`http://%s/`, addr) - t := time.NewTicker(100 * time.Millisecond) - defer t.Stop() - for range t.C { - if isUp(base) { - break + try := time.NewTicker(100 * time.Millisecond) + defer try.Stop() + timeout := time.NewTimer(5 * time.Second) + defer timeout.Stop() +checkLoop: + for { + select { + case <-try.C: + if isUp(base) { + break checkLoop + } + case <-timeout.C: + fmt.Fprintf(os.Stderr, "Couldn't find server after 5 seconds, giving up") + os.Exit(1) } } + open := systemOpen + if !useBrowser { + open = webviewOpen + } if err := open(base); err != nil { fmt.Fprintf(os.Stderr, "Couldn't automatically open: %v\n", err) fmt.Printf("Ready to open %s\n", base) @@ -96,11 +118,26 @@ func main() { // Finally, all unknown paths are assumed to be files. http.Handle("/", server.S) + ln, err := net.Listen("tcp", *uiAddr) + if err != nil { + log.Fatalf("net.Listen failed: %v", err) + } + defer ln.Close() + + wait := make(chan struct{}) + go func() { + if err := http.Serve(ln, nil); err != nil { + log.Fatalf("http.Serve failed: %v", err) + } + close(wait) + }() + // As soon as we're serving, launch "open" which should launch a browser, // or ask the user to do so. - go openWhenUp(*uiAddr) + // This must be called from the main thread to avoid + // https://github.com/zserge/webview/issues/29. + openWhenUp(ln.Addr(), *useDefaultBrowser) - if err := http.ListenAndServe(*uiAddr, nil); err != nil { - log.Fatal(err) - } + // Job done. + <-wait } diff --git a/dev/server/view/css/main.css b/dev/server/view/css/main.css index f1cfe0e..4164f34 100644 --- a/dev/server/view/css/main.css +++ b/dev/server/view/css/main.css @@ -195,13 +195,13 @@ svg#diagram g.textbox text::selection { } svg#diagram g.node g.textbox rect { - fill: #eff; - stroke: #577; + fill: #eff; + stroke: #577; stroke-width: 1; } svg#diagram g.node.selected g.textbox rect { - fill: #cef; + fill: #cef; stroke-width: 2; } diff --git a/dev/server/view/static-css.go b/dev/server/view/static-css.go index c817866..1fdfc8b 100644 --- a/dev/server/view/static-css.go +++ b/dev/server/view/static-css.go @@ -4,5 +4,5 @@ package view var cssResources = map[string][]byte{ "css/fonts.css": []byte("@font-face {\n\tfont-family: 'Go';\n\tsrc: url('/.static/fonts/GoMedium-Italic.ttf') format('truetype');\n\tfont-weight: 500;\n\tfont-style: italic;\n}\n\n@font-face {\n\tfont-family: 'Go';\n\tsrc: url('/.static/fonts/Go-Italic.ttf') format('truetype');\n\tfont-weight: normal;\n\tfont-style: italic;\n}\n\n@font-face {\n\tfont-family: 'Go';\n\tsrc: url('/.static/fonts/Go-Bold.ttf') format('truetype');\n\tfont-weight: bold;\n\tfont-style: normal;\n}\n\n@font-face {\n\tfont-family: 'Go';\n\tsrc: url('/.static/fonts/GoMedium.ttf') format('truetype');\n\tfont-weight: 500;\n\tfont-style: normal;\n}\n\n@font-face {\n\tfont-family: 'Go';\n\tsrc: url('/.static/fonts/Go-BoldItalic.ttf') format('truetype');\n\tfont-weight: bold;\n\tfont-style: italic;\n}\n\n@font-face {\n\tfont-family: 'Go';\n\tsrc: url('/.static/fonts/GoRegular.ttf') format('truetype');\n\tfont-weight: normal;\n\tfont-style: normal;\n}\n\n@font-face {\n\tfont-family: 'Go Mono';\n\tsrc: url('/.static/fonts/GoMono-Bold.ttf') format('truetype');\n\tfont-weight: bold;\n\tfont-style: normal;\n}\n\n@font-face {\n\tfont-family: 'Go Mono';\n\tsrc: url('/.static/fonts/GoMono.ttf') format('truetype');\n\tfont-weight: normal;\n\tfont-style: normal;\n}\n\n@font-face {\n\tfont-family: 'Go Mono';\n\tsrc: url('/.static/fonts/GoMono-Italic.ttf') format('truetype');\n\tfont-weight: normal;\n\tfont-style: italic;\n}\n\n@font-face {\n\tfont-family: 'Go Mono';\n\tsrc: url('/.static/fonts/GoMono-BoldItalic.ttf') format('truetype');\n\tfont-weight: bold;\n\tfont-style: italic;\n}"), - "css/main.css": []byte("body {\n\tfont-family: Go,'San Francisco','Helvetica Neue',Helvetica,Arial,sans-serif;\n\tfloat: none;\n\tmargin: 0;\n\tdisplay: flex;\n\tflex-flow: column;\n\theight: 100%;\n\tmax-height: 100%;\n}\n\na:link, a:visited {\n\tcolor: #05d;\n\ttext-decoration: none;\n}\n\na:hover {\n\tcolor: #07f;\n\ttext-decoration: underline;\n}\n\na.destructive:link, a.destructive:visited {\n color: #d03;\n}\n\na.destructive:hover {\n color: #f04;\n}\n\ncode {\n\tfont-family: 'Go Mono','Fira Code',Menlo,sans-serif;\n\tcolor: #066;\n}\n\nform {\n\tfloat: none;\n\tmax-width: 800px;\n\tmargin: 0 auto;\n}\n\ndiv.formfield {\n\tmargin-top: 12px;\n\tmargin-bottom: 12px;\n}\n\ntable {\n\ttable-layout: fixed;\n\tmargin-top: 12px;\n\tmargin-bottom: 12px;\n}\n\ndiv.formfield label {\n\tfloat: left;\n\ttext-align: right;\n\tmargin-right: 15px;\n\twidth: 30%;\n}\n\ninput {\n\tfont-family: 'Go Mono','Fira Code',Menlo,sans-serif;\n\tfont-size: 12pt;\n}\n\ndiv.formfield input[type=text] {\n\twidth: 65%;\n}\n\ndiv.browse-container {\n\tmargin: 0 auto 8px;\n\tmin-width: 800px;\n}\n\nselect {\n\tfont-family: Go,'San Francisco','Helvetica Neue',Helvetica,Arial,sans-serif;\n\tfont-size: 12pt;\n}\n\ntextarea {\n\tfont-family: 'Go Mono','Fira Code',Menlo,sans-serif;\n\tfont-size: 12pt;\n}\n\ndiv.head {\n\tpadding: 6px;\n\tflex: 0 1 auto;\n\tborder-bottom-style: solid;\n\tborder-bottom-color: #aaa;\n\tborder-bottom-width: 1px;\n}\n\ndiv.box {\n\tdisplay: flex;\n\tflex-flow: row;\n\tflex: 0 1 auto;\n}\ndiv.container {\n\tflex: 1 1 50%;\n}\n\ndiv#diagram-container {\n\toverflow: scroll;\n}\n\ndiv#panels-container {\n\tpadding: 6px;\n\tdisplay: flex;\n\tflex-flow: column;\n}\n\ndiv#node-properties {\n\tdisplay: flex;\n\tflex-flow: column;\n\tflex: auto;\n}\n\ndiv.node-panel {\n\tdisplay: flex;\n\tflex-flow: column;\n\tflex: auto;\n}\n\ndiv.hcentre {\n\ttext-align: center;\n}\n\ntable.browse {\n\tfont-family: 'Go Mono','Fira Code',Menlo,sans-serif;\n\tfont-size: 12pt;\n\tmargin-top: 16pt;\n}\n\nfieldset {\n\tmargin: 4px;\n}\n\nfieldset#pathtemplate {\n\tdisplay: none;\n}\n\n.dropdown {\n position: relative;\n display: inline-block;\n}\n\n.dropdown-content {\n display: none;\n position: absolute;\n background-color: #fff;\n box-shadow: 0px 6px 12px 0px rgba(0,0,0,0.2);\n padding: 4px 4px;\n z-index: 1;\n}\n\n.dropdown:hover .dropdown-content {\n display: block;\n}\n\n.dropdown-content ul {\n\tlist-style-type: none;\n \tmargin: 0;\n \tpadding: 0;\n \toverflow: hidden;\n}\n\n.codeedit {\n\tfont-family: 'Go Mono','Fira Code',Menlo,sans-serif;\n\tfont-size: 14px;\n\tflex: auto;\n}\n\nsvg#diagram {\n\tbackground: #f8f8ff;\n}\n\nsvg#diagram .draggable {\n\tcursor: grab;\n}\n\nsvg#diagram .draggable.dragging {\n\tcursor: grabbing;\n}\n\nsvg#diagram g.textbox text {\n\tfont-family: Go; \n\tfont-size: 16; \n\tuser-select: none; \n\tpointer-events: none;\n\n\talignment-baseline: middle;\n\tdominant-baseline: middle;\n\ttext-anchor: middle;\n}\n\nsvg#diagram g.textbox text::selection {\n background: none;\n}\n\nsvg#diagram g.node g.textbox rect {\n\tfill: #eff; \n\tstroke: #577; \n\tstroke-width: 1;\n}\n\nsvg#diagram g.node.selected g.textbox rect {\n\tfill: #cef; \n\tstroke-width: 2;\n}\n\nsvg#diagram g.node g.pin g.textbox rect {\n\tfill: #efe; \n\tstroke: #575; \n\tstroke-width: 1;\n}\n"), + "css/main.css": []byte("body {\n\tfont-family: Go,'San Francisco','Helvetica Neue',Helvetica,Arial,sans-serif;\n\tfloat: none;\n\tmargin: 0;\n\tdisplay: flex;\n\tflex-flow: column;\n\theight: 100%;\n\tmax-height: 100%;\n}\n\na:link, a:visited {\n\tcolor: #05d;\n\ttext-decoration: none;\n}\n\na:hover {\n\tcolor: #07f;\n\ttext-decoration: underline;\n}\n\na.destructive:link, a.destructive:visited {\n color: #d03;\n}\n\na.destructive:hover {\n color: #f04;\n}\n\ncode {\n\tfont-family: 'Go Mono','Fira Code',Menlo,sans-serif;\n\tcolor: #066;\n}\n\nform {\n\tfloat: none;\n\tmax-width: 800px;\n\tmargin: 0 auto;\n}\n\ndiv.formfield {\n\tmargin-top: 12px;\n\tmargin-bottom: 12px;\n}\n\ntable {\n\ttable-layout: fixed;\n\tmargin-top: 12px;\n\tmargin-bottom: 12px;\n}\n\ndiv.formfield label {\n\tfloat: left;\n\ttext-align: right;\n\tmargin-right: 15px;\n\twidth: 30%;\n}\n\ninput {\n\tfont-family: 'Go Mono','Fira Code',Menlo,sans-serif;\n\tfont-size: 12pt;\n}\n\ndiv.formfield input[type=text] {\n\twidth: 65%;\n}\n\ndiv.browse-container {\n\tmargin: 0 auto 8px;\n\tmin-width: 800px;\n}\n\nselect {\n\tfont-family: Go,'San Francisco','Helvetica Neue',Helvetica,Arial,sans-serif;\n\tfont-size: 12pt;\n}\n\ntextarea {\n\tfont-family: 'Go Mono','Fira Code',Menlo,sans-serif;\n\tfont-size: 12pt;\n}\n\ndiv.head {\n\tpadding: 6px;\n\tflex: 0 1 auto;\n\tborder-bottom-style: solid;\n\tborder-bottom-color: #aaa;\n\tborder-bottom-width: 1px;\n}\n\ndiv.box {\n\tdisplay: flex;\n\tflex-flow: row;\n\tflex: 0 1 auto;\n}\ndiv.container {\n\tflex: 1 1 50%;\n}\n\ndiv#diagram-container {\n\toverflow: scroll;\n}\n\ndiv#panels-container {\n\tpadding: 6px;\n\tdisplay: flex;\n\tflex-flow: column;\n}\n\ndiv#node-properties {\n\tdisplay: flex;\n\tflex-flow: column;\n\tflex: auto;\n}\n\ndiv.node-panel {\n\tdisplay: flex;\n\tflex-flow: column;\n\tflex: auto;\n}\n\ndiv.hcentre {\n\ttext-align: center;\n}\n\ntable.browse {\n\tfont-family: 'Go Mono','Fira Code',Menlo,sans-serif;\n\tfont-size: 12pt;\n\tmargin-top: 16pt;\n}\n\nfieldset {\n\tmargin: 4px;\n}\n\nfieldset#pathtemplate {\n\tdisplay: none;\n}\n\n.dropdown {\n position: relative;\n display: inline-block;\n}\n\n.dropdown-content {\n display: none;\n position: absolute;\n background-color: #fff;\n box-shadow: 0px 6px 12px 0px rgba(0,0,0,0.2);\n padding: 4px 4px;\n z-index: 1;\n}\n\n.dropdown:hover .dropdown-content {\n display: block;\n}\n\n.dropdown-content ul {\n\tlist-style-type: none;\n \tmargin: 0;\n \tpadding: 0;\n \toverflow: hidden;\n}\n\n.codeedit {\n\tfont-family: 'Go Mono','Fira Code',Menlo,sans-serif;\n\tfont-size: 14px;\n\tflex: auto;\n}\n\nsvg#diagram {\n\tbackground: #f8f8ff;\n}\n\nsvg#diagram .draggable {\n\tcursor: grab;\n}\n\nsvg#diagram .draggable.dragging {\n\tcursor: grabbing;\n}\n\nsvg#diagram g.textbox text {\n\tfont-family: Go; \n\tfont-size: 16; \n\tuser-select: none; \n\tpointer-events: none;\n\n\talignment-baseline: middle;\n\tdominant-baseline: middle;\n\ttext-anchor: middle;\n}\n\nsvg#diagram g.textbox text::selection {\n background: none;\n}\n\nsvg#diagram g.node g.textbox rect {\n\tfill: #eff;\n\tstroke: #577;\n\tstroke-width: 1;\n}\n\nsvg#diagram g.node.selected g.textbox rect {\n\tfill: #cef;\n\tstroke-width: 2;\n}\n\nsvg#diagram g.node g.pin g.textbox rect {\n\tfill: #efe; \n\tstroke: #575; \n\tstroke-width: 1;\n}\n"), } diff --git a/dev/server/view/static-templates.go b/dev/server/view/static-templates.go index 58e58d8..8144fb3 100644 --- a/dev/server/view/static-templates.go +++ b/dev/server/view/static-templates.go @@ -4,5 +4,5 @@ package view var templateResources = map[string][]byte{ "templates/browse.html": []byte("\n\tSHENZHEN GO\n\t\n\t\n\n\n\t
\n\t\t

SHENZHEN GO

\n\t\t

{{$.Base}}

\n\t\tUp |\n\t\t
\n\t\t\tNew\n\t\t\t
\n\t\t\t\t\n\t\t\t\tCreate\n\t\t\t
\n\t\t
\n\t\t\n\t\t\t{{range $.Entries -}}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{{- end}}\n\t\t
{{if .IsDir}}<dir>{{end}}{{.Name}}
\n\t
\n"), - "templates/graph.html": []byte("\n\n\t{{$.Graph.Name}}\n\t\n\t\n\t\n\t\n\n\n\t
\n\t\tUp |\n\t\tSave | \n\t\tRevert |\n\t\t{{if $.Graph.IsCommand -}}\n\t\tInstall | \n\t\t{{else -}}\n\t\tBuild | \n\t\t{{end -}}\n\t\tRun | \n\t\t\n\t\t\tNew goroutine \n\t\t\t
\n\t\t\t\t
    \n\t\t\t\t{{range $t, $null := $.PartTypes -}}\n\t\t\t\t\t
  • {{$t}}
  • \n\t\t\t\t{{- end}}\n\t\t\t\t
\n\t\t\t
\n\t\t
|\n\t\tView as: Go JSON\n\t
\n\t
\n\t\t
\n\t\t\t\n\t\t\t\n\t\t
\n\t\t
\n\t\t\t
\n\t\t\t\t

Graph Properties

\n\t\t\t\tSave\n\t\t\t\t
\n\t\t\t\t \n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t \n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t \n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\tSave |\n\t\t\t\t\tClone | \n\t\t\t\t\tConvert to Code |\n\t\t\t\t\tDelete | \n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\tProperties \n\t\t\t\t\t{{range $tk, $type := $.PartTypes}}\n\t\t\t\t\t\n\t\t\t\t\t{{range $type.Panels }}\n\t\t\t\t\t| {{.Name}}\n\t\t\t\t\t{{end}}\n\t\t\t\t\t\n\t\t\t\t\t{{end}}\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t{{range $tk, $type := $.PartTypes}}\n\t\t\t\t{{range $type.Panels}}\n\t\t\t\t
\n\t\t\t\t\t{{.Editor}}\n\t\t\t\t
\n\t\t\t\t{{end}}\n\t\t\t\t{{end}}\n\t\t\t
\n\t\t
\n\t
\n\t\n\n"), + "templates/graph.html": []byte("\n\n\t{{$.Graph.Name}}\n\t\n\t\n\t\n\t\n\n\n\t
\n\t\tUp |\n\t\tSave | \n\t\tRevert |\n\t\t{{if $.Graph.IsCommand -}}\n\t\tInstall | \n\t\t{{else -}}\n\t\tBuild | \n\t\t{{end -}}\n\t\tRun | \n\t\t\n\t\t\tNew goroutine \n\t\t\t
\n\t\t\t\t
    \n\t\t\t\t{{range $t, $null := $.PartTypes -}}\n\t\t\t\t\t
  • {{$t}}
  • \n\t\t\t\t{{- end}}\n\t\t\t\t
\n\t\t\t
\n\t\t
|\n\t\tView as: Go JSON\n\t
\n\t
\n\t\t
\n\t\t\t\n\t\t\t\n\t\t
\n\t\t
\n\t\t\t
\n\t\t\t\t

Graph Properties

\n\t\t\t\tSave\n\t\t\t\t
\n\t\t\t\t \n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t \n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t \n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\tSave |\n\t\t\t\t\tClone | \n\t\t\t\t\tConvert to Code |\n\t\t\t\t\tDelete | \n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\tProperties \n\t\t\t\t\t{{range $tk, $type := $.PartTypes}}\n\t\t\t\t\t\n\t\t\t\t\t{{range $type.Panels }}\n\t\t\t\t\t| {{.Name}}\n\t\t\t\t\t{{end}}\n\t\t\t\t\t\n\t\t\t\t\t{{end}}\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\t{{range $tk, $type := $.PartTypes}}\n\t\t\t\t{{range $type.Panels}}\n\t\t\t\t
\n\t\t\t\t\t{{.Editor}}\n\t\t\t\t
\n\t\t\t\t{{end}}\n\t\t\t\t{{end}}\n\t\t\t
\n\t\t
\n\t
\n\t\n\n"), } diff --git a/dev/server/view/templates/graph.html b/dev/server/view/templates/graph.html index 9374e91..51a382b 100644 --- a/dev/server/view/templates/graph.html +++ b/dev/server/view/templates/graph.html @@ -55,6 +55,9 @@

Graph Properties

+