Skip to content

Commit 67e4cdc

Browse files
connecting it up
1 parent dd7e3f8 commit 67e4cdc

File tree

13 files changed

+76912
-427
lines changed

13 files changed

+76912
-427
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package common
22

3-
// OutputBox is the interface describing the functions for GUI output box.
3+
// 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.
5-
type OutputBox interface {
5+
type Output interface {
66

77
// Clear clears all output.
88
Clear()

playground/internal/common/snippetStore.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ package common
77
// with a mock implementation for testing.
88
type SnippetStore interface {
99

10+
// TODO(grantnelson-wf): Update to work better with predefined snippets.
11+
//
1012
// Read fetches the code snippet with the given hash (including the `#/`)
1113
// from the snippet store.
14+
// If the has is `#` without the `/`, the remainder is checked to see
15+
// if it matches any pre-defined snippets, i.e. `#Hello`.
1216
// If the hash is empty or invalid, it returns the default code.
1317
// This will block while waiting for the network request to complete.
1418
Read(hash string) (string, error)

playground/internal/react/bindings.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ func UseEffectNoDeps(effect func()) {
208208
// If no dependency are provided, the effect is only run once after the initial render.
209209
// See: https://react.dev/reference/react/useEffect
210210
func UseEffect(effect func(), deps ...any) {
211+
if len(deps) == 0 {
212+
deps = []any{}
213+
}
211214
react().Call(`useEffect`, effect, deps)
212215
}
213216

@@ -226,5 +229,8 @@ func UseEffectNoDepsWithCleanup(effect func() func()) {
226229
// is re-run or when the component is unmounted.
227230
// See: https://react.dev/reference/react/useEffect
228231
func UseEffectWithCleanup(effect func() func(), deps ...any) {
232+
if len(deps) == 0 {
233+
deps = []any{}
234+
}
229235
react().Call(`useEffect`, effect, deps)
230236
}

playground/internal/react/formatImportsControl.go

Lines changed: 0 additions & 36 deletions
This file was deleted.

playground/internal/react/outputBox.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package react
33
import (
44
"fmt"
55
"go/scanner"
6+
7+
"github.com/gopherjs/gopherjs.github.io/playground/internal/common"
68
)
79

810
const (
@@ -12,14 +14,18 @@ const (
1214
contextKey = `context`
1315
)
1416

15-
// ClearOutput clears all output items from the output box.
16-
func ClearOutput(setOutput func(any)) {
17-
setOutput([]any{})
17+
func Output(setOutput func(any)) common.Output {
18+
return &outputImpl{setOutput: setOutput}
19+
}
20+
21+
type outputImpl struct{ setOutput func(any) }
22+
23+
func (o *outputImpl) Clear() {
24+
o.setOutput([]any{})
1825
}
1926

20-
// OutputError appends an error item to the output box.
21-
func OutputError(setOutput func(any), err error) {
22-
setOutput(func(items []any) []any {
27+
func (o *outputImpl) AddError(err error) {
28+
o.setOutput(func(items []any) []any {
2329
if list, ok := err.(scanner.ErrorList); ok {
2430
for _, entry := range list {
2531
items = append(items, map[string]any{typeKey: errType, contextKey: entry.Error()})
@@ -30,17 +36,16 @@ func OutputError(setOutput func(any), err error) {
3036
})
3137
}
3238

33-
// OutputString appends a text item to the output box.
34-
func OutputString(setOutput func(any), s string) {
35-
setOutput(func(items []any) []any {
39+
func (o *outputImpl) AddOutput(out string) {
40+
o.setOutput(func(items []any) []any {
3641
if maxItem := len(items) - 1; maxItem >= 0 {
3742
lastItem := items[maxItem].(map[string]any)
3843
if lastItem[typeKey] == textType {
39-
lastItem[contextKey] = lastItem[contextKey].(string) + s
44+
lastItem[contextKey] = lastItem[contextKey].(string) + out
4045
return items
4146
}
4247
}
43-
return append(items, map[string]any{typeKey: textType, contextKey: s})
48+
return append(items, map[string]any{typeKey: textType, contextKey: out})
4449
})
4550
}
4651

Lines changed: 113 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,129 @@
11
package react
22

3+
import (
4+
"github.com/gopherjs/gopherjs/js"
5+
6+
"github.com/gopherjs/gopherjs.github.io/playground/internal/common"
7+
"github.com/gopherjs/gopherjs.github.io/playground/internal/snippets"
8+
)
9+
310
func Playground() *Element {
4-
return CreateElement(playgroundComponent, nil)
5-
}
11+
version := "vx.x.x" // TODO(grantnelson-wf): compiler.Version()
12+
preferslightTheme := getPrefersLightTheme()
13+
snippetsStore := snippets.NewStore()
614

7-
func playgroundComponent() *Element {
8-
code, setCode := UseState(``)
9-
shareUrl, setShareUrl := UseState(``)
10-
output, setOutput := UseState([]any{})
11-
fmtImports, setFmtImports := UseState(true)
15+
return CreateElement(func() *Element {
16+
code, setCode := UseState(``)
17+
shareUrl, setShareUrl := UseState(``)
18+
output, setOutput := UseState([]any{})
19+
fmtImports, setFmtImports := UseState(true)
20+
lightTheme, setLightTheme := UseState(preferslightTheme)
1221

13-
_ = setShareUrl
14-
_ = setOutput
22+
pa := &playgroundAssistant{
23+
snippetStore: snippetsStore,
24+
code: code,
25+
setCode: setCode,
26+
setShareUrl: setShareUrl,
27+
setOutput: setOutput,
28+
fmtImports: fmtImports,
29+
}
1530

16-
version := `vx.x.x`
31+
UseEffect(func() {
32+
setDataTheme(lightTheme)
33+
}, lightTheme)
1734

18-
onRunClick := func() {
19-
print("Run clicked") // TODO(grantnelson-wf): Implement
20-
}
35+
UseEffect(func() {
36+
// code changed so clear share URL
37+
setShareUrl(``)
38+
getLocation().Set(`hash`, ``)
39+
println("Code changed, cleared share URL") // TODO(grantnelson-wf): Remove debug
40+
}, code)
2141

22-
onFormatClick := func() {
23-
print("Format clicked", fmtImports) // TODO(grantnelson-wf): Implement
24-
}
42+
UseEffect(pa.initCode)
2543

26-
onShareClick := func() {
27-
print("Share clicked") // TODO(grantnelson-wf): Implement
28-
}
44+
/* TODO(grantnelson-wf): Implement hashchange loading
45+
dom.GetWindow().Top().AddEventListener("hashchange", false, func(event dom.Event) {
46+
event.PreventDefault()
47+
callback()
48+
})
49+
*/
2950

30-
return Fragment(
31-
Div(Props{
32-
`id`: `banner`,
33-
},
34-
BannerTitle(version),
35-
Span(Props{
36-
`id`: `controls`,
51+
return Fragment(
52+
Div(Props{
53+
`id`: `banner`,
3754
},
38-
Button(`run-button`, `Run`, nil, onRunClick),
39-
Button(`format-button`, `Format`, nil, onFormatClick),
40-
FormatImportsControl(fmtImports, setFmtImports),
41-
ShareUrlControl(shareUrl, onShareClick),
55+
BannerTitle(version),
56+
Span(Props{
57+
`id`: `controls`,
58+
},
59+
Button(`run-button`, `Run`, nil, pa.onRunClick),
60+
Button(`format-button`, `Format`, nil, pa.onFormatClick),
61+
ToggleBox(`format-imports`, `Rewrite imports on Format`, `Imports`, fmtImports, setFmtImports),
62+
ShareUrlControl(shareUrl, pa.onShareClick),
63+
// TODO(grantnelson-wf): Snippet selection control.
64+
ToggleBox(`color-theme`, `Change color-theme`, ``, lightTheme, setLightTheme),
65+
),
4266
),
43-
),
44-
Div(Props{
45-
`id`: `code-output-box`,
46-
},
4767
Div(Props{
48-
`id`: `code-box-container`,
68+
`id`: `code-output-box`,
4969
},
50-
CodeBox(code, setCode),
70+
Div(Props{
71+
`id`: `code-box-container`,
72+
},
73+
CodeBox(code, setCode),
74+
),
75+
OutputBox(output),
5176
),
52-
OutputBox(output),
53-
),
54-
)
77+
)
78+
}, nil)
79+
}
80+
81+
func getPrefersLightTheme() bool {
82+
return js.Global.Get(`window`).Call(`matchMedia`, `(prefers-color-scheme: light)`).Get(`matches`).Bool()
83+
}
84+
85+
func setDataTheme(lightTheme bool) {
86+
theme := `dark`
87+
if lightTheme {
88+
theme = `light`
89+
}
90+
js.Global.Get(`document`).Get(`documentElement`).Call(`setAttribute`, `data-theme`, theme)
91+
}
92+
93+
func getLocation() *js.Object {
94+
return js.Global.Get(`window`).Get(`top`).Get(`location`)
95+
}
96+
97+
type playgroundAssistant struct {
98+
snippetStore common.SnippetStore
99+
code string
100+
setCode func(any)
101+
setShareUrl func(any)
102+
setOutput func(any)
103+
fmtImports bool
104+
}
105+
106+
func (pa *playgroundAssistant) initCode() {
107+
// TODO(grantnelson-wf): Update
108+
hash := getLocation().Get(`hash`).String()
109+
code, err := pa.snippetStore.Read(hash)
110+
if err != nil {
111+
o := Output(pa.setOutput)
112+
o.Clear()
113+
o.AddError(err)
114+
}
115+
// even on error, set the code so the default code is shown.
116+
pa.setCode(code)
117+
}
118+
119+
func (pa *playgroundAssistant) onRunClick() {
120+
println("Run clicked") // TODO(grantnelson-wf): Implement
121+
}
122+
123+
func (pa *playgroundAssistant) onFormatClick() {
124+
println("Format clicked", pa.fmtImports) // TODO(grantnelson-wf): Implement
125+
}
126+
127+
func (pa *playgroundAssistant) onShareClick() {
128+
println("Share URL Clicked") // TODO(grantnelson-wf): Implement
55129
}

playground/internal/react/toggleBox.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,21 @@ func toggleBoxComponent(props Props) *Element {
2424
}
2525

2626
return Div(Props{
27-
`id`: id,
28-
`class`: `toggle-box`,
29-
`title`: title,
27+
`id`: id,
28+
`className`: `toggle-box-wrapper`,
29+
`title`: title,
3030
},
3131
CreateElement(`input`, Props{
32-
`id`: id + `-checkbox`,
33-
`class`: `toggle-box-checkbox`,
34-
`type`: `checkbox`,
35-
`checked`: checked,
36-
`onChange`: onChange,
32+
`id`: id + `-checkbox`,
33+
`className`: `toggle-box-checkbox`,
34+
`type`: `checkbox`,
35+
`checked`: checked,
36+
`onChange`: onChange,
3737
}),
3838
CreateElement(`label`, Props{
39-
`id`: id + `-toggle`,
40-
`class`: `toggle-box-toggle`,
41-
`htmlFor`: id + `-checkbox`,
39+
`id`: id + `-toggle`,
40+
`className`: `toggle-box-toggle`,
41+
`htmlFor`: id + `-checkbox`,
4242
}),
4343
label,
4444
)

playground/internal/runner/packageCache.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ const (
1616
)
1717

1818
type packageCache struct {
19-
output common.OutputBox
19+
output common.Output
2020
fetcher common.Fetcher
2121
cached map[string]*sources.Sources
2222
inprogress map[string]chan struct{}
2323
lock sync.Mutex
2424
}
2525

26-
func newPackageCache(output common.OutputBox, fetcher common.Fetcher) *packageCache {
26+
func newPackageCache(output common.Output, fetcher common.Fetcher) *packageCache {
2727
return &packageCache{
2828
output: output,
2929
fetcher: fetcher,

playground/internal/runner/runner.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ const pseudoFileName = `prog.go`
2222
type runner struct {
2323
cache *packageCache
2424

25-
output common.OutputBox
25+
output common.Output
2626
}
2727

28-
func New(output common.OutputBox, fetcher common.Fetcher) common.Runner {
28+
func New(output common.Output, fetcher common.Fetcher) common.Runner {
2929
return &runner{
3030
cache: newPackageCache(output, fetcher),
3131
output: output,

playground/internal/snippets/snippets.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import _ "embed"
88
//go:embed default.go.txt
99
var DefaultCode string
1010

11-
// Predefined contains the pre-defined code snippets that are available
11+
// predefined contains the pre-defined code snippets that are available
1212
// in the playground, keyed by the display name.
1313
//
1414
// TODO(grantnelson-wf): Add more pre-defined snippets.
15-
var Predefined = map[string]string{
15+
var predefined = map[string]string{
1616
`Hello`: DefaultCode,
1717
}

0 commit comments

Comments
 (0)