Skip to content

Commit a6106bb

Browse files
adding banner
1 parent 5bcf7ae commit a6106bb

File tree

6 files changed

+305
-92
lines changed

6 files changed

+305
-92
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package react
2+
3+
import (
4+
"github.com/gopherjs/gopherjs/js"
5+
)
6+
7+
// Banner creates the top banner React element.
8+
func Banner(
9+
version string,
10+
onRunClick func(),
11+
onFormatClick func(fmtImports bool),
12+
shareUrl string,
13+
onShareClick func(),
14+
) *Element {
15+
return CreateElement(bannerComponent, Props{
16+
`version`: version,
17+
`onRunClick`: onRunClick,
18+
`onFormatClick`: onFormatClick,
19+
`shareUrl`: shareUrl,
20+
`onShareClick`: onShareClick,
21+
})
22+
}
23+
24+
func bannerComponent(props Props) *Element {
25+
version := As[string](props, `version`)
26+
onRun := AsFunc(props, `onRunClick`)
27+
onFormat := AsFunc(props, `onFormatClick`)
28+
shareUrl := As[string](props, `shareUrl`)
29+
onShare := AsFunc(props, `onShareClick`)
30+
fmtImports, setFmtImports := UseState(true)
31+
32+
return Div(Props{
33+
`id`: `banner`,
34+
},
35+
Span(Props{
36+
`id`: `banner-title`,
37+
},
38+
`playground `,
39+
Span(Props{
40+
`id`: `banner-subtitle`,
41+
},
42+
`GopherJS (`, version, `)`,
43+
),
44+
),
45+
Span(Props{
46+
`id`: `controls`,
47+
},
48+
Button(`run-button`, `Run`, nil, func() { onRun() }),
49+
Button(`format-button`, `Format`, nil, func() { onFormat(fmtImports) }),
50+
fmtImportsControl(fmtImports, setFmtImports),
51+
shareUrlControl(shareUrl, onShare),
52+
),
53+
)
54+
}
55+
56+
func fmtImportsControl(fmtImports bool, setFmtImports func(any)) *Element {
57+
return CreateElement(fmtImportsComponent, Props{
58+
`fmtImports`: fmtImports,
59+
`setFmtImports`: setFmtImports,
60+
})
61+
}
62+
63+
func fmtImportsComponent(props Props) *Element {
64+
fmtImports := As[bool](props, `fmtImports`)
65+
setFmtImports := AsSetter(props, `setFmtImports`)
66+
67+
onChange := func(e *js.Object) {
68+
setFmtImports(e.Get(`target`).Get(`checked`).Bool())
69+
}
70+
71+
return CreateElement(`label`, Props{
72+
`id`: `format-imports-label`,
73+
`title`: `Rewrite imports on Format`,
74+
},
75+
CreateElement(`input`, Props{
76+
`id`: `format-imports`,
77+
`type`: `checkbox`,
78+
`checked`: fmtImports,
79+
`onChange`: onChange,
80+
}),
81+
`Imports`,
82+
)
83+
}
84+
85+
func shareUrlControl(shareUrl string, onShare Func) *Element {
86+
return CreateElement(shareUrlComponent, Props{
87+
`shareUrl`: shareUrl,
88+
`onShare`: onShare,
89+
})
90+
}
91+
92+
func shareUrlComponent(props Props) *Element {
93+
shareUrl := As[string](props, `shareUrl`)
94+
onShare := AsFunc(props, `onShare`)
95+
shareUrlRef := UseRef()
96+
97+
onShareUrlFocus := func(e *js.Object) {
98+
e.Get(`target`).Call(`select`)
99+
}
100+
101+
UseEffect(func() {
102+
if len(shareUrl) > 0 {
103+
shareUrlRef.Call(`focus`)
104+
}
105+
}, []any{shareUrl, shareUrlRef})
106+
107+
className := `share-url-hidden`
108+
if len(shareUrl) > 0 {
109+
className = `share-url-show`
110+
}
111+
112+
return Fragment(
113+
Button(`share-button`, `Share`, nil, func() { onShare() }),
114+
CreateElement(`input`, Props{
115+
`id`: `share-url`,
116+
`type`: `text`,
117+
`className`: className,
118+
`ref`: shareUrlRef,
119+
`value`: shareUrl,
120+
`readOnly`: true,
121+
`onFocus`: onShareUrlFocus,
122+
}),
123+
)
124+
}

