Skip to content

Commit be64a43

Browse files
putting react together
1 parent cd47fa0 commit be64a43

9 files changed

Lines changed: 315087 additions & 764 deletions

File tree

playground/internal/angular/banner.go

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

playground/internal/angular/codeBox.go

Lines changed: 0 additions & 61 deletions
This file was deleted.
Lines changed: 73 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package react
22

33
import (
4-
"time"
4+
"fmt"
55

6+
"github.com/gopherjs/gopherjs/js"
67
"honnef.co/go/js/dom"
78

89
"github.com/gopherjs/gopherjs.github.io/playground/internal/common"
9-
"github.com/gopherjs/gopherjs/js"
1010
)
1111

1212
// banner implements playground.Banner using React.
@@ -19,6 +19,7 @@ type banner struct {
1919
shareUrl *State[string]
2020
shareUrlVisible *State[bool]
2121
version *State[string]
22+
shareUrlRef *js.Object
2223

2324
location *dom.Location
2425
}
@@ -30,70 +31,97 @@ func NewBanner() common.Banner {
3031
}
3132
}
3233

34+
var _ Component = (*banner)(nil)
35+
3336
func invokeCallback(c func()) {
3437
if c != nil {
3538
c()
3639
}
3740
}
3841

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+
func (b *banner) onRun(e *js.Object) { invokeCallback(b.onRunCallback) }
43+
func (b *banner) onFormat(e *js.Object) { invokeCallback(b.onFormatCallback) }
44+
func (b *banner) onShare(e *js.Object) { invokeCallback(b.onShareCallback) }
4245

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+
func (b *banner) SetOnRunCallback(callback func()) { b.onRunCallback = callback }
47+
func (b *banner) SetOnFormatCallback(callback func()) { b.onFormatCallback = callback }
48+
func (b *banner) SetOnShareCallback(callback func()) { b.onShareCallback = callback }
4649

47-
func (t *banner) GetUrlHash() string { return t.location.Hash }
48-
func (t *banner) SetUrlHash(hash string) { t.location.Hash = hash }
50+
func (b *banner) GetUrlHash() string { return b.location.Hash }
51+
func (b *banner) SetUrlHash(hash string) { b.location.Hash = hash }
4952

