Skip to content

Commit 2cb171f

Browse files
fixing runner and linking title
1 parent 3d5261c commit 2cb171f

13 files changed

Lines changed: 589 additions & 422 deletions

File tree

playground/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.20
44

55
require (
66
github.com/google/go-cmp v0.5.8
7-
github.com/gopherjs/gopherjs v1.19.0-beta2.0.20260119142308-2cf5f59df243
7+
github.com/gopherjs/gopherjs v1.20.1-0.20260123155143-346e3e9e6759
88
golang.org/x/tools v0.16.0
99
honnef.co/go/js/xhr v0.0.0-20150307031022-00e3346113ae
1010
)

playground/go.sum

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

playground/internal/cmd/precompile/precompile.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ import (
2828
log "github.com/sirupsen/logrus"
2929
)
3030

31+
const (
32+
pkgPath = `pkg/`
33+
pkgExt = `.zip`
34+
35+
jsPkgPath = `github.com/gopherjs/gopherjs/js`
36+
nosyncPkgPath = `github.com/gopherjs/gopherjs/nosync`
37+
playgroundPkgPath = `github.com/gopherjs/gopherjs.github.io/playground`
38+
)
39+
3140
type logLevelFlag struct{ log.Level }
3241

3342
func (l *logLevelFlag) Set(raw string) error { return l.UnmarshalText([]byte(raw)) }
@@ -53,7 +62,7 @@ func run() error {
5362
return fmt.Errorf("failed to enumerate standard library packages")
5463
}
5564
packages = importable(packages)
56-
packages = append(packages, "github.com/gopherjs/gopherjs/js", "github.com/gopherjs/gopherjs/nosync")
65+
packages = append(packages, jsPkgPath, nosyncPkgPath)
5766