playground/internal/react/bindings.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -128,21 +128,24 @@ func Div(props Props, children ...Node) *Element {
128128
return CreateElement(`div`, props, children...)
129129
}
130130

131-
func Span(props Props, children ...Node) *Element {
132-
return CreateElement(`span`, props, children...)
131+
func Pre(props Props, children ...Node) *Element {
132+
return CreateElement(`pre`, props, children...)
133133
}
134134

135-
func SmallSpan(props Props, children ...Node) *Element {
136-
props = props.Affirm()
137-
props[`style`] = `font-size: small`
138-
return Span(props, children...)
135+
func TextArea(props Props, children ...Node) *Element {
136+
return CreateElement(`textarea`, props, children...)
137+
}
138+
139+
func Span(props Props, children ...Node) *Element {
140+
return CreateElement(`span`, props, children...)
139141
}
140142

141143
// Button creates a button input element with the given value, properties,
142144
// and onClick handler. The onClick handler is added to the props as the `onClick`
143145
// property and is called when the button is clicked.
144-
func Button(value string, props Props, onClick func(e *js.Object)) *Element {
146+
func Button(id, value string, props Props, onClick func()) *Element {
145147
props = props.Affirm()
148+
props[`id`] = id
146149
props[`value`] = value
147150
props[`type`] = `button`
148151
props[`onClick`] = onClick

playground/internal/react/codeBox.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ func codeBoxComponent(props Props) *Element {
2929
return Div(Props{
3030
`id`: `code-box`,
3131
},
32-
CreateElement(`textarea`, Props{
32+
TextArea(Props{
3333
`id`: `line-nums`,
3434
`ref`: cba.lineNumsRef,
3535
`value`: cba.getLineNumbers(),
3636
`readOnly`: true,
3737
`disable`: `true`,
3838
}),
39-
CreateElement(`textarea`, Props{
39+
TextArea(Props{
4040
`id`: `code`,
4141
`ref`: cba.textAreaRef,
4242
`value`: cba.curCode,

playground/internal/react/outputBox.go

Lines changed: 78 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,42 @@ import (
55
"go/scanner"
66
)
77

8-
func ClearOutput(outputUpdater func(any)) {
9-
outputUpdater(func(items []any) []any {
10-
return []any{}
11-
})
8+
const (
9+
errType = `err`
10+
textType = `text`
11+
typeKey = `type`
12+
contextKey = `context`
13+
)
14+
15+
// ClearOutput clears all output items from the output box.
16+
func ClearOutput(setOutput func(any)) {
17+
setOutput([]any{})
1218
}
1319

14-
func WriteError(outputUpdater func(any), err error) {
15-
outputUpdater(func(items []any) []any {
20+
// OutputError appends an error item to the output box.
21+
func OutputError(setOutput func(any), err error) {
22+
setOutput(func(items []any) []any {
1623
if list, ok := err.(scanner.ErrorList); ok {
1724
for _, entry := range list {
18-
items = append(items, map[string]any{`type`: `err`, `context`: entry.Error()})
25+
items = append(items, map[string]any{typeKey: errType, contextKey: entry.Error()})
1926
}
2027
return items
2128
}
22-
return append(items, map[string]any{`type`: `err`, `context`: err.Error()})
29+
return append(items, map[string]any{typeKey: errType, contextKey: err.Error()})
2330
})
2431
}
2532

26-
func WriteString(outputUpdater func(any), s string) {
27-
outputUpdater(func(items []any) []any {
33+
// OutputString appends a text item to the output box.
34+
func OutputString(setOutput func(any), s string) {
35+
setOutput(func(items []any) []any {
2836
if maxItem := len(items) - 1; maxItem >= 0 {
2937
lastItem := items[maxItem].(map[string]any)
30-
if lastItem[`type`] == `text` {
31-
lastItem[`context`] = lastItem[`context`].(string) + s
38+
if lastItem[typeKey] == textType {
39+
lastItem[contextKey] = lastItem[contextKey].(string) + s
3240
return items
3341
}
3442
}
35-
return append(items, map[string]any{`type`: `text`, `context`: s})
43+
return append(items, map[string]any{typeKey: textType, contextKey: s})
3644
})
3745
}
3846

@@ -45,25 +53,69 @@ func OutputBox(output []any) *Element {
4553

4654
func outputBoxComponent(props Props) *Element {
4755
output := As[[]any](props, `output`)
56+
outputBoxRef := UseRef()
57+
58+
UseEffect(func() {
59+
// If there are only errors, scroll to the top,
60+
// otherwise scroll to the bottom.
61+
outputBox := outputBoxRef.Current()
62+
scrollTop := 0
63+
if hasNonErrors(output) {
64+
scrollTop = outputBox.Get(`scrollHeight`).Int()
65+
}
66+
outputBox.Set(`scrollTop`, scrollTop)
67+
}, []any{output})
4868

4969
children := make([]Node, 0, len(output))
50-
for _, item := range output {
70+
for i, item := range output {
5171
itemMap := item.(map[string]any)
72+
children = append(children, outputLine(
73+
i, itemMap[typeKey] == errType,
74+
itemMap[contextKey].(string),
75+
))
76+
}
77+
78+
return Div(Props{
79+
`id`: `output-box`,
80+
`ref`: outputBoxRef,
81+
}, children...)
82+
}
5283

53-
classType := `output-text`
54-
if itemMap[`type`] == `err` {
55-
classType = `output-err`
84+
// hasNonErrors determines if any output is not an error,
85+
// otherwise the list is empty or only contains errors.
86+
func hasNonErrors(output []any) bool {
87+
for _, item := range output {
88+
if item.(map[string]any)[typeKey] != errType {
89+
return true
5690
}
57-
id := fmt.Sprintf(`output-item-%d`, len(children))
91+
}
92+
return false
93+
}
94+
95+
// outputLine creates a React element for a single output line.
96+
// The index is used to create a unique ID for the line so it should
97+
// be the line's position in the output list.
98+
func outputLine(index int, isError bool, content string) *Element {
99+
return CreateElement(outputLineComponent, Props{
100+
`index`: index,
101+
`isError`: isError,
102+
`content`: content,
103+
})
104+
}
105+
106+
func outputLineComponent(props Props) *Element {
107+
index := int(As[float64](props, `index`))
108+
isError := As[bool](props, `isError`)
109+
content := As[string](props, `content`)
58110

59-
child := CreateElement(`pre`, Props{
60-
`className`: classType,
61-
`id`: id,
62-
}, itemMap[`context`])
63-
children = append(children, child)
111+
classType := `output-text`
112+
if isError {
113+
classType = `output-err`
64114
}
115+
id := fmt.Sprintf(`output-item-%d`, index)
65116

66-
return Div(Props{
67-
`id`: `output-box`,
68-
}, children...)
117+
return Pre(Props{
118+
`className`: classType,
119+
`id`: id,
120+
}, content)
69121
}

playground/internal/react/playground.go

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,12 @@
11
package react
22

3-
import "fmt"
4-
53
func Playground() *Element {
64
return CreateElement(playgroundComponent, nil)
75
}
86

9-
var initText = func() string {
10-
const lines = 40
11-
init := ""
12-
for i := 1; i <= lines; i++ {
13-
init += fmt.Sprintf("Hello %d\n", i)
14-
}
15-
return init
16-
}()
17-
187
func playgroundComponent() *Element {
19-
code, setCode := UseState(initText)
20-
output, setOutput := UseState([]any{})
21-
22-
UseEffect(func() { // TODO(grantnelson-wf): REMOVE
23-
if len(code)%10 == 0 {
24-
WriteError(setOutput, fmt.Errorf("code changed: %d", len(code)))
25-
} else {
26-
WriteString(setOutput, fmt.Sprintf("Code changed: %d\n", len(code)))
27-
}
28-
}, code)
8+
code, setCode := UseState(``)
9+
output, _ := UseState([]any{})
2910

3011
return Div(Props{
3112
`id`: `playground`,

0 commit comments

Comments
 (0)