Skip to content

Commit cd47fa0

Browse files
putting react together
1 parent ef690fb commit cd47fa0

7 files changed

Lines changed: 20442 additions & 4116 deletions

File tree

playground/internal/playground/playground.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ func (p *Playground) onFormat() {
126126
var out []byte
127127
var err error
128128
if p.banner.IsImportsChecked() {
129-
// TODO(grantnelson-wf): See if https://pkg.go.dev/golang.org/x/tools@v0.37.0/imports can be used instead.
129+
// TODO(grantnelson-wf): We should see if https://pkg.go.dev/golang.org/x/tools@v0.37.0/imports can be used instead.
130130
out, err = imports.Process(`prog.go`, code, nil)
131131
} else {
132132
out, err = format.Source(code)
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package react
2+
3+
import (
4+
"time"
5+
6+
"honnef.co/go/js/dom"
7+
8+
"github.com/gopherjs/gopherjs.github.io/playground/internal/common"
9+
"github.com/gopherjs/gopherjs/js"
10+
)
11+
12+
// banner implements playground.Banner using React.
13+
type banner struct {
14+
onRunCallback func()
15+
onFormatCallback func()
16+
onShareCallback func()
17+
18+
importChecked *State[bool]
19+
shareUrl *State[string]
20+
shareUrlVisible *State[bool]
21+
version *State[string]
22+
23+
location *dom.Location
24+
}
25+
26+
func NewBanner() common.Banner {
27+
return &banner{
28+
// We might be inside an iframe, but want to use the location of topmost window.
29+
location: dom.GetWindow().Top().Location(),
30+
}
31+
}
32+
33+
func invokeCallback(c func()) {
34+
if c != nil {
35+
c()
36+
}
37+
}
38+
39+
func (t *banner) onRun(e *js.Object) { invokeCallback(t.onRunCallback) }
40+
func (t *banner) onFormat(e *js.Object) { invokeCallback(t.onFormatCallback) }
41+
func (t *banner) onShare(e *js.Object) { invokeCallback(t.onShareCallback) }
42+
43+
func (t *banner) SetOnRunCallback(callback func()) { t.onRunCallback = callback }
44+
func (t *banner) SetOnFormatCallback(callback func()) { t.onFormatCallback = callback }
45+
func (t *banner) SetOnShareCallback(callback func()) { t.onShareCallback = callback }
46+
47+
func (t *banner) GetUrlHash() string { return t.location.Hash }
48+
func (t *banner) SetUrlHash(hash string) { t.location.Hash = hash }
49+
50+
func (t *banner) SetOnHashChange(callback func()) {
51+
dom.GetWindow().Top().AddEventListener("hashchange", false, func(event dom.Event) {
52+
event.PreventDefault()
53+
callback()
54+
})
55+
}
56+
57+
func (t *banner) SetImportsCheck(checked bool) { t.importChecked.Set(checked) }
58+
func (t *banner) IsImportsChecked() bool { return t.importChecked.Get() }
59+
60+
func (t *banner) HideShareUrl() {
61+
t.shareUrl.Set(``)
62+
t.shareUrlVisible.Set(false)
63+
}
64+
65+
func (t *banner) ShowShareUrl(url string) {
66+
t.shareUrl.Set(url)
67+
t.shareUrlVisible.Set(true)
68+
69+
// TODO(dmitshur): Do this better using AngularJS.
70+
// Perhaps using http://stackoverflow.com/questions/14833326/how-to-set-focus-on-input-field/18295416.
71+
go func() {
72+
time.Sleep(time.Millisecond)
73+
dom.GetWindow().Document().GetElementByID(`share-url`).(*dom.HTMLInputElement).Select()
74+
}()
75+
}
76+
77+
func (t *banner) SetVersion(version string) {
78+
t.version.Set(version)
79+
}
80+
81+
func (t *banner) Render(props Props) *Element {
82+
return Div(Props{`id`: `banner`},
83+
Span(Props{`id`: `head`}, `playground <small>(GopherJS `, t.version.Get(), `)</small>`)),
84+
85+
//<div id="banner">
86+
// <span id="head">playground <small>(GopherJS {{version}})</small></span>
87+
// <span id="controls">
88+
// <input type="button" value="Run" ng-click="run(false)" />
89+
// <input type="button" value="Format" ng-click="format()" />
90+
// <label title="Rewrite imports on Format">
91+
// <input ng-model="imports" type="checkbox" />Imports
92+
// </label>
93+
// <input type="button" value="Share" ng-click="share()" />
94+
// <input type="text" class="show-share-url-{{showShareUrl}}" id="share-url" value="{{shareUrl}}" onfocus="select()" />
95+
// </span>
96+
//</div>
97+
98+
return nil
99+
}
Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package react
22

33
import (
4+
"fmt"
5+
46
"github.com/gopherjs/gopherjs/js"
57
)
68

@@ -26,13 +28,27 @@ type (
2628
// See: https://react.dev/reference/react/createElement
2729
Props map[string]any
2830

29-
StateMap map[string]any
30-
31-
State[T any] struct{ getter, setter *js.Object }
32-
33-
// Component is a React component used when rendering with state.
34-
// See: https://react.dev/reference/react/Component
35-
Component struct{ *js.Object }
31+
// StateConstraints is a type constraint for types that can be used with State.
32+
// This is help provide compile-time type safety for State values
33+
// since things like `int` will be represented as `float64` in a React hook.
34+
StateConstraints interface{ string | float64 | bool }
35+
36+
// State is a value managed by React's useState hook.
37+
// If the component function hasn't been called yet,
38+
// Get returns zero and Set has no effect, otherwise
39+
// it will return the current value and update it respectively.
40+
// See: https://react.dev/reference/react/useState
41+
State[T StateConstraints] struct{ getter, setter *js.Object }
42+
43+
// ComponentFunc is a function that defines a React component.
44+
// It takes props as input and returns a React element.
45+
// See: https://react.dev/learn
46+
ComponentFunc func(props Props) *Element
47+
48+
// Component is not really an actual React component.
49+
// It is an interface that exposes a Render methods with the same signature
50+
// as a ComponentFunc so that it can be used simular to a React component.
51+
Component interface{ Render(props Props) *Element }
3652
)
3753

3854
func reactDom() *js.Object { return js.Global.Get(`ReactDOM`) }
@@ -56,13 +72,16 @@ func CreateElement(typ any, props Props, children ...Node) *Element {
5672
}
5773

5874
func checkNode(n Node) Node {
59-
//switch c := n.(type) {
60-
//case *Element, string, int, float64:
61-
// return c
62-
//default:
63-
// panic(fmt.Errorf(`unsupported child type %T`, c))
64-
//}
65-
return n
75+
switch c := n.(type) {
76+
case *Element, string, int, float64:
77+
return c
78+
case ComponentFunc:
79+
return CreateElement(c, nil)
80+
case Component:
81+
return CreateElement(c.Render, nil)
82+
default:
83+
panic(fmt.Errorf(`unsupported child type %T`, c))
84+
}
6685
}
6786

6887
func StrictMode(children ...Node) *Element {
@@ -81,6 +100,14 @@ func Span(props Props, children ...Node) *Element {
81100
return CreateElement(`span`, props, children...)
82101
}
83102

103+
func SmallSpan(props Props, children ...Node) *Element {
104+
if props == nil {
105+
props = Props{}
106+
}
107+
props[`style`] = `font-size: small`
108+
return Span(props, children...)
109+
}
110+
84111
func Button(value string, props Props, onClick func()) *Element {
85112
if props == nil {
86113
props = Props{}
@@ -91,17 +118,16 @@ func Button(value string, props Props, onClick func()) *Element {
91118
return CreateElement(`input`, props)
92119
}
93120

94-
func UseState[T string | float64](initial T) *State[T] {
121+
func UseState[T StateConstraints](initial T) *State[T] {
95122
s := react().Call(`useState`, initial)
96123
return &State[T]{getter: s.Index(0), setter: s.Index(1)}
97124
}
98125

99-
func (s *State[T]) Get() T {
100-
if s == nil || s.getter == nil {
101-
var zero T
102-
return zero
126+
func (s *State[T]) Get() (zero T) {
127+
if s != nil && s.getter != nil {
128+
return s.getter.Interface().(T)
103129
}
104-
return s.getter.Interface().(T)
130+
return
105131
}
106132

107133
func (s *State[T]) Set(v T) {
@@ -110,29 +136,6 @@ func (s *State[T]) Set(v T) {
110136
}
111137
}
112138

113-
func NewComponent(props Props, init StateMap, render func(*Component) Node) *Element {
114-
reactComp := react().Get("Component")
115-
ctor := js.MakeFunc(func(this *js.Object, args []*js.Object) any {
116-
reactComp.Call(`call`, props)
117-
this.Set(`state`, init)
118-
//if constructor != nil {
119-
// js.Global.Get(`Reflect`).Call(`apply`, constructor, this, []any{})
120-
//}
121-
return nil
122-
})
123-
rf := js.MakeFunc(func(this *js.Object, args []*js.Object) any {
124-
return render(&Component{Object: this})
125-
})
126-
ctor.Set(`prototype`, reactComp.Get(`prototype`))
127-
ctor.Get(`prototype`).Set(`constructor`, ctor)
128-
ctor.Get("prototype").Set("render", rf)
129-
return &Element{Object: react().Call(`createElement`, ctor)}
130-
}
131-
132-
func (c *Component) GetState(key string) *js.Object {
133-
return c.Get("state").Get(key)
134-
}
135-
136-
func (c *Component) SetState(newState StateMap) {
137-
c.Call("setState", newState)
139+
func UseRef() *js.Object {
140+
return react().Call(`useRef`, nil)
138141
}

playground/internal/react/elements.go

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

playground/internal/react/page.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package react
2+
3+
import "github.com/gopherjs/gopherjs.github.io/playground/internal/common"
4+
5+
func SetupReactPage() (common.Banner, common.CodeBox, common.OutputBox) {
6+
7+
banner := NewBanner()
8+
9+
page := func(props Props) *Element {
10+
11+
return StrictMode(
12+
banner.Render,
13+
)
14+
15+
// <div class="box" id="content">
16+
//
17+
// <div class="box yellow" id="input">
18+
// <textarea ng-model="code" id="code" autocorrect="off" autocomplete="off" autocapitalize="off" spellcheck="false"></textarea>
19+
// </div>
20+
//
21+
// </div>
22+
//
23+
// <div class="box" id="output">
24+
//
25+
// <pre ng-repeat="line in output" class="{{line.type}}">{{line.content}}&nbsp;</pre>
26+
//
27+
// </div>
28+
}
29+
30+
root := CreateRoot(`root`)
31+
root.Render(page)
32+
return banner, nil, nil
33+
}

playground/playground.go

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,16 @@
11
package main
22

33
import (
4+
"github.com/gopherjs/gopherjs.github.io/playground/internal/playground"
45
"github.com/gopherjs/gopherjs.github.io/playground/internal/react"
6+
"github.com/gopherjs/gopherjs.github.io/playground/internal/runner"
7+
"github.com/gopherjs/gopherjs.github.io/playground/internal/snippets"
58
)
69

710
func main() {
8-
//if false {
9-
// app := angularjs.NewModule("playground", nil, nil)
10-
// app.NewController("PlaygroundCtrl", func(scope *angularjs.Scope) {
11-
// banner := angular.NewBanner(scope)
12-
// codeBox := angular.NewCodeBox(scope)
13-
// output := angular.NewOutputBox(scope)
14-
// snippetsStore := snippets.NewStore()
15-
// fetcher := runner.NewFetcher()
16-
// runner := runner.New(output, fetcher)
17-
// playground.New(banner, codeBox, output, snippetsStore, runner)
18-
// })
19-
//}
20-
21-
root := react.CreateRoot(`root`)
22-
//root.Render(react.CreateElement(`div`, nil, `Hello World!`))
23-
24-
//root.Render(react.NewCounter())
25-
26-
root.Render(react.NewCounter(`grant`, 48))
11+
banner, codeBox, output := react.SetupReactPage()
12+
snippetsStore := snippets.NewStore()
13+
fetcher := runner.NewFetcher()
14+
runner := runner.New(output, fetcher)
15+
playground.New(banner, codeBox, output, snippetsStore, runner)
2716
}

0 commit comments

Comments
 (0)