50-
func (t *banner) SetOnHashChange(callback func()) {
53+
func (b *banner) SetOnHashChange(callback func()) {
5154
dom.GetWindow().Top().AddEventListener("hashchange", false, func(event dom.Event) {
5255
event.PreventDefault()
5356
callback()
5457
})
5558
}
5659

57-
func (t *banner) SetImportsCheck(checked bool) { t.importChecked.Set(checked) }
58-
func (t *banner) IsImportsChecked() bool { return t.importChecked.Get() }
60+
func (b *banner) SetImportsCheck(checked bool) { b.importChecked.Set(checked) }
61+
func (b *banner) IsImportsChecked() bool { return b.importChecked.Get() }
5962

60-
func (t *banner) HideShareUrl() {
61-
t.shareUrl.Set(``)
62-
t.shareUrlVisible.Set(false)
63+
func (b *banner) onImportCheckedChange(e *js.Object) {
64+
checked := e.Get(`target`).Get(`checked`).Bool()
65+
b.importChecked.Set(checked)
6366
}
6467

65-
func (t *banner) ShowShareUrl(url string) {
66-
t.shareUrl.Set(url)
67-
t.shareUrlVisible.Set(true)
68+
func (b *banner) onShareUrlFocus(e *js.Object) {
69+
e.Get(`target`).Call(`select`)
70+
}
71+
72+
func (b *banner) HideShareUrl() {
73+
b.shareUrl.Set(``)
74+
b.shareUrlVisible.Set(false)
75+
}
6876

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-
}()
77+
func (b *banner) ShowShareUrl(url string) {
78+
b.shareUrl.Set(url)
79+
b.shareUrlVisible.Set(true)
7580
}
7681

77-
func (t *banner) SetVersion(version string) {
78-
t.version.Set(version)
82+
func (b *banner) SetVersion(version string) {
83+
b.version.Set(version)
7984
}
8085

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
86+
func (b *banner) Render(props Props) *Element {
87+
b.importChecked = UseState(true)
88+
b.shareUrl = UseState(``)
89+
b.shareUrlVisible = UseState(false)
90+
b.version = UseState(``)
91+
b.shareUrlRef = UseRef()
92+
93+
UseEffect(func() {
94+
if b.shareUrlVisible.Get() {
95+
b.shareUrlRef.Call(`select`)
96+
}
97+
}, b.shareUrlVisible, b.shareUrlRef)
98+
99+
return Div(Props{
100+
`id`: `banner`,
101+
}, Span(Props{
102+
`id`: `head`,
103+
}, `playground `,
104+
CreateElement(`small`, nil, `GopherJS (`, b.version.Get(), `)`),
105+
), Span(Props{
106+
`id`: `controls`,
107+
}, Button(`Run`, nil, b.onRun),
108+
Button(`Format`, nil, b.onFormat),
109+
CreateElement(`label`, Props{
110+
`title`: `Rewrite imports on Format`,
111+
}, CreateElement(`input`, Props{
112+
`type`: `checkbox`,
113+
`checked`: b.importChecked.Get(),
114+
`onChange`: b.onImportCheckedChange,
115+
}), `Imports`,
116+
), Button(`Share`, nil, b.onShare),
117+
CreateElement(`input`, Props{
118+
`type`: `text`,
119+
`className`: fmt.Sprintf(`show-share-url-%t`, b.shareUrlVisible.Get()),
120+
`id`: `share-url`,
121+
`ref`: b.shareUrlRef,
122+
`value`: b.shareUrl.Get(),
123+
`readOnly`: true,
124+
`onFocus`: b.onShareUrlFocus,
125+
}),
126+
))
99127
}

playground/internal/react/bindings.go

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
package react
22

3-
import (
4-
"fmt"
5-
6-
"github.com/gopherjs/gopherjs/js"
7-
)
3+
import "github.com/gopherjs/gopherjs/js"
84

95
type (
106
// Node is a React node that can be displayed.
@@ -28,17 +24,14 @@ type (
2824
// See: https://react.dev/reference/react/createElement
2925
Props map[string]any
3026

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-
3627
// State is a value managed by React's useState hook.
3728
// If the component function hasn't been called yet,
3829
// Get returns zero and Set has no effect, otherwise
3930
// it will return the current value and update it respectively.
31+
// NOTE: `State[int]` should be `State[float64]` because of
32+
// how the underlying React hook works.
4033
// See: https://react.dev/reference/react/useState
41-
State[T StateConstraints] struct{ getter, setter *js.Object }
34+
State[T any] struct{ getter, setter *js.Object }
4235

4336
// ComponentFunc is a function that defines a React component.
4437
// It takes props as input and returns a React element.
@@ -73,14 +66,12 @@ func CreateElement(typ any, props Props, children ...Node) *Element {
7366

7467
func checkNode(n Node) Node {
7568
switch c := n.(type) {
76-
case *Element, string, int, float64:
77-
return c
7869
case ComponentFunc:
7970
return CreateElement(c, nil)
8071
case Component:
8172
return CreateElement(c.Render, nil)
8273
default:
83-
panic(fmt.Errorf(`unsupported child type %T`, c))
74+
return c
8475
}
8576
}
8677

@@ -108,7 +99,7 @@ func SmallSpan(props Props, children ...Node) *Element {
10899
return Span(props, children...)
109100
}
110101

111-
func Button(value string, props Props, onClick func()) *Element {
102+
func Button(value string, props Props, onClick func(e *js.Object)) *Element {
112103
if props == nil {
113104
props = Props{}
114105
}
@@ -118,7 +109,7 @@ func Button(value string, props Props, onClick func()) *Element {
118109
return CreateElement(`input`, props)
119110
}
120111

121-
func UseState[T StateConstraints](initial T) *State[T] {
112+
func UseState[T any](initial T) *State[T] {
122113
s := react().Call(`useState`, initial)
123114
return &State[T]{getter: s.Index(0), setter: s.Index(1)}
124115
}
@@ -139,3 +130,7 @@ func (s *State[T]) Set(v T) {
139130
func UseRef() *js.Object {
140131
return react().Call(`useRef`, nil)
141132
}
133+
134+
func UseEffect(effect func(), deps ...any) {
135+
react().Call(`useEffect`, effect, deps)
136+
}

0 commit comments

Comments
 (0)