5867
for _, path := range packages {
5968
pkg, err := s.XContext().Import(path, ``, 0)
@@ -84,7 +93,7 @@ func run() error {
8493
}
8594

8695
func writePackage(target string, srcs *sources.Sources) (err error) {
87-
path := filepath.Join(target, filepath.FromSlash(srcs.ImportPath)+".a.js")
96+
path := filepath.Join(target, filepath.FromSlash(srcs.ImportPath)+pkgExt)
8897
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
8998
return fmt.Errorf("failed to create precompiled package directory %q: %w", filepath.Dir(path), err)
9099
}
@@ -113,11 +122,11 @@ func writePackage(target string, srcs *sources.Sources) (err error) {
113122
// targetDir returns path to the directory where precompiled packages must be
114123
// stored.
115124
func targetDir(s *build.Session) (string, error) {
116-
pkg, err := s.XContext().Import("github.com/gopherjs/gopherjs.github.io/playground", "", gobuild.FindOnly)
125+
pkg, err := s.XContext().Import(playgroundPkgPath, "", gobuild.FindOnly)
117126
if err != nil {
118127
return "", fmt.Errorf("failed to find playground package directory: %w", err)
119128
}
120-
target := filepath.Join(pkg.Dir, "pkg")
129+
target := filepath.Join(pkg.Dir, pkgPath)
121130
if _, err := os.Stat(target); os.IsNotExist(err) {
122131
return "", fmt.Errorf("target directory %q not found", target)
123132
}

playground/internal/common/output.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,33 @@ package common
33
// Output is the interface describing the functions for GUI output box.
44
// This interface is to separate the playground logic from the actual GUI implementation.
55
type Output interface {
6-
76
// Clear clears all output.
87
Clear()
98

109
// AddError adds an error message to the output.
10+
// This will stop scrolling the output since typically
11+
// this for compiler errors where we want to have the top
12+
// of the error in view.
1113
//
1214
// All text including new lines will be escaped for HTML.
1315
AddError(err error)
1416

15-
// AddOutput adds output text to the output.
17+
// AppendOut appends output text to the output.
18+
// This is typically used like appending new content from os.Stdout.
1619
// This will also scroll the output box to the bottom.
1720
//
18-
// If a form feed ("\0c") is encountered in the output,
21+
// If a form feed ("\x0c") is encountered in the output,
1922
// the output box is cleared before adding the remaining output.
2023
// All text including new lines will be escaped for HTML.
21-
AddOutput(out string)
24+
AppendOut(out string)
25+
26+
// AppendErr appends error text to the output.
27+
// This is similar to AppendOut but for os.Stdout
28+
// so the text will be red and the auto-scrolling will stop.
29+
AppendErr(err string)
30+
31+
// AddSystem appends system text to the output on its own line.
32+
// This is similar to AppendOut but for messages about the
33+
// build and run of GopherJS. The text will be gray.
34+
AddSystem(err string)
2235
}
Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,51 @@
11
package page
22

33
import (
4+
"strings"
5+
46
"github.com/gopherjs/gopherjs/compiler"
57

68
"github.com/gopherjs/gopherjs.github.io/playground/internal/react"
79
)
810

11+
const (
12+
repoUrl = `https://github.com/gopherjs/gopherjs`
13+
version = compiler.Version
14+
)
15+
16+
var tagUrl = func() string {
17+
if index := strings.IndexByte(version, '+'); index >= 0 {
18+
return repoUrl + `/releases/tag/v` + version[:index]
19+
}
20+
return repoUrl
21+
}()
22+
923
func BannerTitle() *react.Element {
1024
return react.CreateElement(bannerTitleComponent, react.Props{})
1125
}
1226

1327
func bannerTitleComponent(props react.Props) *react.Element {
14-
version := react.UseMemo(func() string {
15-
return compiler.Version
16-
}, []any{})
17-
1828
return react.Span(react.Props{
1929
`id`: `banner-title`,
20-
},
21-
`playground `,
30+
}, `playground `,
2231
react.Span(react.Props{
2332
`id`: `banner-title-sub`,
2433
},
25-
// TODO(grantnelson-wf): Make this a link to the gopherjs repo
26-
// https://github.com/gopherjs/gopherjs
27-
`GopherJS `,
34+
react.CreateElement(`a`, react.Props{
35+
`href`: repoUrl,
36+
`target`: `_blank`,
37+
}, `GopherJS`),
38+
` `,
2839
),
2940
react.Span(react.Props{
3041
`id`: `banner-title-version`,
3142
},
32-
// TODO(grantnelson-wf): Make this version a link to release notes
33-
// https://github.com/gopherjs/gopherjs/releases/tag/v1.19.0-beta2
34-
`(`+version+`)`,
43+
`(`,
44+
react.CreateElement(`a`, react.Props{
45+
`href`: tagUrl,
46+
`target`: `_blank`,
47+
}, version),
48+
`)`,
3549
),
3650
)
3751
}

playground/internal/page/globals.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import (
99
"github.com/gopherjs/gopherjs.github.io/playground/internal/undoRedo"
1010
)
1111

12-
const verbose = true
13-
1412
// globals are the global objects used by the react components.
1513
//
1614
// Since passing Go structs through React props will cause problems
@@ -27,7 +25,7 @@ var globals = struct {
2725
}{
2826
UndoRedo: OnceValue(undoRedo.NewStack),
2927
SnippetsStore: OnceValue(snippets.NewLocalStore), // TODO(grantnelson-wf): Switch to snippets.NewStore before deploying
30-
Runner: OnceValue(func() common.Runner { return runner.New(runner.NewFetcher(verbose)) }),
28+
Runner: OnceValue(func() common.Runner { return runner.New(runner.NewFetcher(false)) }),
3129
}
3230

3331
// OnceValue initializes a value on the first call

playground/internal/page/outputBox.go

Lines changed: 91 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@ package page
33
import (
44
"fmt"
55
"go/scanner"
6+
"strings"
7+
8+
"github.com/gopherjs/gopherjs/compiler/errlist"
69

710
"github.com/gopherjs/gopherjs.github.io/playground/internal/common"
811
"github.com/gopherjs/gopherjs.github.io/playground/internal/react"
912
)
1013

1114
const (
12-
errType = `err`
13-
textType = `text`
15+
stderrType = `stderr`
16+
stdoutType = `stdout`
17+
systemType = `system`
1418
typeKey = `type`
15-
contextKey = `context`
19+
contentKey = `content`
1620
)
1721

1822
func Output(setOutput react.Func) common.Output {
@@ -25,29 +29,77 @@ func (o *outputImpl) Clear() {
2529
o.setOutput.Invoke([]any{})
2630
}
2731

32+
func ungroupError(err error) []error {
33+
switch t := err.(type) {
34+
case scanner.ErrorList:
35+
errs := make([]error, len(t))
36+
for i, entry := range t {
37+
errs[i] = entry
38+
}
39+
return errs
40+
case errlist.ErrorList:
41+
return t
42+
default:
43+
return []error{err}
44+
}
45+
}
46+
47+
func appendContent(items []any, itemTyp, content string, startNewLine bool) []any {
48+
// Check for a form feed ("\x0c") to clear the output.
49+
if index := strings.LastIndexByte(content, '\x0c'); index >= 0 {
50+
items = []any{}
51+
content = content[index:]
52+
}
53+
54+
// Attempt to append the content to a prior item if the type matches,
55+
// also prepend a new line to the content if needed and requested.
56+
if maxItem := len(items) - 1; maxItem >= 0 {
57+
lastItem := items[maxItem].(map[string]any)
58+
59+
lastContent := lastItem[contentKey].(string)
60+
if startNewLine && !strings.HasSuffix(lastContent, "\n") {
61+
content = "\n" + content
62+
}
63+
64+
if lastItem[typeKey] == itemTyp {
65+
lastItem[contentKey] = lastContent + content
66+
return items
67+
}
68+
}
69+
70+
// Append new type of content.
71+
return append(items, map[string]any{typeKey: itemTyp, contentKey: content})
72+
}
73+
2874
func (o *outputImpl) AddError(err error) {
2975
o.setOutput.Invoke(func(items []any) []any {
30-
// TODO(grantnelson-wf): Need to move errlist.ErrorList out of internal so that we can split it out here
31-
if list, ok := err.(scanner.ErrorList); ok {
32-
for _, entry := range list {
33-
items = append(items, map[string]any{typeKey: errType, contextKey: entry.Error()})
76+
content := ``
77+
for _, entry := range ungroupError(err) {
78+
text := entry.Error()
79+
content += text
80+
if !strings.HasSuffix(text, "\n") {
81+
content += "\n"
3482
}
35-
return items
3683
}
37-
return append(items, map[string]any{typeKey: errType, contextKey: err.Error()})
84+
return appendContent(items, stderrType, content, true)
3885
})
3986
}
4087

41-
func (o *outputImpl) AddOutput(out string) {
88+
func (o *outputImpl) AppendOut(out string) {
4289
o.setOutput.Invoke(func(items []any) []any {
43-
if maxItem := len(items) - 1; maxItem >= 0 {
44-
lastItem := items[maxItem].(map[string]any)
45-
if lastItem[typeKey] == textType {
46-
lastItem[contextKey] = lastItem[contextKey].(string) + out
47-
return items
48-
}
49-
}
50-
return append(items, map[string]any{typeKey: textType, contextKey: out})
90+
return appendContent(items, stdoutType, out, false)
91+
})
92+
}
93+
94+
func (o *outputImpl) AppendErr(err string) {
95+
o.setOutput.Invoke(func(items []any) []any {
96+
return appendContent(items, stderrType, err, false)
97+
})
98+
}
99+
100+
func (o *outputImpl) AddSystem(sys string) {
101+
o.setOutput.Invoke(func(items []any) []any {
102+
return appendContent(items, systemType, sys+"\n", true)
51103
})
52104
}
53105

@@ -64,24 +116,21 @@ func outputBoxComponent(props react.Props) *react.Element {
64116
outputBoxRef = react.UseRef()
65117
)
66118

119+
// Add effect so that if there are no stderr's in the output, scroll to the bottom.
67120
react.UseEffect(func() {
68-
// If there are only errors, scroll to the top,
69-
// otherwise scroll to the bottom.
70-
outputBox := outputBoxRef.Current()
71-
scrollTop := 0
72-
if hasNonErrors(output) {
73-
scrollTop = outputBox.Get(`scrollHeight`).Int()
121+
if !hasErrors(output) {
122+
outputBox := outputBoxRef.Current()
123+
scrollTop := outputBox.Get(`scrollHeight`).Int()
124+
outputBox.Set(`scrollTop`, scrollTop)
74125
}
75-
outputBox.Set(`scrollTop`, scrollTop)
76126
}, []any{output})
77127

78-
children := make([]react.Node, 0, len(output))
128+
children := make([]react.Node, len(output))
79129
for i, item := range output {
80130
itemMap := item.(map[string]any)
81-
children = append(children, outputLine(
82-
i, itemMap[typeKey] == errType,
83-
itemMap[contextKey].(string),
84-
))
131+
classType := itemMap[typeKey].(string)
132+
content := itemMap[contentKey].(string)
133+
children[i] = outputLine(i, classType, content)
85134
}
86135

87136
return react.Div(react.Props{
@@ -92,42 +141,35 @@ func outputBoxComponent(props react.Props) *react.Element {
92141
}, children...))
93142
}
94143

95-
// hasNonErrors determines if any output is not an error,
96-
// otherwise the list is empty or only contains errors.
97-
func hasNonErrors(output []any) bool {
144+
// hasErrors determines if any output is an error.
145+
func hasErrors(output []any) bool {
98146
for _, item := range output {
99-
if item.(map[string]any)[typeKey] != errType {
147+
if item.(map[string]any)[typeKey] == stderrType {
100148
return true
101149
}
102150
}
103151
return false
104152
}
105153

106-
// outputLine creates a React element for a single output line.
154+
// outputLine creates a React element for a span of output content.
107155
// The index is used to create a unique ID for the line so it should
108156
// be the line's position in the output list.
109-
func outputLine(index int, isError bool, content string) *react.Element {
157+
func outputLine(index int, classType string, content string) *react.Element {
110158
return react.CreateElement(outputLineComponent, react.Props{
111-
`index`: index,
112-
`isError`: isError,
113-
`content`: content,
159+
`index`: index,
160+
`classType`: classType,
161+
`content`: content,
114162
})
115163
}
116164

117165
func outputLineComponent(props react.Props) *react.Element {
118166
var (
119-
index = props.GetInt(`index`)
120-
isError = props.GetBool(`isError`)
121-
content = props.GetString(`content`)
167+
index = props.GetInt(`index`)
168+
classType = props.GetString(`classType`)
169+
content = props.GetString(`content`)
122170
)
123-
124-
classType := `output-text`
125-
if isError {
126-
classType = `output-err`
127-
}
128171
id := fmt.Sprintf(`output-item-%d`, index)
129-
130-
return react.Pre(react.Props{
172+
return react.Span(react.Props{
131173
`className`: classType,
132174
`id`: id,
133175
}, content)

0 commit comments

Comments
 (0)