~repos /gromer
git clone https://pyrossh.dev/repos/gromer.git
gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.
50275872
—
Peter John 4 years ago
improv
- app_common.go +0 -15
- app_nowasm.go +0 -66
- app_wasm.go +0 -82
- attributes.go +11 -46
- component.go +0 -319
- component_test.go +0 -277
- element.go +19 -351
- errors/README.md +0 -17
- errors/errors.go +0 -208
- errors/errors_test.go +0 -153
- example/about.go +0 -19
- example/assets/alpine.js +5 -0
- example/atoms/atoms.go +0 -15
- example/clock.go +0 -51
- example/components/header.go +1 -1
- example/components/page.go +22 -0
- example/container.go +0 -40
- example/index.go +0 -35
- example/main.go +36 -18
- example/pages/about.go +19 -0
- example/pages/index.go +38 -0
- example/panic.go +0 -9
- go.mod +5 -10
- go.sum +0 -95
- html.go +53 -71
- js/js.go +0 -257
- js/js_nowasm.go +0 -143
- js/js_wasm.go +0 -254
- node.go +0 -70
- node_test.go +0 -128
- raw.go +0 -148
- raw_test.go +0 -160
- router.go +142 -22
- router_nowasm.go +0 -116
- storage.go +0 -29
- storage_nowasm.go +0 -39
- storage_test.go +0 -154
- storage_wasm.go +0 -53
- testing.go +0 -289
- testing_test.go +0 -46
- utils.go +0 -66
app_common.go
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
package app
|
|
2
|
-
|
|
3
|
-
var (
|
|
4
|
-
body *Element
|
|
5
|
-
content UI
|
|
6
|
-
renderFunc RenderFunc
|
|
7
|
-
helmet = &Helmet{}
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
type Helmet struct {
|
|
11
|
-
Title string
|
|
12
|
-
Description string
|
|
13
|
-
Keywords string
|
|
14
|
-
Author string
|
|
15
|
-
}
|
app_nowasm.go
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// +build !wasm
|
|
2
|
-
|
|
3
|
-
package app
|
|
4
|
-
|
|
5
|
-
import (
|
|
6
|
-
"io"
|
|
7
|
-
"net/http"
|
|
8
|
-
"os"
|
|
9
|
-
"path/filepath"
|
|
10
|
-
"strings"
|
|
11
|
-
|
|
12
|
-
"github.com/aws/aws-lambda-go/lambda"
|
|
13
|
-
"github.com/markbates/pkger"
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
func Run() {
|
|
17
|
-
isLambda := os.Getenv("_LAMBDA_SERVER_PORT") != ""
|
|
18
|
-
if !isLambda {
|
|
19
|
-
wd, err := os.Getwd()
|
|
20
|
-
if err != nil {
|
|
21
|
-
println("could not get wd")
|
|
22
|
-
return
|
|
23
|
-
}
|
|
24
|
-
assetsFS := http.FileServer(pkger.Dir(filepath.Join(wd, "assets")))
|
|
25
|
-
globalRouter.GET("/assets/*filepath", func(w http.ResponseWriter, r *http.Request) {
|
|
26
|
-
r.URL.Path = strings.Replace(r.URL.Path, "/assets", "", 1)
|
|
27
|
-
assetsFS.ServeHTTP(w, r)
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
if isLambda {
|
|
31
|
-
println("running in lambda mode")
|
|
32
|
-
lambda.Start(globalRouter.Lambda)
|
|
33
|
-
} else {
|
|
34
|
-
println("Serving on HTTP port: 1234")
|
|
35
|
-
http.ListenAndServe(":1234", globalRouter)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
func Reload() {
|
|
40
|
-
panic("wasm required")
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
func Route(path string, render RenderFunc) {
|
|
44
|
-
println("registering route: " + path)
|
|
45
|
-
globalRouter.GET(path, render)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
var wasm_exec_js *Element = Script(`const enosys = () => { const a = new Error("not implemented"); return a.code = "ENOSYS", a }; let outputBuf = ""; window.fs = { constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, writeSync(a, b) { outputBuf += decoder.decode(b); const c = outputBuf.lastIndexOf("\n"); return -1 != c && (console.log(outputBuf.substr(0, c)), outputBuf = outputBuf.substr(c + 1)), b.length }, write(a, b, c, d, e, f) { if (0 !== c || d !== b.length || null !== e) return void f(enosys()); const g = this.writeSync(a, b); f(null, g) } }; const encoder = new TextEncoder("utf-8"), decoder = new TextDecoder("utf-8"); class Go { constructor() { this.argv = ["js"], this.env = {}, this.exit = a => { 0 !== a && console.warn("exit code:", a) }, this._exitPromise = new Promise(a => { this._resolveExitPromise = a }), this._pendingEvent = null, this._scheduledTimeouts = new Map, this._nextCallbackTimeoutID = 1; const a = (a, b) => { this.mem.setUint32(a + 0, b, !0), this.mem.setUint32(a + 4, Math.floor(b / 4294967296), !0) }, b = a => { const b = this.mem.getUint32(a + 0, !0), c = this.mem.getInt32(a + 4, !0); return b + 4294967296 * c }, c = a => { const b = this.mem.getFloat64(a, !0); if (0 !== b) { if (!isNaN(b)) return b; const c = this.mem.getUint32(a, !0); return this._values[c] } }, d = (a, b) => { const c = 2146959360; if ("number" == typeof b) return isNaN(b) ? (this.mem.setUint32(a + 4, 2146959360, !0), void this.mem.setUint32(a, 0, !0)) : 0 === b ? (this.mem.setUint32(a + 4, 2146959360, !0), void this.mem.setUint32(a, 1, !0)) : void this.mem.setFloat64(a, b, !0); switch (b) { case void 0: return void this.mem.setFloat64(a, 0, !0); case null: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 2, !0); case !0: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 3, !0); case !1: return this.mem.setUint32(a + 4, c, !0), void this.mem.setUint32(a, 4, !0); }let d = this._ids.get(b); d === void 0 && (d = this._idPool.pop(), d === void 0 && (d = this._values.length), this._values[d] = b, this._goRefCounts[d] = 0, this._ids.set(b, d)), this._goRefCounts[d]++; let e = 1; switch (typeof b) { case "string": e = 2; break; case "symbol": e = 3; break; case "function": e = 4; }this.mem.setUint32(a + 4, 2146959360 | e, !0), this.mem.setUint32(a, d, !0) }, e = a => { const c = b(a + 0), d = b(a + 8); return new Uint8Array(this._inst.exports.mem.buffer, c, d) }, f = d => { const e = b(d + 0), f = b(d + 8), g = Array(f); for (let a = 0; a < f; a++)g[a] = c(e + 8 * a); return g }, g = a => { const c = b(a + 0), d = b(a + 8); return decoder.decode(new DataView(this._inst.exports.mem.buffer, c, d)) }, h = Date.now() - performance.now(); this.importObject = { go: { "runtime.wasmExit": a => { const b = this.mem.getInt32(a + 8, !0); this.exited = !0, delete this._inst, delete this._values, delete this._goRefCounts, delete this._ids, delete this._idPool, this.exit(b) }, "runtime.wasmWrite": a => { const c = b(a + 8), d = b(a + 16), e = this.mem.getInt32(a + 24, !0); fs.writeSync(c, new Uint8Array(this._inst.exports.mem.buffer, d, e)) }, "runtime.resetMemoryDataView": () => { this.mem = new DataView(this._inst.exports.mem.buffer) }, "runtime.nanotime1": b => { a(b + 8, 1e6 * (h + performance.now())) }, "runtime.walltime1": b => { const c = new Date().getTime(); a(b + 8, c / 1e3), this.mem.setInt32(b + 16, 1e6 * (c % 1e3), !0) }, "runtime.scheduleTimeoutEvent": a => { const c = this._nextCallbackTimeoutID; this._nextCallbackTimeoutID++, this._scheduledTimeouts.set(c, setTimeout(() => { for (this._resume(); this._scheduledTimeouts.has(c);)console.warn("scheduleTimeoutEvent: missed timeout event"), this._resume() }, b(a + 8) + 1)), this.mem.setInt32(a + 16, c, !0) }, "runtime.clearTimeoutEvent": a => { const b = this.mem.getInt32(a + 8, !0); clearTimeout(this._scheduledTimeouts.get(b)), this._scheduledTimeouts.delete(b) }, "runtime.getRandomData": a => { crypto.getRandomValues(e(a + 8)) }, "syscall/js.finalizeRef": a => { const b = this.mem.getUint32(a + 8, !0); if (this._goRefCounts[b]--, 0 === this._goRefCounts[b]) { const a = this._values[b]; this._values[b] = null, this._ids.delete(a), this._idPool.push(b) } }, "syscall/js.stringVal": a => { d(a + 24, g(a + 8)) }, "syscall/js.valueGet": a => { const b = Reflect.get(c(a + 8), g(a + 16)); a = this._inst.exports.getsp(), d(a + 32, b) }, "syscall/js.valueSet": a => { Reflect.set(c(a + 8), g(a + 16), c(a + 32)) }, "syscall/js.valueDelete": a => { Reflect.deleteProperty(c(a + 8), g(a + 16)) }, "syscall/js.valueIndex": a => { d(a + 24, Reflect.get(c(a + 8), b(a + 16))) }, "syscall/js.valueSetIndex": a => { Reflect.set(c(a + 8), b(a + 16), c(a + 24)) }, "syscall/js.valueCall": a => { try { const b = c(a + 8), e = Reflect.get(b, g(a + 16)), h = f(a + 32), i = Reflect.apply(e, b, h); a = this._inst.exports.getsp(), d(a + 56, i), this.mem.setUint8(a + 64, 1) } catch (b) { d(a + 56, b), this.mem.setUint8(a + 64, 0) } }, "syscall/js.valueInvoke": a => { try { const b = c(a + 8), e = f(a + 16), g = Reflect.apply(b, void 0, e); a = this._inst.exports.getsp(), d(a + 40, g), this.mem.setUint8(a + 48, 1) } catch (b) { d(a + 40, b), this.mem.setUint8(a + 48, 0) } }, "syscall/js.valueNew": a => { try { const b = c(a + 8), e = f(a + 16), g = Reflect.construct(b, e); a = this._inst.exports.getsp(), d(a + 40, g), this.mem.setUint8(a + 48, 1) } catch (b) { d(a + 40, b), this.mem.setUint8(a + 48, 0) } }, "syscall/js.valueLength": b => { a(b + 16, parseInt(c(b + 8).length)) }, "syscall/js.valuePrepareString": b => { const e = encoder.encode(c(b + 8) + ""); d(b + 16, e), a(b + 24, e.length) }, "syscall/js.valueLoadString": a => { const b = c(a + 8); e(a + 16).set(b) }, "syscall/js.valueInstanceOf": a => { this.mem.setUint8(a + 24, c(a + 8) instanceof c(a + 16)) }, "syscall/js.copyBytesToGo": b => { const d = e(b + 8), f = c(b + 32); if (!(f instanceof Uint8Array)) return void this.mem.setUint8(b + 48, 0); const g = f.subarray(0, d.length); d.set(g), a(b + 40, g.length), this.mem.setUint8(b + 48, 1) }, "syscall/js.copyBytesToJS": b => { const d = c(b + 8), f = e(b + 16); if (!(d instanceof Uint8Array)) return void this.mem.setUint8(b + 48, 0); const g = f.subarray(0, d.length); d.set(g), a(b + 40, g.length), this.mem.setUint8(b + 48, 1) }, debug: a => { console.log(a) } } } } async run(a) { this._inst = a, this.mem = new DataView(this._inst.exports.mem.buffer), this._values = [NaN, 0, null, !0, !1, window, this], this._goRefCounts = [], this._ids = new Map, this._idPool = [], this.exited = !1; let b = 4096; const c = a => { const c = b, d = encoder.encode(a + "\0"); return new Uint8Array(this.mem.buffer, b, d.length).set(d), b += d.length, 0 != b % 8 && (b += 8 - b % 8), c }, d = this.argv.length, e = []; this.argv.forEach(a => { e.push(c(a)) }), e.push(0); const f = Object.keys(this.env).sort(); f.forEach(a => { e.push(c(a + "=" + this.env[a])) }), e.push(0); const g = b; e.forEach(a => { this.mem.setUint32(b, a, !0), this.mem.setUint32(b + 4, 0, !0), b += 8 }), this._inst.exports.run(d, g), this.exited && this._resolveExitPromise(), await this._exitPromise } _resume() { if (this.exited) throw new Error("Go program has already exited"); this._inst.exports.resume(), this.exited && this._resolveExitPromise() } _makeFuncWrapper(a) { const b = this; return function () { const c = { id: a, this: this, args: arguments }; return b._pendingEvent = c, b._resume(), c.result } } } const go = new Go; if (!WebAssembly.instantiateStreaming) { WebAssembly.instantiateStreaming = async (resp, importObject) => { const source = await (await resp).arrayBuffer(); return await WebAssembly.instantiate(source, importObject); };}; WebAssembly.instantiateStreaming(fetch("/assets/main.wasm"), go.importObject).then(a => go.run(a.instance)).catch(a => console.error("could not load wasm", a));`)
|
|
49
|
-
|
|
50
|
-
func writePage(ui UI, w io.Writer) {
|
|
51
|
-
hasWasm := os.Getenv("HAS_WASM") != "false"
|
|
52
|
-
Html(
|
|
53
|
-
Head(
|
|
54
|
-
Title(helmet.Title),
|
|
55
|
-
Meta("description", helmet.Description),
|
|
56
|
-
Meta("author", helmet.Author),
|
|
57
|
-
Meta("keywords", helmet.Keywords),
|
|
58
|
-
Meta("viewport", "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover"),
|
|
59
|
-
Link("icon", "/assets/icon.png"),
|
|
60
|
-
Link("apple-touch-icon", "/assets/icon.png"),
|
|
61
|
-
Link("stylesheet", "/assets/styles.css"),
|
|
62
|
-
If(hasWasm, wasm_exec_js),
|
|
63
|
-
),
|
|
64
|
-
Body(ui),
|
|
65
|
-
).Html(w)
|
|
66
|
-
}
|
app_wasm.go
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
// +build wasm
|
|
2
|
-
|
|
3
|
-
package app
|
|
4
|
-
|
|
5
|
-
import (
|
|
6
|
-
"io"
|
|
7
|
-
|
|
8
|
-
"github.com/pyros2097/wapp/js"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
func Run() {
|
|
12
|
-
handle, _, _ := globalRouter.Lookup("GET", js.Window.Location().Pathname)
|
|
13
|
-
if handle == nil {
|
|
14
|
-
renderFunc = globalRouter.NotFound
|
|
15
|
-
} else {
|
|
16
|
-
renderFunc, _ = handle.(RenderFunc)
|
|
17
|
-
}
|
|
18
|
-
defer func() {
|
|
19
|
-
err := recover()
|
|
20
|
-
// show alert
|
|
21
|
-
panic(err)
|
|
22
|
-
}()
|
|
23
|
-
|
|
24
|
-
initBody()
|
|
25
|
-
initContent()
|
|
26
|
-
if err := body.replaceChildAt(0, renderFunc); err != nil {
|
|
27
|
-
panic("replacing content failed")
|
|
28
|
-
}
|
|
29
|
-
content = renderFunc
|
|
30
|
-
|
|
31
|
-
for {
|
|
32
|
-
select {
|
|
33
|
-
case f := <-uiChan:
|
|
34
|
-
f()
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
func Reload() {
|
|
40
|
-
dispatch(func() {
|
|
41
|
-
js.Window.Location().Reload()
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
func Route(path string, render RenderFunc) {
|
|
46
|
-
globalRouter.GET(path, render)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
func initBody() {
|
|
50
|
-
body = &Element{
|
|
51
|
-
jsvalue: js.Window.Get("document").Get("body"),
|
|
52
|
-
tag: "body",
|
|
53
|
-
}
|
|
54
|
-
body.setSelf(body)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
func initContent() {
|
|
58
|
-
content := &Element{
|
|
59
|
-
jsvalue: body.JSValue().Get("firstElementChild"),
|
|
60
|
-
tag: "div",
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
content.setSelf(content)
|
|
64
|
-
content.setParent(body)
|
|
65
|
-
body.body = append(body.body, content)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// func onPopState(this js.Value, args []js.Value) interface{} {
|
|
69
|
-
// dispatch(func() {
|
|
70
|
-
// // navigate(Window().URL(), false)
|
|
71
|
-
// })
|
|
72
|
-
// return nil
|
|
73
|
-
// }
|
|
74
|
-
// func isExternalNavigation(u *url.URL) bool {
|
|
75
|
-
// return u.Host != "" && u.Host != js.Window().URL().Host
|
|
76
|
-
// }
|
|
77
|
-
// func isFragmentNavigation(u *url.URL) bool {
|
|
78
|
-
// return u.Fragment != ""
|
|
79
|
-
// }
|
|
80
|
-
|
|
81
|
-
func writePage(ui UI, w io.Writer) {
|
|
82
|
-
}
|
attributes.go
CHANGED
|
@@ -2,8 +2,6 @@ package app
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"strconv"
|
|
5
|
-
|
|
6
|
-
"github.com/pyros2097/wapp/js"
|
|
7
5
|
)
|
|
8
6
|
|
|
9
7
|
type Attribute struct {
|
|
@@ -55,6 +53,10 @@ func Src(v string) Attribute {
|
|
|
55
53
|
return Attribute{"src", v}
|
|
56
54
|
}
|
|
57
55
|
|
|
56
|
+
func Defer() Attribute {
|
|
57
|
+
return Attribute{"defer", "true"}
|
|
58
|
+
}
|
|
59
|
+
|
|
58
60
|
func ViewBox(v string) Attribute {
|
|
59
61
|
return Attribute{"viewBox", v}
|
|
60
62
|
}
|
|
@@ -94,37 +96,16 @@ func CssIf(v bool, d string) CssAttribute {
|
|
|
94
96
|
return CssAttribute{}
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
type OnClickAttribute struct {
|
|
98
|
-
cb func()
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
func
|
|
99
|
+
func XData(v string) Attribute {
|
|
102
|
-
return OnClickAttribute{cb: cb}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
type OnChangeAttribute struct {
|
|
106
|
-
cb js.EventHandlerFunc
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
func OnChange(cb js.EventHandlerFunc) OnChangeAttribute {
|
|
110
|
-
return
|
|
100
|
+
return Attribute{"x-data", v}
|
|
111
101
|
}
|
|
112
102
|
|
|
113
|
-
|
|
103
|
+
func XText(v string) Attribute {
|
|
114
|
-
|
|
104
|
+
return Attribute{"x-text", v}
|
|
115
105
|
}
|
|
116
106
|
|
|
117
|
-
func OnInput(cb js.EventHandlerFunc) OnInputAttribute {
|
|
118
|
-
return OnInputAttribute{cb: cb}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
type HelmetTitle string
|
|
122
|
-
type HelmetDescription string
|
|
123
|
-
type HelmetAuthor string
|
|
124
|
-
type HelmetKeywords string
|
|
125
|
-
|
|
126
107
|
func MergeAttributes(parent *Element, uis ...interface{}) *Element {
|
|
127
|
-
elems := []
|
|
108
|
+
elems := []*Element{}
|
|
128
109
|
for _, v := range uis {
|
|
129
110
|
switch c := v.(type) {
|
|
130
111
|
case Attribute:
|
|
@@ -135,23 +116,7 @@ func MergeAttributes(parent *Element, uis ...interface{}) *Element {
|
|
|
135
116
|
} else {
|
|
136
117
|
parent.setAttr("class", c.classes)
|
|
137
118
|
}
|
|
138
|
-
case OnClickAttribute:
|
|
139
|
-
parent.setEventHandler("click", func(e js.Event) {
|
|
140
|
-
c.cb()
|
|
141
|
-
})
|
|
142
|
-
case OnChangeAttribute:
|
|
143
|
-
parent.setEventHandler("change", c.cb)
|
|
144
|
-
case OnInputAttribute:
|
|
145
|
-
parent.setEventHandler("input", c.cb)
|
|
146
|
-
case
|
|
119
|
+
case *Element:
|
|
147
|
-
helmet.Title = string(c)
|
|
148
|
-
case HelmetDescription:
|
|
149
|
-
helmet.Description = string(c)
|
|
150
|
-
case HelmetAuthor:
|
|
151
|
-
helmet.Author = string(c)
|
|
152
|
-
case HelmetKeywords:
|
|
153
|
-
helmet.Keywords = string(c)
|
|
154
|
-
case UI:
|
|
155
120
|
elems = append(elems, c)
|
|
156
121
|
case nil:
|
|
157
122
|
// dont need to add nil items
|
|
@@ -161,7 +126,7 @@ func MergeAttributes(parent *Element, uis ...interface{}) *Element {
|
|
|
161
126
|
}
|
|
162
127
|
}
|
|
163
128
|
if !parent.selfClosing {
|
|
164
|
-
parent.
|
|
129
|
+
parent.body = elems
|
|
165
130
|
}
|
|
166
131
|
return parent
|
|
167
132
|
}
|
component.go
DELETED
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
package app
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"reflect"
|
|
5
|
-
"strings"
|
|
6
|
-
|
|
7
|
-
"github.com/pyros2097/wapp/js"
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
var contextMap = map[int]*RenderContext{}
|
|
11
|
-
var contextIndex = 0
|
|
12
|
-
|
|
13
|
-
func getCurrentContext() *RenderContext {
|
|
14
|
-
return contextMap[contextIndex]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type RenderFunc func(ctx *RenderContext) UI
|
|
18
|
-
|
|
19
|
-
func (r RenderFunc) JSValue() js.Value {
|
|
20
|
-
c := getCurrentContext()
|
|
21
|
-
return c.root.JSValue()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
func (r RenderFunc) Mounted() bool {
|
|
25
|
-
c := getCurrentContext()
|
|
26
|
-
return c.root != nil && c.root.Mounted() &&
|
|
27
|
-
r.self() != nil
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
func (r RenderFunc) Render() UI {
|
|
31
|
-
c := getCurrentContext()
|
|
32
|
-
c.index = 0
|
|
33
|
-
c.eindex = 0
|
|
34
|
-
println("render")
|
|
35
|
-
return r(c)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
func (r RenderFunc) Update() {
|
|
39
|
-
dispatch(func() {
|
|
40
|
-
if !r.Mounted() {
|
|
41
|
-
return
|
|
42
|
-
}
|
|
43
|
-
println("update")
|
|
44
|
-
if err := r.updateRoot(); err != nil {
|
|
45
|
-
panic(err)
|
|
46
|
-
}
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
func (r RenderFunc) name() string {
|
|
51
|
-
name := reflect.TypeOf(r.self()).String()
|
|
52
|
-
name = strings.ReplaceAll(name, "main.", "")
|
|
53
|
-
return name
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
func (r RenderFunc) self() UI {
|
|
57
|
-
c := getCurrentContext()
|
|
58
|
-
return c.this
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
func (r RenderFunc) setSelf(n UI) {
|
|
62
|
-
c := getCurrentContext()
|
|
63
|
-
if n != nil {
|
|
64
|
-
println("xnew render context")
|
|
65
|
-
c := NewRenderContext()
|
|
66
|
-
c.this = n.(RenderFunc)
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
c.this = nil
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
func (r RenderFunc) attributes() map[string]string {
|
|
74
|
-
return nil
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
func (r RenderFunc) eventHandlers() map[string]js.EventHandler {
|
|
78
|
-
return nil
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
func (r RenderFunc) parent() UI {
|
|
82
|
-
c := getCurrentContext()
|
|
83
|
-
return c.parentElem
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
func (r RenderFunc) setParent(p UI) {
|
|
87
|
-
c := getCurrentContext()
|
|
88
|
-
c.parentElem = p
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
func (r RenderFunc) children() []UI {
|
|
92
|
-
c := getCurrentContext()
|
|
93
|
-
return []UI{c.root}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
func (r RenderFunc) mount() error {
|
|
97
|
-
c := getCurrentContext()
|
|
98
|
-
if r.Mounted() {
|
|
99
|
-
panic("mounting component failed already mounted " + r.name())
|
|
100
|
-
}
|
|
101
|
-
root := r.Render()
|
|
102
|
-
if err := mount(root); err != nil {
|
|
103
|
-
panic("mounting component failed " + r.name())
|
|
104
|
-
}
|
|
105
|
-
root.setParent(c.this)
|
|
106
|
-
c.root = root
|
|
107
|
-
return nil
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
func (r RenderFunc) dismount() {
|
|
111
|
-
c := getCurrentContext()
|
|
112
|
-
for _, v := range c.effectsUnsub {
|
|
113
|
-
if v != nil {
|
|
114
|
-
v()
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
dismount(c.root)
|
|
118
|
-
delete(contextMap, c.contextMapIndex)
|
|
119
|
-
contextIndex--
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
func (r RenderFunc) update(n UI) error {
|
|
123
|
-
if r.self() == n || !r.Mounted() {
|
|
124
|
-
return nil
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if n.name() != n.name() {
|
|
128
|
-
panic("updating ui element failed replace different element type current-name: " + r.name() + " updated-name: " + n.name())
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
aval := reflect.Indirect(reflect.ValueOf(r.self()))
|
|
132
|
-
bval := reflect.Indirect(reflect.ValueOf(n))
|
|
133
|
-
compotype := reflect.ValueOf(r).Elem().Type()
|
|
134
|
-
|
|
135
|
-
for i := 0; i < aval.NumField(); i++ {
|
|
136
|
-
a := aval.Field(i)
|
|
137
|
-
b := bval.Field(i)
|
|
138
|
-
|
|
139
|
-
if a.Type() == compotype {
|
|
140
|
-
continue
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if !a.CanSet() {
|
|
144
|
-
continue
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if !reflect.DeepEqual(a.Interface(), b.Interface()) {
|
|
148
|
-
a.Set(b)
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return r.updateRoot()
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
func (r RenderFunc) updateRoot() error {
|
|
156
|
-
c := getCurrentContext()
|
|
157
|
-
a := c.root
|
|
158
|
-
println("updateRoot")
|
|
159
|
-
b := r.Render()
|
|
160
|
-
|
|
161
|
-
err := update(a, b)
|
|
162
|
-
if isErrReplace(err) {
|
|
163
|
-
err = r.replaceRoot(b)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if err != nil {
|
|
167
|
-
panic("updating component failed " + r.name())
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return nil
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
func (r RenderFunc) replaceRoot(n UI) error {
|
|
174
|
-
c := getCurrentContext()
|
|
175
|
-
old := c.root
|
|
176
|
-
new := n
|
|
177
|
-
|
|
178
|
-
if err := mount(new); err != nil {
|
|
179
|
-
panic("replacing component root failed name: " + r.name() + " root-name: " + old.name() + "new-root-name: " + new.name())
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
var parent UI
|
|
183
|
-
for {
|
|
184
|
-
parent = r.parent()
|
|
185
|
-
_, isElem := parent.(*Element)
|
|
186
|
-
if parent == nil || isElem {
|
|
187
|
-
break
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if parent == nil {
|
|
192
|
-
panic("replacing component root failed name: " + r.name() + " component does not have html element parents")
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
c.root = new
|
|
196
|
-
new.setParent(r.self())
|
|
197
|
-
|
|
198
|
-
oldjs := old.JSValue()
|
|
199
|
-
newjs := n.JSValue()
|
|
200
|
-
parent.JSValue().Call("replaceChild", newjs, oldjs)
|
|
201
|
-
|
|
202
|
-
dismount(old)
|
|
203
|
-
return nil
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
type RenderContext struct {
|
|
207
|
-
contextMapIndex int
|
|
208
|
-
parentElem UI
|
|
209
|
-
root UI
|
|
210
|
-
this RenderFunc
|
|
211
|
-
index int
|
|
212
|
-
values map[int]interface{}
|
|
213
|
-
eindex int
|
|
214
|
-
effects map[int][]interface{}
|
|
215
|
-
effectsUnsub map[int]func()
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
func NewRenderContext() *RenderContext {
|
|
219
|
-
c := &RenderContext{
|
|
220
|
-
contextMapIndex: contextIndex,
|
|
221
|
-
values: map[int]interface{}{},
|
|
222
|
-
effects: map[int][]interface{}{},
|
|
223
|
-
effectsUnsub: map[int]func(){},
|
|
224
|
-
}
|
|
225
|
-
contextMap[contextIndex] = c
|
|
226
|
-
// contextIndex++
|
|
227
|
-
return c
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
func (c *RenderContext) UseState(initial interface{}) (func() interface{}, func(v interface{})) {
|
|
231
|
-
i := c.index
|
|
232
|
-
c.index++
|
|
233
|
-
if _, ok := c.values[i]; !ok {
|
|
234
|
-
c.values[i] = initial
|
|
235
|
-
}
|
|
236
|
-
return func() interface{} {
|
|
237
|
-
return c.values[i].(interface{})
|
|
238
|
-
}, func(v interface{}) {
|
|
239
|
-
c.values[i] = v
|
|
240
|
-
// special check so that the backend doesn't crash
|
|
241
|
-
if c.this != nil {
|
|
242
|
-
c.this.Update()
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
func (c *RenderContext) UseInt(initial int) (func() int, func(v int)) {
|
|
248
|
-
getState, setState := c.UseState(initial)
|
|
249
|
-
return func() int {
|
|
250
|
-
return getState().(int)
|
|
251
|
-
}, func(v int) {
|
|
252
|
-
setState(v)
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
func (c *RenderContext) UseEffect(f func() func(), deps ...interface{}) {
|
|
257
|
-
i := c.eindex
|
|
258
|
-
c.eindex++
|
|
259
|
-
if _, ok := c.effects[i]; !ok {
|
|
260
|
-
println("initial deps")
|
|
261
|
-
c.effects[i] = deps
|
|
262
|
-
c.effectsUnsub[i] = f()
|
|
263
|
-
return
|
|
264
|
-
}
|
|
265
|
-
hasChanged := false
|
|
266
|
-
for di, ndv := range deps {
|
|
267
|
-
odv := c.effects[i][di]
|
|
268
|
-
if odv != ndv {
|
|
269
|
-
c.effects[i] = deps
|
|
270
|
-
hasChanged = true
|
|
271
|
-
break
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
println("hasChanged", hasChanged)
|
|
275
|
-
if hasChanged {
|
|
276
|
-
f()
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
func (c *RenderContext) UseAtom(a *Atom) interface{} {
|
|
281
|
-
c.UseEffect(func() func() {
|
|
282
|
-
return a.Subscribe(func(v interface{}) {
|
|
283
|
-
c.this.Update()
|
|
284
|
-
})
|
|
285
|
-
})
|
|
286
|
-
return a.Get()
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
type Subscriber func(v interface{})
|
|
290
|
-
|
|
291
|
-
type Atom struct {
|
|
292
|
-
value interface{}
|
|
293
|
-
subscribers []Subscriber
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
func NewAtom(v interface{}) *Atom {
|
|
297
|
-
return &Atom{
|
|
298
|
-
value: v,
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
func (a *Atom) Subscribe(v Subscriber) func() {
|
|
303
|
-
a.subscribers = append(a.subscribers, v)
|
|
304
|
-
i := len(a.subscribers)
|
|
305
|
-
return func() {
|
|
306
|
-
a.subscribers = append(a.subscribers[:i], a.subscribers[i+1:]...)
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
func (a *Atom) Get() interface{} {
|
|
311
|
-
return a.value
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
func (a *Atom) Set(v interface{}) {
|
|
315
|
-
a.value = v
|
|
316
|
-
for _, s := range a.subscribers {
|
|
317
|
-
s(v)
|
|
318
|
-
}
|
|
319
|
-
}
|
component_test.go
DELETED
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
package app
|
|
2
|
-
|
|
3
|
-
// func TestCompoMountDismount(t *testing.T) {
|
|
4
|
-
// testMountDismount(t, []mountTest{
|
|
5
|
-
// {
|
|
6
|
-
// scenario: "component",
|
|
7
|
-
// node: &hello{},
|
|
8
|
-
// },
|
|
9
|
-
// })
|
|
10
|
-
// }
|
|
11
|
-
|
|
12
|
-
// func TestCompoUpdate(t *testing.T) {
|
|
13
|
-
// testUpdate(t, []updateTest{
|
|
14
|
-
// {
|
|
15
|
-
// scenario: "component is updated",
|
|
16
|
-
// a: &bar{Value: "rab"},
|
|
17
|
-
// b: &bar{Value: "bar"},
|
|
18
|
-
// matches: []TestUIDescriptor{
|
|
19
|
-
// {
|
|
20
|
-
// Path: TestPath(),
|
|
21
|
-
// Expected: &bar{Value: "bar"},
|
|
22
|
-
// },
|
|
23
|
-
// {
|
|
24
|
-
// Path: TestPath(0),
|
|
25
|
-
// Expected: Text("bar"),
|
|
26
|
-
// },
|
|
27
|
-
// },
|
|
28
|
-
// },
|
|
29
|
-
// {
|
|
30
|
-
// scenario: "component returns replace error when updated with a non component-element",
|
|
31
|
-
// a: &hello{},
|
|
32
|
-
// b: Text("hello"),
|
|
33
|
-
// replaceErr: true,
|
|
34
|
-
// },
|
|
35
|
-
// {
|
|
36
|
-
// scenario: "component is updated",
|
|
37
|
-
// a: &hello{},
|
|
38
|
-
// b: &hello{Greeting: "world"},
|
|
39
|
-
// matches: []TestUIDescriptor{
|
|
40
|
-
// {
|
|
41
|
-
// Path: TestPath(),
|
|
42
|
-
// Expected: &hello{Greeting: "world"},
|
|
43
|
-
// },
|
|
44
|
-
// {
|
|
45
|
-
// Path: TestPath(0),
|
|
46
|
-
// Expected: Div(),
|
|
47
|
-
// },
|
|
48
|
-
// {
|
|
49
|
-
// Path: TestPath(0, 0),
|
|
50
|
-
// Expected: H1(),
|
|
51
|
-
// },
|
|
52
|
-
// {
|
|
53
|
-
// Path: TestPath(0, 0, 0),
|
|
54
|
-
// Expected: Text("hello, "),
|
|
55
|
-
// },
|
|
56
|
-
// {
|
|
57
|
-
// Path: TestPath(0, 0, 1),
|
|
58
|
-
// Expected: Text("world"),
|
|
59
|
-
// },
|
|
60
|
-
// },
|
|
61
|
-
// },
|
|
62
|
-
// {
|
|
63
|
-
// scenario: "component is replaced by a text",
|
|
64
|
-
// a: Div().Body(
|
|
65
|
-
// &hello{},
|
|
66
|
-
// ),
|
|
67
|
-
// b: Div().Body(
|
|
68
|
-
// Text("hello"),
|
|
69
|
-
// ),
|
|
70
|
-
// matches: []TestUIDescriptor{
|
|
71
|
-
// {
|
|
72
|
-
// Path: TestPath(),
|
|
73
|
-
// Expected: Div(),
|
|
74
|
-
// },
|
|
75
|
-
// {
|
|
76
|
-
// Path: TestPath(0),
|
|
77
|
-
// Expected: Text("hello"),
|
|
78
|
-
// },
|
|
79
|
-
// },
|
|
80
|
-
// },
|
|
81
|
-
// {
|
|
82
|
-
// scenario: "component is replaced by an html element",
|
|
83
|
-
// a: Div().Body(
|
|
84
|
-
// &hello{},
|
|
85
|
-
// ),
|
|
86
|
-
// b: Div().Body(
|
|
87
|
-
// H2().Text("hello"),
|
|
88
|
-
// ),
|
|
89
|
-
// matches: []TestUIDescriptor{
|
|
90
|
-
// {
|
|
91
|
-
// Path: TestPath(),
|
|
92
|
-
// Expected: Div(),
|
|
93
|
-
// },
|
|
94
|
-
// {
|
|
95
|
-
// Path: TestPath(0),
|
|
96
|
-
// Expected: H2(),
|
|
97
|
-
// },
|
|
98
|
-
// {
|
|
99
|
-
// Path: TestPath(0, 0),
|
|
100
|
-
// Expected: Text("hello"),
|
|
101
|
-
// },
|
|
102
|
-
// },
|
|
103
|
-
// },
|
|
104
|
-
// {
|
|
105
|
-
// scenario: "component is replaced by a raw html element",
|
|
106
|
-
// a: Div().Body(
|
|
107
|
-
// &hello{},
|
|
108
|
-
// ),
|
|
109
|
-
// b: Div().Body(
|
|
110
|
-
// Raw("<svg></svg>"),
|
|
111
|
-
// ),
|
|
112
|
-
// matches: []TestUIDescriptor{
|
|
113
|
-
// {
|
|
114
|
-
// Path: TestPath(),
|
|
115
|
-
// Expected: Div(),
|
|
116
|
-
// },
|
|
117
|
-
// {
|
|
118
|
-
// Path: TestPath(0),
|
|
119
|
-
// Expected: Raw("<svg></svg>"),
|
|
120
|
-
// },
|
|
121
|
-
// },
|
|
122
|
-
// },
|
|
123
|
-
// {
|
|
124
|
-
// scenario: "component is replaced by another component",
|
|
125
|
-
// a: Div().Body(
|
|
126
|
-
// &hello{},
|
|
127
|
-
// ),
|
|
128
|
-
// b: Div().Body(
|
|
129
|
-
// &bar{},
|
|
130
|
-
// ),
|
|
131
|
-
// matches: []TestUIDescriptor{
|
|
132
|
-
// {
|
|
133
|
-
// Path: TestPath(),
|
|
134
|
-
// Expected: Div(),
|
|
135
|
-
// },
|
|
136
|
-
// {
|
|
137
|
-
// Path: TestPath(0),
|
|
138
|
-
// Expected: &bar{},
|
|
139
|
-
// },
|
|
140
|
-
// {
|
|
141
|
-
// Path: TestPath(0, 0),
|
|
142
|
-
// Expected: Text(""),
|
|
143
|
-
// },
|
|
144
|
-
// },
|
|
145
|
-
// },
|
|
146
|
-
// {
|
|
147
|
-
// scenario: "component root is updated",
|
|
148
|
-
// a: Div().Body(
|
|
149
|
-
// &foo{Bar: "hello"},
|
|
150
|
-
// ),
|
|
151
|
-
// b: Div().Body(
|
|
152
|
-
// &foo{Bar: "goodbye"},
|
|
153
|
-
// ),
|
|
154
|
-
// matches: []TestUIDescriptor{
|
|
155
|
-
// {
|
|
156
|
-
// Path: TestPath(),
|
|
157
|
-
// Expected: Div(),
|
|
158
|
-
// },
|
|
159
|
-
// {
|
|
160
|
-
// Path: TestPath(0),
|
|
161
|
-
// Expected: &foo{Bar: "goodbye"},
|
|
162
|
-
// },
|
|
163
|
-
// {
|
|
164
|
-
// Path: TestPath(0, 0),
|
|
165
|
-
// Expected: &bar{Value: "goodbye"},
|
|
166
|
-
// },
|
|
167
|
-
// {
|
|
168
|
-
// Path: TestPath(0, 0, 0),
|
|
169
|
-
// Expected: Text("goodbye"),
|
|
170
|
-
// },
|
|
171
|
-
// },
|
|
172
|
-
// },
|
|
173
|
-
// {
|
|
174
|
-
// scenario: "component root is replaced by a component",
|
|
175
|
-
// a: Div().Body(
|
|
176
|
-
// &foo{},
|
|
177
|
-
// ),
|
|
178
|
-
// b: Div().Body(
|
|
179
|
-
// &foo{Bar: "test"},
|
|
180
|
-
// ),
|
|
181
|
-
// matches: []TestUIDescriptor{
|
|
182
|
-
// {
|
|
183
|
-
// Path: TestPath(),
|
|
184
|
-
// Expected: Div(),
|
|
185
|
-
// },
|
|
186
|
-
// {
|
|
187
|
-
// Path: TestPath(0),
|
|
188
|
-
// Expected: &foo{Bar: "test"},
|
|
189
|
-
// },
|
|
190
|
-
// {
|
|
191
|
-
// Path: TestPath(0, 0),
|
|
192
|
-
// Expected: &bar{Value: "test"},
|
|
193
|
-
// },
|
|
194
|
-
// {
|
|
195
|
-
// Path: TestPath(0, 0, 0),
|
|
196
|
-
// Expected: Text("test"),
|
|
197
|
-
// },
|
|
198
|
-
// },
|
|
199
|
-
// },
|
|
200
|
-
// {
|
|
201
|
-
// scenario: "component root is replaced by a non-component",
|
|
202
|
-
// a: Div().Body(
|
|
203
|
-
// &foo{Bar: "test"},
|
|
204
|
-
// ),
|
|
205
|
-
// b: Div().Body(
|
|
206
|
-
// &foo{},
|
|
207
|
-
// ),
|
|
208
|
-
// matches: []TestUIDescriptor{
|
|
209
|
-
// {
|
|
210
|
-
// Path: TestPath(),
|
|
211
|
-
// Expected: Div(),
|
|
212
|
-
// },
|
|
213
|
-
// {
|
|
214
|
-
// Path: TestPath(0),
|
|
215
|
-
// Expected: &foo{},
|
|
216
|
-
// },
|
|
217
|
-
// {
|
|
218
|
-
// Path: TestPath(0, 0),
|
|
219
|
-
// Expected: Text("bar"),
|
|
220
|
-
// },
|
|
221
|
-
// },
|
|
222
|
-
// },
|
|
223
|
-
// })
|
|
224
|
-
// }
|
|
225
|
-
|
|
226
|
-
// type hello struct {
|
|
227
|
-
// Compo
|
|
228
|
-
|
|
229
|
-
// Greeting string
|
|
230
|
-
// onNavURL string
|
|
231
|
-
// }
|
|
232
|
-
|
|
233
|
-
// func (h *hello) OnMount(Context) {
|
|
234
|
-
// }
|
|
235
|
-
|
|
236
|
-
// func (h *hello) OnNav(ctx Context, u *url.URL) {
|
|
237
|
-
// h.onNavURL = u.String()
|
|
238
|
-
// }
|
|
239
|
-
|
|
240
|
-
// func (h *hello) OnDismount(Context) {
|
|
241
|
-
// }
|
|
242
|
-
|
|
243
|
-
// func (h *hello) Render() UI {
|
|
244
|
-
// return Div().Body(
|
|
245
|
-
// H1().Body(
|
|
246
|
-
// Text("hello, "),
|
|
247
|
-
// Text(h.Greeting),
|
|
248
|
-
// ),
|
|
249
|
-
// )
|
|
250
|
-
// }
|
|
251
|
-
|
|
252
|
-
// type foo struct {
|
|
253
|
-
// Compo
|
|
254
|
-
// Bar string
|
|
255
|
-
// }
|
|
256
|
-
|
|
257
|
-
// func (f *foo) Render() UI {
|
|
258
|
-
// return If(f.Bar != "",
|
|
259
|
-
// &bar{Value: f.Bar},
|
|
260
|
-
// ).Else(
|
|
261
|
-
// Text("bar"),
|
|
262
|
-
// )
|
|
263
|
-
// }
|
|
264
|
-
|
|
265
|
-
// type bar struct {
|
|
266
|
-
// Compo
|
|
267
|
-
// Value string
|
|
268
|
-
// onNavURL string
|
|
269
|
-
// }
|
|
270
|
-
|
|
271
|
-
// func (b *bar) OnNav(ctx Context, u *url.URL) {
|
|
272
|
-
// b.onNavURL = u.String()
|
|
273
|
-
// }
|
|
274
|
-
|
|
275
|
-
// func (b *bar) Render() UI {
|
|
276
|
-
// return Text(b.Value)
|
|
277
|
-
// }
|
element.go
CHANGED
|
@@ -2,212 +2,21 @@ package app
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"io"
|
|
5
|
-
"
|
|
5
|
+
"unsafe"
|
|
6
|
-
|
|
7
|
-
"github.com/pyros2097/wapp/js"
|
|
8
6
|
)
|
|
9
7
|
|
|
10
8
|
type Element struct {
|
|
9
|
+
tag string
|
|
11
10
|
attrs map[string]string
|
|
12
|
-
body []
|
|
11
|
+
body []*Element
|
|
13
|
-
events map[string]js.EventHandler
|
|
14
|
-
jsvalue js.Value
|
|
15
|
-
parentElem UI
|
|
16
12
|
selfClosing bool
|
|
17
|
-
|
|
13
|
+
text string
|
|
18
|
-
this UI
|
|
19
14
|
}
|
|
20
15
|
|
|
21
16
|
func NewElement(tag string, selfClosing bool, uis ...interface{}) *Element {
|
|
22
17
|
return MergeAttributes(&Element{tag: tag, selfClosing: selfClosing}, uis...)
|
|
23
18
|
}
|
|
24
19
|
|
|
25
|
-
func (e *Element) JSValue() js.Value {
|
|
26
|
-
return e.jsvalue
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
func (e *Element) Mounted() bool {
|
|
30
|
-
return e.self() != nil &&
|
|
31
|
-
e.jsvalue != nil
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
func (e *Element) name() string {
|
|
35
|
-
return e.tag
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
func (e *Element) self() UI {
|
|
39
|
-
return e.this
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
func (e *Element) setSelf(n UI) {
|
|
43
|
-
e.this = n
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
func (e *Element) attributes() map[string]string {
|
|
47
|
-
return e.attrs
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
func (e *Element) eventHandlers() map[string]js.EventHandler {
|
|
51
|
-
return e.events
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
func (e *Element) parent() UI {
|
|
55
|
-
return e.parentElem
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
func (e *Element) setParent(p UI) {
|
|
59
|
-
e.parentElem = p
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
func (e *Element) children() []UI {
|
|
63
|
-
return e.body
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
func (e *Element) mount() error {
|
|
67
|
-
if e.Mounted() {
|
|
68
|
-
panic("mounting elem failed already mounted " + e.name())
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
v := js.Window.Get("document").Call("createElement", e.tag)
|
|
72
|
-
if !v.Truthy() {
|
|
73
|
-
panic("mounting component failed create javascript node returned nil " + e.name())
|
|
74
|
-
}
|
|
75
|
-
e.jsvalue = v
|
|
76
|
-
|
|
77
|
-
for k, v := range e.attrs {
|
|
78
|
-
e.setJsAttr(k, v)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
for k, v := range e.events {
|
|
82
|
-
e.setJsEventHandler(k, v)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
for _, c := range e.children() {
|
|
86
|
-
if err := e.appendChild(c, true); err != nil {
|
|
87
|
-
panic("mounting component failed appendChild " + e.name())
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return nil
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
func (e *Element) dismount() {
|
|
95
|
-
for _, c := range e.children() {
|
|
96
|
-
dismount(c)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
for k, v := range e.events {
|
|
100
|
-
e.delJsEventHandler(k, v)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
e.jsvalue = nil
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
func (e *Element) update(n UI) error {
|
|
107
|
-
if !e.Mounted() {
|
|
108
|
-
return nil
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if n.name() != e.name() {
|
|
112
|
-
panic("updating element failed replace different element type current-name: " + e.name() + " updated-name: " + n.name())
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
e.updateAttrs(n.attributes())
|
|
116
|
-
e.updateEventHandler(n.eventHandlers())
|
|
117
|
-
|
|
118
|
-
achildren := e.children()
|
|
119
|
-
bchildren := n.children()
|
|
120
|
-
i := 0
|
|
121
|
-
|
|
122
|
-
// Update children:
|
|
123
|
-
for len(achildren) != 0 && len(bchildren) != 0 {
|
|
124
|
-
a := achildren[0]
|
|
125
|
-
b := bchildren[0]
|
|
126
|
-
|
|
127
|
-
err := update(a, b)
|
|
128
|
-
if isErrReplace(err) {
|
|
129
|
-
err = e.replaceChildAt(i, b)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if err != nil {
|
|
133
|
-
panic("updating element failed name: " + e.name())
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
achildren = achildren[1:]
|
|
137
|
-
bchildren = bchildren[1:]
|
|
138
|
-
i++
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Remove children:
|
|
142
|
-
for len(achildren) != 0 {
|
|
143
|
-
if err := e.removeChildAt(i); err != nil {
|
|
144
|
-
panic("updating element failed name: " + e.name())
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
achildren = achildren[1:]
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Add children:
|
|
151
|
-
for len(bchildren) != 0 {
|
|
152
|
-
c := bchildren[0]
|
|
153
|
-
|
|
154
|
-
if err := e.appendChild(c, false); err != nil {
|
|
155
|
-
panic("updating element failed name: " + e.name())
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
bchildren = bchildren[1:]
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return nil
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
func (e *Element) appendChild(c UI, onlyJsValue bool) error {
|
|
165
|
-
if err := mount(c); err != nil {
|
|
166
|
-
panic("appending child failed child-name: " + c.name() + " name: " + e.name())
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if !onlyJsValue {
|
|
170
|
-
e.body = append(e.body, c)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
c.setParent(e.self())
|
|
174
|
-
e.JSValue().Call("appendChild", c)
|
|
175
|
-
return nil
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
func (e *Element) replaceChildAt(idx int, new UI) error {
|
|
179
|
-
old := e.body[idx]
|
|
180
|
-
|
|
181
|
-
if err := mount(new); err != nil {
|
|
182
|
-
panic("replacing child failed name: " + e.name() + " old-name: " + old.name() + " new-name: " + new.name())
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
e.body[idx] = new
|
|
186
|
-
new.setParent(e.self())
|
|
187
|
-
e.JSValue().Call("replaceChild", new, old)
|
|
188
|
-
|
|
189
|
-
dismount(old)
|
|
190
|
-
return nil
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
func (e *Element) removeChildAt(idx int) error {
|
|
194
|
-
body := e.body
|
|
195
|
-
if idx < 0 || idx >= len(body) {
|
|
196
|
-
panic("removing child failed index out of range name: " + e.name() + " index: " + strconv.Itoa(idx))
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
c := body[idx]
|
|
200
|
-
|
|
201
|
-
copy(body[idx:], body[idx+1:])
|
|
202
|
-
body[len(body)-1] = nil
|
|
203
|
-
body = body[:len(body)-1]
|
|
204
|
-
e.body = body
|
|
205
|
-
|
|
206
|
-
e.JSValue().Call("removeChild", c)
|
|
207
|
-
dismount(c)
|
|
208
|
-
return nil
|
|
209
|
-
}
|
|
210
|
-
|
|
211
20
|
func (e *Element) updateAttrs(attrs map[string]string) {
|
|
212
21
|
for k := range e.attrs {
|
|
213
22
|
if _, exists := attrs[k]; !exists {
|
|
@@ -222,7 +31,6 @@ func (e *Element) updateAttrs(attrs map[string]string) {
|
|
|
222
31
|
for k, v := range attrs {
|
|
223
32
|
if curval, exists := e.attrs[k]; !exists || curval != v {
|
|
224
33
|
e.attrs[k] = v
|
|
225
|
-
e.setJsAttr(k, v)
|
|
226
34
|
}
|
|
227
35
|
}
|
|
228
36
|
}
|
|
@@ -257,77 +65,26 @@ func (e *Element) setAttr(k string, v string) {
|
|
|
257
65
|
}
|
|
258
66
|
}
|
|
259
67
|
|
|
260
|
-
func (e *Element) setJsAttr(k, v string) {
|
|
261
|
-
e.JSValue().Call("setAttribute", k, v)
|
|
262
|
-
}
|
|
263
|
-
|
|
264
68
|
func (e *Element) delAttr(k string) {
|
|
265
|
-
e.JSValue().Call("removeAttribute", k)
|
|
266
69
|
delete(e.attrs, k)
|
|
267
70
|
}
|
|
268
71
|
|
|
269
|
-
func (
|
|
72
|
+
func writeIndent(w io.Writer, indent int) {
|
|
270
|
-
for
|
|
73
|
+
for i := 0; i < indent*4; i++ {
|
|
271
|
-
if _, exists := handlers[k]; !exists {
|
|
272
|
-
e.delJsEventHandler(k, current)
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if e.events == nil && len(handlers) != 0 {
|
|
277
|
-
e.events = make(map[string]js.EventHandler, len(handlers))
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
for k, new := range handlers {
|
|
281
|
-
if current, exists := e.events[k]; !current.Equal(new) {
|
|
282
|
-
if exists {
|
|
283
|
-
e.delJsEventHandler(k, current)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
e.events[k] = new
|
|
287
|
-
|
|
74
|
+
w.Write(stob(" "))
|
|
288
|
-
}
|
|
289
75
|
}
|
|
290
76
|
}
|
|
291
77
|
|
|
292
|
-
func (e *Element) setEventHandler(k string, h js.EventHandlerFunc) {
|
|
293
|
-
|
|
78
|
+
func ln() []byte {
|
|
294
|
-
e.events = make(map[string]js.EventHandler)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
79
|
+
return stob("\n")
|
|
298
80
|
}
|
|
299
81
|
|
|
300
|
-
func (e *Element) setJsEventHandler(k string, h js.EventHandler) {
|
|
301
|
-
jshandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
302
|
-
dispatch(func() {
|
|
303
|
-
|
|
82
|
+
func btos(b []byte) string {
|
|
304
|
-
return
|
|
305
|
-
}
|
|
306
|
-
e := js.Event{
|
|
307
|
-
Src: this,
|
|
308
|
-
Value: args[0],
|
|
309
|
-
}
|
|
310
|
-
trackMousePosition(e)
|
|
311
|
-
h.Value(e)
|
|
312
|
-
})
|
|
313
|
-
return nil
|
|
314
|
-
})
|
|
315
|
-
h.JSvalue = jshandler
|
|
316
|
-
e.events[k] = h
|
|
317
|
-
|
|
83
|
+
return *(*string)(unsafe.Pointer(&b))
|
|
318
84
|
}
|
|
319
85
|
|
|
320
|
-
func (e *Element) delJsEventHandler(k string, h js.EventHandler) {
|
|
321
|
-
e.JSValue().Call("removeEventListener", k, h.JSvalue)
|
|
322
|
-
h.JSvalue.Release()
|
|
323
|
-
delete(e.events, k)
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
func (
|
|
86
|
+
func stob(s string) []byte {
|
|
327
|
-
if e.selfClosing {
|
|
328
|
-
|
|
87
|
+
return *(*[]byte)(unsafe.Pointer(&s))
|
|
329
|
-
}
|
|
330
|
-
e.body = body
|
|
331
88
|
}
|
|
332
89
|
|
|
333
90
|
func (e *Element) Html(w io.Writer) {
|
|
@@ -339,6 +96,11 @@ func (e *Element) HtmlWithIndent(w io.Writer, indent int) {
|
|
|
339
96
|
if e.tag == "html" {
|
|
340
97
|
w.Write(stob("<!DOCTYPE html>\n"))
|
|
341
98
|
}
|
|
99
|
+
if e.tag == "text" {
|
|
100
|
+
writeIndent(w, indent)
|
|
101
|
+
w.Write(stob(e.text))
|
|
102
|
+
return
|
|
103
|
+
}
|
|
342
104
|
w.Write(stob("<"))
|
|
343
105
|
w.Write(stob(e.tag))
|
|
344
106
|
|
|
@@ -362,7 +124,7 @@ func (e *Element) HtmlWithIndent(w io.Writer, indent int) {
|
|
|
362
124
|
for _, c := range e.body {
|
|
363
125
|
w.Write(ln())
|
|
364
126
|
if c != nil {
|
|
365
|
-
c.
|
|
127
|
+
c.HtmlWithIndent(w, indent+1)
|
|
366
128
|
}
|
|
367
129
|
}
|
|
368
130
|
|
|
@@ -375,97 +137,3 @@ func (e *Element) HtmlWithIndent(w io.Writer, indent int) {
|
|
|
375
137
|
w.Write(stob(e.tag))
|
|
376
138
|
w.Write(stob(">"))
|
|
377
139
|
}
|
|
378
|
-
|
|
379
|
-
type text struct {
|
|
380
|
-
jsvalue js.Value
|
|
381
|
-
parentElem UI
|
|
382
|
-
value string
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Text creates a simple text element.
|
|
386
|
-
func Text(v string) UI {
|
|
387
|
-
return &text{value: v}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
func (t *text) JSValue() js.Value {
|
|
391
|
-
return t.jsvalue
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
func (t *text) Mounted() bool {
|
|
395
|
-
return t.jsvalue != nil
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
func (t *text) name() string {
|
|
399
|
-
return "text"
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
func (t *text) self() UI {
|
|
403
|
-
return t
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
func (t *text) setSelf(n UI) {
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
func (t *text) attributes() map[string]string {
|
|
410
|
-
return nil
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
func (t *text) eventHandlers() map[string]js.EventHandler {
|
|
414
|
-
return nil
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
func (t *text) parent() UI {
|
|
418
|
-
return t.parentElem
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
func (t *text) setParent(p UI) {
|
|
422
|
-
t.parentElem = p
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
func (t *text) children() []UI {
|
|
426
|
-
return nil
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
func (t *text) mount() error {
|
|
430
|
-
if t.Mounted() {
|
|
431
|
-
panic("mounting text element failed already mounted" + t.name() + " " + t.value)
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
t.jsvalue = js.Window.
|
|
435
|
-
Get("document").
|
|
436
|
-
Call("createTextNode", t.value)
|
|
437
|
-
|
|
438
|
-
return nil
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
func (t *text) dismount() {
|
|
442
|
-
t.jsvalue = nil
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
func (t *text) update(n UI) error {
|
|
446
|
-
if !t.Mounted() {
|
|
447
|
-
return nil
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
o, isText := n.(*text)
|
|
451
|
-
if !isText {
|
|
452
|
-
panic("updating ui element failed replace different element type current-name: " + t.name() + " updated-name: " + n.name())
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if t.value != o.value {
|
|
456
|
-
t.value = o.value
|
|
457
|
-
t.jsvalue.Set("nodeValue", o.value)
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
return nil
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
func (t *text) Html(w io.Writer) {
|
|
464
|
-
t.HtmlWithIndent(w, 0)
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
func (t *text) HtmlWithIndent(w io.Writer, indent int) {
|
|
468
|
-
writeIndent(w, indent)
|
|
469
|
-
// html.EscapeString(
|
|
470
|
-
w.Write(stob(t.value))
|
|
471
|
-
}
|
errors/README.md
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# errors
|
|
2
|
-
|
|
3
|
-
Package errors implements functions to manipulate errors.
|
|
4
|
-
|
|
5
|
-
Errors created are taggable and wrappable.
|
|
6
|
-
|
|
7
|
-
```go
|
|
8
|
-
errWithTags := errors.New("an error with tags").
|
|
9
|
-
Tag("a", 42).
|
|
10
|
-
Tag("b", 21)
|
|
11
|
-
|
|
12
|
-
errWithWrap := errors.New("error").
|
|
13
|
-
Tag("a", 42).
|
|
14
|
-
Wrap(errors.New("wrapped error"))
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
The package mirrors https:golang.org/pkg/errors package.
|
errors/errors.go
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
// Package errors implements functions to manipulate errors.
|
|
2
|
-
//
|
|
3
|
-
// Errors created are taggable and wrappable.
|
|
4
|
-
//
|
|
5
|
-
// errWithTags := errors.New("an error with tags").
|
|
6
|
-
// Tag("a", 42).
|
|
7
|
-
// Tag("b", 21)
|
|
8
|
-
//
|
|
9
|
-
// errWithWrap := errors.New("error").
|
|
10
|
-
// Tag("a", 42).
|
|
11
|
-
// Wrap(errors.New("wrapped error"))
|
|
12
|
-
//
|
|
13
|
-
// The package mirrors https://golang.org/pkg/errors package.
|
|
14
|
-
package errors
|
|
15
|
-
|
|
16
|
-
import (
|
|
17
|
-
"bytes"
|
|
18
|
-
"errors"
|
|
19
|
-
"fmt"
|
|
20
|
-
"reflect"
|
|
21
|
-
"sort"
|
|
22
|
-
"strings"
|
|
23
|
-
"unsafe"
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
// As is documented at https://golang.org/pkg/errors/#As.
|
|
27
|
-
func As(err error, target interface{}) bool {
|
|
28
|
-
return errors.As(err, target)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Is is documented at https://golang.org/pkg/errors/#Is.
|
|
32
|
-
func Is(err, target error) bool {
|
|
33
|
-
return errors.Is(err, target)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Unwrap is documented at https://golang.org/pkg/errors/#Unwrap.
|
|
37
|
-
func Unwrap(err error) error {
|
|
38
|
-
return errors.Unwrap(err)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Tag retrieves the value of the tag named by the key. If the tag exists,
|
|
42
|
-
// its value (which may be empty) is returned and the boolean is true. Otherwise
|
|
43
|
-
// the returned value will be empty and the boolean will be false.
|
|
44
|
-
func Tag(err error, k string) (string, bool) {
|
|
45
|
-
ierr, ok := err.(Error)
|
|
46
|
-
if !ok {
|
|
47
|
-
return "", false
|
|
48
|
-
}
|
|
49
|
-
return ierr.Lookup(k)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// New returns an error with the given description that can be tagged.
|
|
53
|
-
func New(v string) Error {
|
|
54
|
-
return Error{
|
|
55
|
-
description: v,
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Newf returns an error with the given formatted description that can be
|
|
60
|
-
// tagged.
|
|
61
|
-
func Newf(format string, v ...interface{}) Error {
|
|
62
|
-
return New(fmt.Sprintf(format, v...))
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Error is an error implementation that supports tagging and wrapping.
|
|
66
|
-
type Error struct {
|
|
67
|
-
description string
|
|
68
|
-
tags []tag
|
|
69
|
-
maxKeyLen int
|
|
70
|
-
wrap error
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Tag sets the named tag with the given value.
|
|
74
|
-
func (e Error) Tag(k string, v interface{}) Error {
|
|
75
|
-
if e.tags == nil {
|
|
76
|
-
e.tags = make([]tag, 0, 8)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if l := len(k); l > e.maxKeyLen {
|
|
80
|
-
e.maxKeyLen = l
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
switch v := v.(type) {
|
|
84
|
-
case string:
|
|
85
|
-
e.tags = append(e.tags, tag{key: k, value: v})
|
|
86
|
-
|
|
87
|
-
default:
|
|
88
|
-
e.tags = append(e.tags, tag{key: k, value: fmt.Sprintf("%+v", v)})
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return e
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Lookup retrieves the value of the tag named by the key. If the tag exists,
|
|
95
|
-
// its value (which may be empty) is returned and the boolean is true. Otherwise
|
|
96
|
-
// the returned value will be empty and the boolean will be false.
|
|
97
|
-
func (e Error) Lookup(tag string) (string, bool) {
|
|
98
|
-
for _, t := range e.tags {
|
|
99
|
-
if t.key == tag {
|
|
100
|
-
return t.value, true
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return "", false
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Wrap wraps the given error. Nil errors are ingnored.
|
|
107
|
-
func (e Error) Wrap(err error) Error {
|
|
108
|
-
if err == nil {
|
|
109
|
-
return e
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if e.maxKeyLen < 5 {
|
|
113
|
-
e.maxKeyLen = 5
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if e.wrap == nil {
|
|
117
|
-
e.wrap = err
|
|
118
|
-
return e
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
description := ""
|
|
122
|
-
if perr, ok := err.(Error); ok {
|
|
123
|
-
description = perr.description
|
|
124
|
-
} else {
|
|
125
|
-
description = err.Error()
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
e.wrap = New(description).Wrap(e.wrap)
|
|
129
|
-
return e
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Unwrap unwraps the given error.
|
|
133
|
-
func (e Error) Unwrap() error {
|
|
134
|
-
return e.wrap
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Is reports if the target matches the error or its wrapped values.
|
|
138
|
-
func (e Error) Is(target error) bool {
|
|
139
|
-
o, ok := target.(Error)
|
|
140
|
-
if !ok {
|
|
141
|
-
return false
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return e.description == o.description &&
|
|
145
|
-
reflect.DeepEqual(e.tags, o.tags)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
func (e Error) Error() string {
|
|
149
|
-
w := bytes.NewBuffer(make([]byte, 0, len(e.description)+len(e.tags)*(e.maxKeyLen+11)))
|
|
150
|
-
e.format(w, 0)
|
|
151
|
-
return bytesToString(w.Bytes())
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
func (e Error) format(w *bytes.Buffer, indent int) {
|
|
155
|
-
w.WriteString(e.description)
|
|
156
|
-
if e.wrap != nil || len(e.tags) != 0 {
|
|
157
|
-
w.WriteByte(':')
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
tags := e.tags
|
|
161
|
-
sort.Slice(tags, func(a, b int) bool {
|
|
162
|
-
return strings.Compare(tags[a].key, tags[b].key) < 0
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
for _, t := range e.tags {
|
|
166
|
-
k := t.key
|
|
167
|
-
v := t.value
|
|
168
|
-
|
|
169
|
-
w.WriteByte('\n')
|
|
170
|
-
e.indent(w, indent+4)
|
|
171
|
-
w.WriteString(k)
|
|
172
|
-
w.WriteByte(':')
|
|
173
|
-
e.indent(w, e.maxKeyLen-len(k)+1)
|
|
174
|
-
w.WriteString(v)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if e.wrap == nil {
|
|
178
|
-
return
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
w.WriteByte('\n')
|
|
182
|
-
e.indent(w, indent+4)
|
|
183
|
-
w.WriteString("error")
|
|
184
|
-
w.WriteByte(':')
|
|
185
|
-
e.indent(w, e.maxKeyLen-5+1)
|
|
186
|
-
|
|
187
|
-
if err, ok := e.wrap.(Error); ok {
|
|
188
|
-
err.format(w, indent+4)
|
|
189
|
-
return
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
w.WriteString(e.wrap.Error())
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
func (e Error) indent(w *bytes.Buffer, n int) {
|
|
196
|
-
for i := 0; i < n; i++ {
|
|
197
|
-
w.WriteByte(' ')
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
type tag struct {
|
|
202
|
-
key string
|
|
203
|
-
value string
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
func bytesToString(b []byte) string {
|
|
207
|
-
return *(*string)(unsafe.Pointer(&b))
|
|
208
|
-
}
|
errors/errors_test.go
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
package errors
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"errors"
|
|
5
|
-
"testing"
|
|
6
|
-
"time"
|
|
7
|
-
|
|
8
|
-
"github.com/stretchr/testify/require"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
func TestError(t *testing.T) {
|
|
12
|
-
err := New("a simple error")
|
|
13
|
-
t.Log(err)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
func TestErrorWithTags(t *testing.T) {
|
|
17
|
-
err := New("an error with tags").
|
|
18
|
-
Tag("string", "hello world").
|
|
19
|
-
Tag("go-stringer", goStringer{}).
|
|
20
|
-
Tag("duration", time.Duration(3600000000)).
|
|
21
|
-
Tag("int", 42).
|
|
22
|
-
Tag("int8", int8(8)).
|
|
23
|
-
Tag("int16", int16(16)).
|
|
24
|
-
Tag("int32", int32(32)).
|
|
25
|
-
Tag("int64", int64(64)).
|
|
26
|
-
Tag("uint", uint(42)).
|
|
27
|
-
Tag("uint8", uint8(8)).
|
|
28
|
-
Tag("uint16", uint16(16)).
|
|
29
|
-
Tag("uint32", uint32(32)).
|
|
30
|
-
Tag("uint64", uint64(64)).
|
|
31
|
-
Tag("float32", float32(32.42)).
|
|
32
|
-
Tag("float64", float64(64.42)).
|
|
33
|
-
Tag("slice", []string{"hello", "world"})
|
|
34
|
-
t.Log("\n", err)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
func TestErrorWithWrap(t *testing.T) {
|
|
38
|
-
b := New("b").Wrap(errors.New("c"))
|
|
39
|
-
a := New("a").
|
|
40
|
-
Wrap(b).
|
|
41
|
-
Wrap(nil)
|
|
42
|
-
require.True(t, b.Is(a.wrap))
|
|
43
|
-
|
|
44
|
-
d := New("d").
|
|
45
|
-
Wrap(a).
|
|
46
|
-
Wrap(b).
|
|
47
|
-
Wrap(errors.New("f"))
|
|
48
|
-
t.Log("\n", d)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
func TestErrorWithTagsAndWrap(t *testing.T) {
|
|
52
|
-
err := New("an error with tags").
|
|
53
|
-
Tag("uint64", uint64(64)).
|
|
54
|
-
Tag("float64", float64(64.42)).
|
|
55
|
-
Tag("slice", []string{"hello", "world"}).
|
|
56
|
-
Wrap(errors.New("another error"))
|
|
57
|
-
t.Log("\n", err)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
func TestLookup(t *testing.T) {
|
|
61
|
-
err := New("error").Tag("foo", 42)
|
|
62
|
-
|
|
63
|
-
v, found := err.Lookup("foo")
|
|
64
|
-
require.True(t, found)
|
|
65
|
-
require.Equal(t, "42", v)
|
|
66
|
-
|
|
67
|
-
v, found = err.Lookup("bar")
|
|
68
|
-
require.False(t, found)
|
|
69
|
-
require.Empty(t, v)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
func TestErrorUwrap(t *testing.T) {
|
|
73
|
-
a := New("a").Tag("wrap", true)
|
|
74
|
-
b := New("b").Wrap(a)
|
|
75
|
-
|
|
76
|
-
err := b.Unwrap()
|
|
77
|
-
require.Equal(t, a, err)
|
|
78
|
-
|
|
79
|
-
err = Unwrap(b)
|
|
80
|
-
require.Equal(t, a, err)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
func TestAs(t *testing.T) {
|
|
84
|
-
a := New("a").Tag("wrap", true)
|
|
85
|
-
b := New("b").Wrap(a)
|
|
86
|
-
c := New("c").Wrap(b)
|
|
87
|
-
d := New("d")
|
|
88
|
-
|
|
89
|
-
require.True(t, As(c, &a))
|
|
90
|
-
require.True(t, As(c, &d))
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
func TestIs(t *testing.T) {
|
|
94
|
-
a := New("a").Tag("wrap", true)
|
|
95
|
-
b := New("b").Wrap(a)
|
|
96
|
-
c := New("c").Wrap(b)
|
|
97
|
-
d := New("d")
|
|
98
|
-
e := errors.New("e")
|
|
99
|
-
|
|
100
|
-
require.True(t, Is(c, a))
|
|
101
|
-
require.False(t, Is(c, d))
|
|
102
|
-
require.False(t, Is(d, e))
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
func TestTag(t *testing.T) {
|
|
106
|
-
a := errors.New("a")
|
|
107
|
-
v, isTagged := Tag(a, "test")
|
|
108
|
-
require.Empty(t, v)
|
|
109
|
-
require.False(t, isTagged)
|
|
110
|
-
|
|
111
|
-
b := New("b").Tag("test", "true")
|
|
112
|
-
v, isTagged = Tag(b, "test")
|
|
113
|
-
require.Equal(t, "true", v)
|
|
114
|
-
require.True(t, isTagged)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
func TestNewf(t *testing.T) {
|
|
118
|
-
err := Newf("hello %q", "world")
|
|
119
|
-
t.Log(err)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
type goStringer struct{}
|
|
123
|
-
|
|
124
|
-
func (s goStringer) GoString() string {
|
|
125
|
-
return "go stringer !"
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
func BenchmarkNew(b *testing.B) {
|
|
129
|
-
for n := 0; n < b.N; n++ {
|
|
130
|
-
New("an error with tags").
|
|
131
|
-
Tag("string", "hello world").
|
|
132
|
-
Tag("int8", int8(8)).
|
|
133
|
-
Tag("int16", int16(16)).
|
|
134
|
-
Tag("int32", int32(32)).
|
|
135
|
-
Tag("int64", int64(64))
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
func BenchmarkError(b *testing.B) {
|
|
140
|
-
var s string
|
|
141
|
-
|
|
142
|
-
for n := 0; n < b.N; n++ {
|
|
143
|
-
s = New("an error with tags").
|
|
144
|
-
Tag("string", "hello world").
|
|
145
|
-
Tag("int8", int8(8)).
|
|
146
|
-
Tag("int16", int16(16)).
|
|
147
|
-
Tag("int32", int32(32)).
|
|
148
|
-
Tag("int64", int64(64)).
|
|
149
|
-
Error()
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
b.Log(s)
|
|
153
|
-
}
|
example/about.go
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
. "github.com/pyros2097/wapp"
|
|
5
|
-
. "github.com/pyros2097/wapp/example/components"
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
func About(c *RenderContext) UI {
|
|
9
|
-
return Col(
|
|
10
|
-
Header(c),
|
|
11
|
-
Row(Css("text-5xl text-gray-700"),
|
|
12
|
-
HelmetTitle("wapp-example"),
|
|
13
|
-
HelmetDescription("wapp is a framework"),
|
|
14
|
-
HelmetAuthor("pyros2097"),
|
|
15
|
-
HelmetKeywords("wapp,wapp-example,golang,framework,frontend,ui,wasm,isomorphic"),
|
|
16
|
-
Text("About Me"),
|
|
17
|
-
),
|
|
18
|
-
)
|
|
19
|
-
}
|
example/assets/alpine.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
(()=>{var Ie=!1,Le=!1,Y=[];function yt(e){Ir(e)}function Ir(e){Y.includes(e)||Y.push(e),Lr()}function Lr(){!Le&&!Ie&&(Ie=!0,queueMicrotask($r))}function $r(){Ie=!1,Le=!0;for(let e=0;e<Y.length;e++)Y[e]();Y.length=0,Le=!1}var v,M,z,$e,Fe=!0;function xt(e){Fe=!1,e(),Fe=!0}function bt(e){v=e.reactive,z=e.release,M=t=>e.effect(t,{scheduler:r=>{Fe?yt(r):r()}}),$e=e.raw}function je(e){M=e}function vt(e){let t=()=>{};return[n=>{let i=M(n);e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),z(i))}},()=>{t()}]}var wt=[],Et=[],St=[];function At(e){St.push(e)}function Ot(e){Et.push(e)}function Tt(e){wt.push(e)}function Rt(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function Ke(e,t){!e._x_attributeCleanups||Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&n.forEach(i=>i()),delete e._x_attributeCleanups[r]})}var ze=new MutationObserver(Ct),Ve=!1;function He(){ze.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),Ve=!0}function Fr(){ze.disconnect(),Ve=!1}var J=[],Be=!1;function Kr(){J=J.concat(ze.takeRecords()),J.length&&!Be&&(Be=!0,queueMicrotask(()=>{jr(),Be=!1}))}function jr(){Ct(J),J.length=0}function m(e){if(!Ve)return e();Kr(),Fr();let t=e();return He(),t}function Ct(e){let t=[],r=[],n=new Map,i=new Map;for(let o=0;o<e.length;o++)if(!e[o].target._x_ignoreMutationObserver&&(e[o].type==="childList"&&(e[o].addedNodes.forEach(s=>s.nodeType===1&&t.push(s)),e[o].removedNodes.forEach(s=>s.nodeType===1&&r.push(s))),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,c=e[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{Ke(s,o)}),n.forEach((o,s)=>{wt.forEach(a=>a(s,o))});for(let o of t)r.includes(o)||St.forEach(s=>s(o));for(let o of r)t.includes(o)||Et.forEach(s=>s(o));t=null,r=null,n=null,i=null}function V(e,t,r){return e._x_dataStack=[t,...Z(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function qe(e,t){let r=e._x_dataStack[0];Object.entries(t).forEach(([n,i])=>{r[n]=i})}function Z(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?Z(e.host):e.parentNode?Z(e.parentNode):[]}function Ue(e){return new Proxy({},{ownKeys:()=>Array.from(new Set(e.flatMap(t=>Object.keys(t)))),has:(t,r)=>e.some(n=>n.hasOwnProperty(r)),get:(t,r)=>(e.find(n=>n.hasOwnProperty(r))||{})[r],set:(t,r,n)=>{let i=e.find(o=>o.hasOwnProperty(r));return i?i[r]=n:e[e.length-1][r]=n,!0}})}function Mt(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(n).forEach(([o,s])=>{let a=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,a,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,a)})};return r(e)}function de(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>zr(n,i),s=>We(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function zr(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function We(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),We(e[t[0]],t.slice(1),r)}}var Nt={};function x(e,t){Nt[e]=t}function Q(e,t){return Object.entries(Nt).forEach(([r,n])=>{Object.defineProperty(e,`$${r}`,{get(){return n(t,{Alpine:S,interceptor:de})},enumerable:!1})}),e}function b(e,t,r={}){let n;return h(e,t)(i=>n=i,r),n}function h(...e){return kt(...e)}var kt=Ge;function Dt(e){kt=e}function Ge(e,t){let r={};Q(r,e);let n=[r,...Z(e)];if(typeof t=="function")return Vr(n,t);let i=Hr(n,t);return Br.bind(null,e,t,i)}function Vr(e,t){return(r=()=>{},{scope:n={},params:i=[]}={})=>{let o=t.apply(Ue([n,...e]),i);pe(r,o)}}var Ye={};function qr(e){if(Ye[e])return Ye[e];let t=Object.getPrototypeOf(async function(){}).constructor,r=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)/.test(e)?`(() => { ${e} })()`:e,n=new t(["__self","scope"],`with (scope) { __self.result = ${r} }; __self.finished = true; return __self.result;`);return Ye[e]=n,n}function Hr(e,t){let r=qr(t);return(n=()=>{},{scope:i={},params:o=[]}={})=>{r.result=void 0,r.finished=!1;let s=Ue([i,...e]),a=r(r,s);r.finished?pe(n,r.result,s,o):a.then(c=>{pe(n,c,s,o)})}}function pe(e,t,r,n){if(typeof t=="function"){let i=t.apply(r,n);i instanceof Promise?i.then(o=>pe(e,o,r,n)):e(i)}else e(t)}function Br(e,t,r,...n){try{return r(...n)}catch(i){throw console.warn(`Alpine Expression Error: ${i.message}
|
|
2
|
+
|
|
3
|
+
Expression: "${t}"
|
|
4
|
+
|
|
5
|
+
`,e),i}}var Je="x-";function A(e=""){return Je+e}function Pt(e){Je=e}var It={};function p(e,t){It[e]=t}function X(e,t,r){let n={};return Array.from(t).map(Lt((o,s)=>n[o]=s)).filter($t).map(Wr(n,r)).sort(Gr).map(o=>Ur(e,o))}function Ft(e){return Array.from(e).map(Lt()).filter(t=>!$t(t))}var Ze=!1,ee=new Map,jt=Symbol();function Kt(e){Ze=!0;let t=Symbol();jt=t,ee.set(t,[]);let r=()=>{for(;ee.get(t).length;)ee.get(t).shift()();ee.delete(t)},n=()=>{Ze=!1,r()};e(r),n()}function Ur(e,t){let r=()=>{},n=It[t.type]||r,i=[],o=d=>i.push(d),[s,a]=vt(e);i.push(a);let c={Alpine:S,effect:s,cleanup:o,evaluateLater:h.bind(h,e),evaluate:b.bind(b,e)},l=()=>i.forEach(d=>d());Rt(e,t.original,l);let u=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,c),n=n.bind(n,e,t,c),Ze?ee.get(jt).push(n):n())};return u.runCleanups=l,u}var me=(e,t)=>({name:r,value:n})=>(r.startsWith(e)&&(r=r.replace(e,t)),{name:r,value:n}),he=e=>e;function Lt(e=()=>{}){return({name:t,value:r})=>{let{name:n,value:i}=zt.reduce((o,s)=>s(o),{name:t,value:r});return n!==t&&e(n,t),{name:n,value:i}}}var zt=[];function H(e){zt.push(e)}function $t({name:e}){return Vt().test(e)}var Vt=()=>new RegExp(`^${Je}([^:^.]+)\\b`);function Wr(e,t){return({name:r,value:n})=>{let i=r.match(Vt()),o=r.match(/:([a-zA-Z0-9\-:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(c=>c.replace(".","")),expression:n,original:a}}}var Qe="DEFAULT",ge=["ignore","ref","data","bind","init","for","model","transition","show","if",Qe,"element"];function Gr(e,t){let r=ge.indexOf(e.type)===-1?Qe:e.type,n=ge.indexOf(t.type)===-1?Qe:t.type;return ge.indexOf(r)-ge.indexOf(n)}function $(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}var Xe=[],et=!1;function B(e){Xe.push(e),queueMicrotask(()=>{et||setTimeout(()=>{_e()})})}function _e(){for(et=!1;Xe.length;)Xe.shift()()}function Ht(){et=!0}function k(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>k(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)k(n,t,!1),n=n.nextElementSibling}function ye(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}function Bt(){document.body||ye("Unable to initialize. Trying to load Alpine before `<body>` is available. Did you forget to add `defer` in Alpine's `<script>` tag?"),$(document,"alpine:init"),$(document,"alpine:initializing"),He(),At(t=>T(t,k)),Ot(t=>B(()=>Jr(t))),Tt((t,r)=>{X(t,r).forEach(n=>n())});let e=t=>!O(t.parentElement);Array.from(document.querySelectorAll(Yr())).filter(e).forEach(t=>{T(t)}),$(document,"alpine:initialized")}var tt=[],qt=[];function Ut(){return tt.map(e=>e())}function Yr(){return tt.concat(qt).map(e=>e())}function xe(e){tt.push(e)}function Wt(e){qt.push(e)}function O(e){if(!!e){if(Ut().some(t=>e.matches(t)))return e;if(!!e.parentElement)return O(e.parentElement)}}function Gt(e){return Ut().some(t=>e.matches(t))}function T(e,t=k){Kt(()=>{t(e,(r,n)=>{X(r,r.attributes).forEach(i=>i()),r._x_ignore&&n()})})}function Jr(e){k(e,t=>Ke(t))}function be(e,t){var r;return function(){var n=this,i=arguments,o=function(){r=null,e.apply(n,i)};clearTimeout(r),r=setTimeout(o,t)}}function ve(e,t){let r;return function(){let n=this,i=arguments;r||(e.apply(n,i),r=!0,setTimeout(()=>r=!1,t))}}function Yt(e){e(S)}var q={},Jt=!1;function Zt(e,t){if(Jt||(q=v(q),Jt=!0),t===void 0)return q[e];q[e]=t,typeof t=="object"&&t!==null&&t.hasOwnProperty("init")&&typeof t.init=="function"&&q[e].init()}function Qt(){return q}var rt=!1;function U(e){return(...t)=>rt||e(...t)}function Xt(e,t){t._x_dataStack=e._x_dataStack,rt=!0,Qr(()=>{Zr(t)}),rt=!1}function Zr(e){let t=!1;T(e,(n,i)=>{k(n,(o,s)=>{if(t&&Gt(o))return s();t=!0,i(o,s)})})}function Qr(e){let t=M;je((r,n)=>{let i=t(r);return z(i),()=>{}}),e(),je(t)}var er={};function tr(e,t){er[e]=t}function rr(e,t){return Object.entries(er).forEach(([r,n])=>{Object.defineProperty(e,r,{get(){return(...i)=>n.bind(t)(...i)},enumerable:!1})}),e}var Xr={get reactive(){return v},get release(){return z},get effect(){return M},get raw(){return $e},version:"3.3.3",disableEffectScheduling:xt,setReactivityEngine:bt,addRootSelector:xe,mapAttributes:H,evaluateLater:h,setEvaluator:Dt,closestRoot:O,interceptor:de,mutateDom:m,directive:p,throttle:ve,debounce:be,evaluate:b,initTree:T,nextTick:B,prefix:Pt,plugin:Yt,magic:x,store:Zt,start:Bt,clone:Xt,data:tr},S=Xr;function nt(e,t){let r=Object.create(null),n=e.split(",");for(let i=0;i<n.length;i++)r[n[i]]=!0;return t?i=>!!r[i.toLowerCase()]:i=>!!r[i]}var so={[1]:"TEXT",[2]:"CLASS",[4]:"STYLE",[8]:"PROPS",[16]:"FULL_PROPS",[32]:"HYDRATE_EVENTS",[64]:"STABLE_FRAGMENT",[128]:"KEYED_FRAGMENT",[256]:"UNKEYED_FRAGMENT",[512]:"NEED_PATCH",[1024]:"DYNAMIC_SLOTS",[2048]:"DEV_ROOT_FRAGMENT",[-1]:"HOISTED",[-2]:"BAIL"},ao={[1]:"STABLE",[2]:"DYNAMIC",[3]:"FORWARDED"};var en="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly";var co=nt(en+",async,autofocus,autoplay,controls,default,defer,disabled,hidden,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected");var nr=Object.freeze({}),lo=Object.freeze([]);var it=Object.assign;var tn=Object.prototype.hasOwnProperty,te=(e,t)=>tn.call(e,t),D=Array.isArray,W=e=>ir(e)==="[object Map]";var rn=e=>typeof e=="string",we=e=>typeof e=="symbol",re=e=>e!==null&&typeof e=="object";var nn=Object.prototype.toString,ir=e=>nn.call(e),ot=e=>ir(e).slice(8,-1);var Ee=e=>rn(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e;var Se=e=>{let t=Object.create(null);return r=>t[r]||(t[r]=e(r))},on=/-(\w)/g,uo=Se(e=>e.replace(on,(t,r)=>r?r.toUpperCase():"")),sn=/\B([A-Z])/g,fo=Se(e=>e.replace(sn,"-$1").toLowerCase()),st=Se(e=>e.charAt(0).toUpperCase()+e.slice(1)),po=Se(e=>e?`on${st(e)}`:""),at=(e,t)=>e!==t&&(e===e||t===t);var ct=new WeakMap,ne=[],R,F=Symbol("iterate"),lt=Symbol("Map key iterate");function an(e){return e&&e._isEffect===!0}function or(e,t=nr){an(e)&&(e=e.raw);let r=cn(e,t);return t.lazy||r(),r}function ar(e){e.active&&(sr(e),e.options.onStop&&e.options.onStop(),e.active=!1)}var ln=0;function cn(e,t){let r=function(){if(!r.active)return e();if(!ne.includes(r)){sr(r);try{return un(),ne.push(r),R=r,e()}finally{ne.pop(),cr(),R=ne[ne.length-1]}}};return r.id=ln++,r.allowRecurse=!!t.allowRecurse,r._isEffect=!0,r.active=!0,r.raw=e,r.deps=[],r.options=t,r}function sr(e){let{deps:t}=e;if(t.length){for(let r=0;r<t.length;r++)t[r].delete(e);t.length=0}}var G=!0,ut=[];function fn(){ut.push(G),G=!1}function un(){ut.push(G),G=!0}function cr(){let e=ut.pop();G=e===void 0?!0:e}function w(e,t,r){if(!G||R===void 0)return;let n=ct.get(e);n||ct.set(e,n=new Map);let i=n.get(r);i||n.set(r,i=new Set),i.has(R)||(i.add(R),R.deps.push(i),R.options.onTrack&&R.options.onTrack({effect:R,target:e,type:t,key:r}))}function P(e,t,r,n,i,o){let s=ct.get(e);if(!s)return;let a=new Set,c=u=>{u&&u.forEach(d=>{(d!==R||d.allowRecurse)&&a.add(d)})};if(t==="clear")s.forEach(c);else if(r==="length"&&D(e))s.forEach((u,d)=>{(d==="length"||d>=n)&&c(u)});else switch(r!==void 0&&c(s.get(r)),t){case"add":D(e)?Ee(r)&&c(s.get("length")):(c(s.get(F)),W(e)&&c(s.get(lt)));break;case"delete":D(e)||(c(s.get(F)),W(e)&&c(s.get(lt)));break;case"set":W(e)&&c(s.get(F));break}let l=u=>{u.options.onTrigger&&u.options.onTrigger({effect:u,target:e,key:r,type:t,newValue:n,oldValue:i,oldTarget:o}),u.options.scheduler?u.options.scheduler(u):u()};a.forEach(l)}var dn=nt("__proto__,__v_isRef,__isVue"),lr=new Set(Object.getOwnPropertyNames(Symbol).map(e=>Symbol[e]).filter(we)),pn=Ae(),mn=Ae(!1,!0),hn=Ae(!0),gn=Ae(!0,!0),Oe={};["includes","indexOf","lastIndexOf"].forEach(e=>{let t=Array.prototype[e];Oe[e]=function(...r){let n=g(this);for(let o=0,s=this.length;o<s;o++)w(n,"get",o+"");let i=t.apply(n,r);return i===-1||i===!1?t.apply(n,r.map(g)):i}});["push","pop","shift","unshift","splice"].forEach(e=>{let t=Array.prototype[e];Oe[e]=function(...r){fn();let n=t.apply(this,r);return cr(),n}});function Ae(e=!1,t=!1){return function(n,i,o){if(i==="__v_isReactive")return!e;if(i==="__v_isReadonly")return e;if(i==="__v_raw"&&o===(e?t?yn:fr:t?_n:ur).get(n))return n;let s=D(n);if(!e&&s&&te(Oe,i))return Reflect.get(Oe,i,o);let a=Reflect.get(n,i,o);return(we(i)?lr.has(i):dn(i))||(e||w(n,"get",i),t)?a:ft(a)?!s||!Ee(i)?a.value:a:re(a)?e?dr(a):Te(a):a}}var xn=pr(),bn=pr(!0);function pr(e=!1){return function(r,n,i,o){let s=r[n];if(!e&&(i=g(i),s=g(s),!D(r)&&ft(s)&&!ft(i)))return s.value=i,!0;let a=D(r)&&Ee(n)?Number(n)<r.length:te(r,n),c=Reflect.set(r,n,i,o);return r===g(o)&&(a?at(i,s)&&P(r,"set",n,i,s):P(r,"add",n,i)),c}}function vn(e,t){let r=te(e,t),n=e[t],i=Reflect.deleteProperty(e,t);return i&&r&&P(e,"delete",t,void 0,n),i}function wn(e,t){let r=Reflect.has(e,t);return(!we(t)||!lr.has(t))&&w(e,"has",t),r}function En(e){return w(e,"iterate",D(e)?"length":F),Reflect.ownKeys(e)}var mr={get:pn,set:xn,deleteProperty:vn,has:wn,ownKeys:En},hr={get:hn,set(e,t){return console.warn(`Set operation on key "${String(t)}" failed: target is readonly.`,e),!0},deleteProperty(e,t){return console.warn(`Delete operation on key "${String(t)}" failed: target is readonly.`,e),!0}},xo=it({},mr,{get:mn,set:bn}),bo=it({},hr,{get:gn}),dt=e=>re(e)?Te(e):e,pt=e=>re(e)?dr(e):e,mt=e=>e,Re=e=>Reflect.getPrototypeOf(e);function Ce(e,t,r=!1,n=!1){e=e.__v_raw;let i=g(e),o=g(t);t!==o&&!r&&w(i,"get",t),!r&&w(i,"get",o);let{has:s}=Re(i),a=n?mt:r?pt:dt;if(s.call(i,t))return a(e.get(t));if(s.call(i,o))return a(e.get(o));e!==i&&e.get(t)}function Me(e,t=!1){let r=this.__v_raw,n=g(r),i=g(e);return e!==i&&!t&&w(n,"has",e),!t&&w(n,"has",i),e===i?r.has(e):r.has(e)||r.has(i)}function Ne(e,t=!1){return e=e.__v_raw,!t&&w(g(e),"iterate",F),Reflect.get(e,"size",e)}function gr(e){e=g(e);let t=g(this);return Re(t).has.call(t,e)||(t.add(e),P(t,"add",e,e)),this}function yr(e,t){t=g(t);let r=g(this),{has:n,get:i}=Re(r),o=n.call(r,e);o?_r(r,n,e):(e=g(e),o=n.call(r,e));let s=i.call(r,e);return r.set(e,t),o?at(t,s)&&P(r,"set",e,t,s):P(r,"add",e,t),this}function xr(e){let t=g(this),{has:r,get:n}=Re(t),i=r.call(t,e);i?_r(t,r,e):(e=g(e),i=r.call(t,e));let o=n?n.call(t,e):void 0,s=t.delete(e);return i&&P(t,"delete",e,void 0,o),s}function br(){let e=g(this),t=e.size!==0,r=W(e)?new Map(e):new Set(e),n=e.clear();return t&&P(e,"clear",void 0,void 0,r),n}function ke(e,t){return function(n,i){let o=this,s=o.__v_raw,a=g(s),c=t?mt:e?pt:dt;return!e&&w(a,"iterate",F),s.forEach((l,u)=>n.call(i,c(l),c(u),o))}}function De(e,t,r){return function(...n){let i=this.__v_raw,o=g(i),s=W(o),a=e==="entries"||e===Symbol.iterator&&s,c=e==="keys"&&s,l=i[e](...n),u=r?mt:t?pt:dt;return!t&&w(o,"iterate",c?lt:F),{next(){let{value:d,done:E}=l.next();return E?{value:d,done:E}:{value:a?[u(d[0]),u(d[1])]:u(d),done:E}},[Symbol.iterator](){return this}}}}function I(e){return function(...t){{let r=t[0]?`on key "${t[0]}" `:"";console.warn(`${st(e)} operation ${r}failed: target is readonly.`,g(this))}return e==="delete"?!1:this}}var vr={get(e){return Ce(this,e)},get size(){return Ne(this)},has:Me,add:gr,set:yr,delete:xr,clear:br,forEach:ke(!1,!1)},wr={get(e){return Ce(this,e,!1,!0)},get size(){return Ne(this)},has:Me,add:gr,set:yr,delete:xr,clear:br,forEach:ke(!1,!0)},Er={get(e){return Ce(this,e,!0)},get size(){return Ne(this,!0)},has(e){return Me.call(this,e,!0)},add:I("add"),set:I("set"),delete:I("delete"),clear:I("clear"),forEach:ke(!0,!1)},Sr={get(e){return Ce(this,e,!0,!0)},get size(){return Ne(this,!0)},has(e){return Me.call(this,e,!0)},add:I("add"),set:I("set"),delete:I("delete"),clear:I("clear"),forEach:ke(!0,!0)},Sn=["keys","values","entries",Symbol.iterator];Sn.forEach(e=>{vr[e]=De(e,!1,!1),Er[e]=De(e,!0,!1),wr[e]=De(e,!1,!0),Sr[e]=De(e,!0,!0)});function Pe(e,t){let r=t?e?Sr:wr:e?Er:vr;return(n,i,o)=>i==="__v_isReactive"?!e:i==="__v_isReadonly"?e:i==="__v_raw"?n:Reflect.get(te(r,i)&&i in n?r:n,i,o)}var An={get:Pe(!1,!1)},vo={get:Pe(!1,!0)},On={get:Pe(!0,!1)},wo={get:Pe(!0,!0)};function _r(e,t,r){let n=g(r);if(n!==r&&t.call(e,n)){let i=ot(e);console.warn(`Reactive ${i} contains both the raw and reactive versions of the same object${i==="Map"?" as keys":""}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.`)}}var ur=new WeakMap,_n=new WeakMap,fr=new WeakMap,yn=new WeakMap;function Tn(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Rn(e){return e.__v_skip||!Object.isExtensible(e)?0:Tn(ot(e))}function Te(e){return e&&e.__v_isReadonly?e:Ar(e,!1,mr,An,ur)}function dr(e){return Ar(e,!0,hr,On,fr)}function Ar(e,t,r,n,i){if(!re(e))return console.warn(`value cannot be made reactive: ${String(e)}`),e;if(e.__v_raw&&!(t&&e.__v_isReactive))return e;let o=i.get(e);if(o)return o;let s=Rn(e);if(s===0)return e;let a=new Proxy(e,s===2?n:r);return i.set(e,a),a}function g(e){return e&&g(e.__v_raw)||e}function ft(e){return Boolean(e&&e.__v_isRef===!0)}x("nextTick",()=>B);x("dispatch",e=>$.bind($,e));x("watch",e=>(t,r)=>{let n=h(e,t),i=!0,o;M(()=>n(s=>{let a=document.createElement("div");a.dataset.throwAway=s,i?o=s:queueMicrotask(()=>{r(s,o),o=s}),i=!1}))});x("store",Qt);x("root",e=>O(e));x("refs",e=>O(e)._x_refs||{});x("el",e=>e);function ie(e,t){return Array.isArray(t)?Or(e,t.join(" ")):typeof t=="object"&&t!==null?Cn(e,t):typeof t=="function"?ie(e,t()):Or(e,t)}function Or(e,t){let r=o=>o.split(" ").filter(Boolean),n=o=>o.split(" ").filter(s=>!e.classList.contains(s)).filter(Boolean),i=o=>(e.classList.add(...o),()=>{e.classList.remove(...o)});return t=t===!0?t="":t||"",i(n(t))}function Cn(e,t){let r=a=>a.split(" ").filter(Boolean),n=Object.entries(t).flatMap(([a,c])=>c?r(a):!1).filter(Boolean),i=Object.entries(t).flatMap(([a,c])=>c?!1:r(a)).filter(Boolean),o=[],s=[];return i.forEach(a=>{e.classList.contains(a)&&(e.classList.remove(a),s.push(a))}),n.forEach(a=>{e.classList.contains(a)||(e.classList.add(a),o.push(a))}),()=>{s.forEach(a=>e.classList.add(a)),o.forEach(a=>e.classList.remove(a))}}function oe(e,t){return typeof t=="object"&&t!==null?Mn(e,t):Nn(e,t)}function Mn(e,t){let r={};return Object.entries(t).forEach(([n,i])=>{r[n]=e.style[n],e.style.setProperty(kn(n),i)}),setTimeout(()=>{e.style.length===0&&e.removeAttribute("style")}),()=>{oe(e,r)}}function Nn(e,t){let r=e.getAttribute("style",t);return e.setAttribute("style",t),()=>{e.setAttribute("style",r)}}function kn(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function se(e,t=()=>{}){let r=!1;return function(){r?t.apply(this,arguments):(r=!0,e.apply(this,arguments))}}p("transition",(e,{value:t,modifiers:r,expression:n},{evaluate:i})=>{typeof n=="function"&&(n=i(n)),n?Dn(e,n,t):Pn(e,r,t)});function Dn(e,t,r){Tr(e,ie,""),{enter:i=>{e._x_transition.enter.during=i},"enter-start":i=>{e._x_transition.enter.start=i},"enter-end":i=>{e._x_transition.enter.end=i},leave:i=>{e._x_transition.leave.during=i},"leave-start":i=>{e._x_transition.leave.start=i},"leave-end":i=>{e._x_transition.leave.end=i}}[r](t)}function Pn(e,t,r){Tr(e,oe);let n=!t.includes("in")&&!t.includes("out")&&!r,i=n||t.includes("in")||["enter"].includes(r),o=n||t.includes("out")||["leave"].includes(r);t.includes("in")&&!n&&(t=t.filter((_,y)=>y<t.indexOf("out"))),t.includes("out")&&!n&&(t=t.filter((_,y)=>y>t.indexOf("out")));let s=!t.includes("opacity")&&!t.includes("scale"),a=s||t.includes("opacity"),c=s||t.includes("scale"),l=a?0:1,u=c?ae(t,"scale",95)/100:1,d=ae(t,"delay",0),E=ae(t,"origin","center"),L="opacity, transform",j=ae(t,"duration",150)/1e3,ue=ae(t,"duration",75)/1e3,f="cubic-bezier(0.4, 0.0, 0.2, 1)";i&&(e._x_transition.enter.during={transformOrigin:E,transitionDelay:d,transitionProperty:L,transitionDuration:`${j}s`,transitionTimingFunction:f},e._x_transition.enter.start={opacity:l,transform:`scale(${u})`},e._x_transition.enter.end={opacity:1,transform:"scale(1)"}),o&&(e._x_transition.leave.during={transformOrigin:E,transitionDelay:d,transitionProperty:L,transitionDuration:`${ue}s`,transitionTimingFunction:f},e._x_transition.leave.start={opacity:1,transform:"scale(1)"},e._x_transition.leave.end={opacity:l,transform:`scale(${u})`})}function Tr(e,t,r={}){e._x_transition||(e._x_transition={enter:{during:r,start:r,end:r},leave:{during:r,start:r,end:r},in(n=()=>{},i=()=>{}){Rr(e,t,{during:this.enter.during,start:this.enter.start,end:this.enter.end,entering:!0},n,i)},out(n=()=>{},i=()=>{}){Rr(e,t,{during:this.leave.during,start:this.leave.start,end:this.leave.end,entering:!1},n,i)}})}window.Element.prototype._x_toggleAndCascadeWithTransitions=function(e,t,r,n){let i=()=>requestAnimationFrame(r);if(t){e._x_transition?e._x_transition.in(r):i();return}e._x_hidePromise=e._x_transition?new Promise((o,s)=>{e._x_transition.out(()=>{},()=>o(n)),e._x_transitioning.beforeCancel(()=>s({isFromCancelledTransition:!0}))}):Promise.resolve(n),queueMicrotask(()=>{let o=Cr(e);o?(o._x_hideChildren||(o._x_hideChildren=[]),o._x_hideChildren.push(e)):queueMicrotask(()=>{let s=a=>{let c=Promise.all([a._x_hidePromise,...(a._x_hideChildren||[]).map(s)]).then(([l])=>l());return delete a._x_hidePromise,delete a._x_hideChildren,c};s(e).catch(a=>{if(!a.isFromCancelledTransition)throw a})})})};function Cr(e){let t=e.parentNode;if(!!t)return t._x_hidePromise?t:Cr(t)}function Rr(e,t,{during:r,start:n,end:i,entering:o}={},s=()=>{},a=()=>{}){if(e._x_transitioning&&e._x_transitioning.cancel(),Object.keys(r).length===0&&Object.keys(n).length===0&&Object.keys(i).length===0){s(),a();return}let c,l,u;In(e,{start(){c=t(e,n)},during(){l=t(e,r)},before:s,end(){c(),u=t(e,i)},after:a,cleanup(){l(),u()}},o)}function In(e,t,r){let n,i,o,s=se(()=>{m(()=>{n=!0,i||t.before(),o||(t.end(),_e()),t.after(),e.isConnected&&t.cleanup(),delete e._x_transitioning})});e._x_transitioning={beforeCancels:[],beforeCancel(a){this.beforeCancels.push(a)},cancel:se(function(){for(;this.beforeCancels.length;)this.beforeCancels.shift()();s()}),finish:s,entering:r},m(()=>{t.start(),t.during()}),Ht(),requestAnimationFrame(()=>{if(n)return;let a=Number(getComputedStyle(e).transitionDuration.replace(/,.*/,"").replace("s",""))*1e3,c=Number(getComputedStyle(e).transitionDelay.replace(/,.*/,"").replace("s",""))*1e3;a===0&&(a=Number(getComputedStyle(e).animationDuration.replace("s",""))*1e3),m(()=>{t.before()}),i=!0,requestAnimationFrame(()=>{n||(m(()=>{t.end()}),_e(),setTimeout(e._x_transitioning.finish,a+c),o=!0)})})}function ae(e,t,r){if(e.indexOf(t)===-1)return r;let n=e[e.indexOf(t)+1];if(!n||t==="scale"&&isNaN(n))return r;if(t==="duration"){let i=n.match(/([0-9]+)ms/);if(i)return i[1]}return t==="origin"&&["top","right","left","center","bottom"].includes(e[e.indexOf(t)+2])?[n,e[e.indexOf(t)+2]].join(" "):n}var Mr=()=>{};Mr.inline=(e,{modifiers:t},{cleanup:r})=>{t.includes("self")?e._x_ignoreSelf=!0:e._x_ignore=!0,r(()=>{t.includes("self")?delete e._x_ignoreSelf:delete e._x_ignore})};p("ignore",Mr);p("effect",(e,{expression:t},{effect:r})=>r(h(e,t)));function ce(e,t,r,n=[]){switch(e._x_bindings||(e._x_bindings=v({})),e._x_bindings[t]=r,t=n.includes("camel")?Kn(t):t,t){case"value":Ln(e,r);break;case"style":Fn(e,r);break;case"class":$n(e,r);break;default:jn(e,t,r);break}}function Ln(e,t){if(e.type==="radio")e.attributes.value===void 0&&(e.value=t),window.fromModel&&(e.checked=Nr(e.value,t));else if(e.type==="checkbox")Number.isInteger(t)?e.value=t:!Number.isInteger(t)&&!Array.isArray(t)&&typeof t!="boolean"&&![null,void 0].includes(t)?e.value=String(t):Array.isArray(t)?e.checked=t.some(r=>Nr(r,e.value)):e.checked=!!t;else if(e.tagName==="SELECT")zn(e,t);else{if(e.value===t)return;e.value=t}}function $n(e,t){e._x_undoAddedClasses&&e._x_undoAddedClasses(),e._x_undoAddedClasses=ie(e,t)}function Fn(e,t){e._x_undoAddedStyles&&e._x_undoAddedStyles(),e._x_undoAddedStyles=oe(e,t)}function jn(e,t,r){[null,void 0,!1].includes(r)&&Bn(t)?e.removeAttribute(t):(Hn(t)&&(r=t),Vn(e,t,r))}function Vn(e,t,r){e.getAttribute(t)!=r&&e.setAttribute(t,r)}function zn(e,t){let r=[].concat(t).map(n=>n+"");Array.from(e.options).forEach(n=>{n.selected=r.includes(n.value)})}function Kn(e){return e.toLowerCase().replace(/-(\w)/g,(t,r)=>r.toUpperCase())}function Nr(e,t){return e==t}function Hn(e){return["disabled","checked","required","readonly","hidden","open","selected","autofocus","itemscope","multiple","novalidate","allowfullscreen","allowpaymentrequest","formnovalidate","autoplay","controls","loop","muted","playsinline","default","ismap","reversed","async","defer","nomodule"].includes(e)}function Bn(e){return!["aria-pressed","aria-checked"].includes(e)}function le(e,t,r,n){let i=e,o=c=>n(c),s={},a=(c,l)=>u=>l(c,u);if(r.includes("dot")&&(t=qn(t)),r.includes("camel")&&(t=Un(t)),r.includes("passive")&&(s.passive=!0),r.includes("window")&&(i=window),r.includes("document")&&(i=document),r.includes("prevent")&&(o=a(o,(c,l)=>{l.preventDefault(),c(l)})),r.includes("stop")&&(o=a(o,(c,l)=>{l.stopPropagation(),c(l)})),r.includes("self")&&(o=a(o,(c,l)=>{l.target===e&&c(l)})),(r.includes("away")||r.includes("outside"))&&(i=document,o=a(o,(c,l)=>{e.contains(l.target)||e.offsetWidth<1&&e.offsetHeight<1||c(l)})),o=a(o,(c,l)=>{Wn(t)&&Gn(l,r)||c(l)}),r.includes("debounce")){let c=r[r.indexOf("debounce")+1]||"invalid-wait",l=ht(c.split("ms")[0])?Number(c.split("ms")[0]):250;o=be(o,l)}if(r.includes("throttle")){let c=r[r.indexOf("throttle")+1]||"invalid-wait",l=ht(c.split("ms")[0])?Number(c.split("ms")[0]):250;o=ve(o,l)}return r.includes("once")&&(o=a(o,(c,l)=>{c(l),i.removeEventListener(t,o,s)})),i.addEventListener(t,o,s),()=>{i.removeEventListener(t,o,s)}}function qn(e){return e.replace(/-/g,".")}function Un(e){return e.toLowerCase().replace(/-(\w)/g,(t,r)=>r.toUpperCase())}function ht(e){return!Array.isArray(e)&&!isNaN(e)}function Yn(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[_\s]/,"-").toLowerCase()}function Wn(e){return["keydown","keyup"].includes(e)}function Gn(e,t){let r=t.filter(o=>!["window","document","prevent","stop","once"].includes(o));if(r.includes("debounce")){let o=r.indexOf("debounce");r.splice(o,ht((r[o+1]||"invalid-wait").split("ms")[0])?2:1)}if(r.length===0||r.length===1&&kr(e.key).includes(r[0]))return!1;let i=["ctrl","shift","alt","meta","cmd","super"].filter(o=>r.includes(o));return r=r.filter(o=>!i.includes(o)),!(i.length>0&&i.filter(s=>((s==="cmd"||s==="super")&&(s="meta"),e[`${s}Key`])).length===i.length&&kr(e.key).includes(r[0]))}function kr(e){if(!e)return[];e=Yn(e);let t={ctrl:"control",slash:"/",space:"-",spacebar:"-",cmd:"meta",esc:"escape",up:"arrow-up",down:"arrow-down",left:"arrow-left",right:"arrow-right",period:".",equal:"="};return t[e]=e,Object.keys(t).map(r=>{if(t[r]===e)return r}).filter(r=>r)}p("model",(e,{modifiers:t,expression:r},{effect:n,cleanup:i})=>{let o=h(e,r),s=`${r} = rightSideOfExpression($event, ${r})`,a=h(e,s);var c=e.tagName.toLowerCase()==="select"||["checkbox","radio"].includes(e.type)||t.includes("lazy")?"change":"input";let l=Jn(e,t,r),u=le(e,c,t,d=>{a(()=>{},{scope:{$event:d,rightSideOfExpression:l}})});i(()=>u()),e._x_forceModelUpdate=()=>{o(d=>{d===void 0&&r.match(/\./)&&(d=""),window.fromModel=!0,m(()=>ce(e,"value",d)),delete window.fromModel})},n(()=>{t.includes("unintrusive")&&document.activeElement.isSameNode(e)||e._x_forceModelUpdate()})});function Jn(e,t,r){return e.type==="radio"&&m(()=>{e.hasAttribute("name")||e.setAttribute("name",r)}),(n,i)=>m(()=>{if(n instanceof CustomEvent&&n.detail!==void 0)return n.detail||n.target.value;if(e.type==="checkbox")if(Array.isArray(i)){let o=t.includes("number")?gt(n.target.value):n.target.value;return n.target.checked?i.concat([o]):i.filter(s=>!Zn(s,o))}else return n.target.checked;else{if(e.tagName.toLowerCase()==="select"&&e.multiple)return t.includes("number")?Array.from(n.target.selectedOptions).map(o=>{let s=o.value||o.text;return gt(s)}):Array.from(n.target.selectedOptions).map(o=>o.value||o.text);{let o=n.target.value;return t.includes("number")?gt(o):t.includes("trim")?o.trim():o}}})}function gt(e){let t=e?parseFloat(e):null;return Qn(t)?t:e}function Zn(e,t){return e==t}function Qn(e){return!Array.isArray(e)&&!isNaN(e)}p("cloak",e=>queueMicrotask(()=>m(()=>e.removeAttribute(A("cloak")))));Wt(()=>`[${A("init")}]`);p("init",U((e,{expression:t})=>typeof t=="string"?!!t.trim()&&b(e,t,{},!1):b(e,t,{},!1)));p("text",(e,{expression:t},{effect:r,evaluateLater:n})=>{let i=n(t);r(()=>{i(o=>{m(()=>{e.textContent=o})})})});p("html",(e,{expression:t},{effect:r,evaluateLater:n})=>{let i=n(t);r(()=>{i(o=>{e.innerHTML=o})})});H(me(":",he(A("bind:"))));p("bind",(e,{value:t,modifiers:r,expression:n,original:i},{effect:o})=>{if(!t)return Xn(e,n,i,o);if(t==="key")return ei(e,n);let s=h(e,n);o(()=>s(a=>{a===void 0&&n.match(/\./)&&(a=""),m(()=>ce(e,t,a,r))}))});function Xn(e,t,r,n){let i=h(e,t),o=[];n(()=>{for(;o.length;)o.pop()();i(s=>{let a=Object.entries(s).map(([c,l])=>({name:c,value:l}));Ft(a).forEach(({name:c,value:l},u)=>{a[u]={name:`x-bind:${c}`,value:`"${l}"`}}),X(e,a,r).map(c=>{o.push(c.runCleanups),c()})})})}function ei(e,t){e._x_keyExpression=t}xe(()=>`[${A("data")}]`);p("data",U((e,{expression:t},{cleanup:r})=>{t=t===""?"{}":t;let n={};Q(n,e);let i={};rr(i,n);let o=b(e,t,{scope:i});Q(o,e);let s=v(o);Mt(s);let a=V(e,s);s.init&&b(e,s.init),r(()=>{a(),s.destroy&&b(e,s.destroy)})}));p("show",(e,{modifiers:t,expression:r},{effect:n})=>{let i=h(e,r),o=()=>m(()=>{e.style.display="none",e._x_isShown=!1}),s=()=>m(()=>{e.style.length===1&&e.style.display==="none"?e.removeAttribute("style"):e.style.removeProperty("display"),e._x_isShown=!0}),a=()=>setTimeout(s),c=se(d=>d?s():o(),d=>{typeof e._x_toggleAndCascadeWithTransitions=="function"?e._x_toggleAndCascadeWithTransitions(e,d,s,o):d?a():o()}),l,u=!0;n(()=>i(d=>{!u&&d===l||(t.includes("immediate")&&(d?a():o()),c(d),l=d,u=!1)}))});p("for",(e,{expression:t},{effect:r,cleanup:n})=>{let i=ri(t),o=h(e,i.items),s=h(e,e._x_keyExpression||"index");e._x_prevKeys=[],e._x_lookup={},r(()=>ti(e,i,o,s)),n(()=>{Object.values(e._x_lookup).forEach(a=>a.remove()),delete e._x_prevKeys,delete e._x_lookup})});function ti(e,t,r,n){let i=s=>typeof s=="object"&&!Array.isArray(s),o=e;r(s=>{ni(s)&&s>=0&&(s=Array.from(Array(s).keys(),f=>f+1)),s===void 0&&(s=[]);let a=e._x_lookup,c=e._x_prevKeys,l=[],u=[];if(i(s))s=Object.entries(s).map(([f,_])=>{let y=Dr(t,_,f,s);n(C=>u.push(C),{scope:{index:f,...y}}),l.push(y)});else for(let f=0;f<s.length;f++){let _=Dr(t,s[f],f,s);n(y=>u.push(y),{scope:{index:f,..._}}),l.push(_)}let d=[],E=[],L=[],j=[];for(let f=0;f<c.length;f++){let _=c[f];u.indexOf(_)===-1&&L.push(_)}c=c.filter(f=>!L.includes(f));let ue="template";for(let f=0;f<u.length;f++){let _=u[f],y=c.indexOf(_);if(y===-1)c.splice(f,0,_),d.push([ue,f]);else if(y!==f){let C=c.splice(f,1)[0],N=c.splice(y-1,1)[0];c.splice(f,0,N),c.splice(y,0,C),E.push([C,N])}else j.push(_);ue=_}for(let f=0;f<L.length;f++){let _=L[f];a[_].remove(),a[_]=null,delete a[_]}for(let f=0;f<E.length;f++){let[_,y]=E[f],C=a[_],N=a[y],K=document.createElement("div");m(()=>{N.after(K),C.after(N),K.before(C),K.remove()}),qe(N,l[u.indexOf(y)])}for(let f=0;f<d.length;f++){let[_,y]=d[f],C=_==="template"?o:a[_],N=l[y],K=u[y],fe=document.importNode(o.content,!0).firstElementChild;V(fe,v(N),o),m(()=>{C.after(fe),T(fe)}),typeof K=="object"&&ye("x-for key cannot be an object, it must be a string or an integer",o),a[K]=fe}for(let f=0;f<j.length;f++)qe(a[j[f]],l[u.indexOf(j[f])]);o._x_prevKeys=u})}function ri(e){let t=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,r=/^\s*\(|\)\s*$/g,n=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,i=e.match(n);if(!i)return;let o={};o.items=i[2].trim();let s=i[1].replace(r,"").trim(),a=s.match(t);return a?(o.item=s.replace(t,"").trim(),o.index=a[1].trim(),a[2]&&(o.collection=a[2].trim())):o.item=s,o}function Dr(e,t,r,n){let i={};return/^\[.*\]$/.test(e.item)&&Array.isArray(t)?e.item.replace("[","").replace("]","").split(",").map(s=>s.trim()).forEach((s,a)=>{i[s]=t[a]}):/^\{.*\}$/.test(e.item)&&!Array.isArray(t)&&typeof t=="object"?e.item.replace("{","").replace("}","").split(",").map(s=>s.trim()).forEach(s=>{i[s]=t[s]}):i[e.item]=t,e.index&&(i[e.index]=r),e.collection&&(i[e.collection]=n),i}function ni(e){return!Array.isArray(e)&&!isNaN(e)}function Pr(){}Pr.inline=(e,{expression:t},{cleanup:r})=>{let n=O(e);n._x_refs||(n._x_refs={}),n._x_refs[t]=e,r(()=>delete n._x_refs[t])};p("ref",Pr);p("if",(e,{expression:t},{effect:r,cleanup:n})=>{let i=h(e,t),o=()=>{if(e._x_currentIfEl)return e._x_currentIfEl;let a=e.content.cloneNode(!0).firstElementChild;return V(a,{},e),m(()=>{e.after(a),T(a)}),e._x_currentIfEl=a,e._x_undoIf=()=>{a.remove(),delete e._x_currentIfEl},a},s=()=>{!e._x_undoIf||(e._x_undoIf(),delete e._x_undoIf)};r(()=>i(a=>{a?o():s()})),n(()=>e._x_undoIf&&e._x_undoIf())});H(me("@",he(A("on:"))));p("on",U((e,{value:t,modifiers:r,expression:n},{cleanup:i})=>{let o=n?h(e,n):()=>{},s=le(e,t,r,a=>{o(()=>{},{scope:{$event:a},params:[a]})});i(()=>s())}));S.setEvaluator(Ge);S.setReactivityEngine({reactive:Te,effect:or,release:ar,raw:g});var _t=S;window.Alpine=_t;queueMicrotask(()=>{_t.start()});})();
|
example/atoms/atoms.go
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
package atoms
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
app "github.com/pyros2097/wapp"
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
var CountAtom = app.NewAtom(0)
|
|
8
|
-
|
|
9
|
-
func IncCount() {
|
|
10
|
-
CountAtom.Set(CountAtom.Get().(int) + 1)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
func DecCount() {
|
|
14
|
-
CountAtom.Set(CountAtom.Get().(int) - 1)
|
|
15
|
-
}
|
example/clock.go
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"time"
|
|
5
|
-
|
|
6
|
-
. "github.com/pyros2097/wapp"
|
|
7
|
-
. "github.com/pyros2097/wapp/example/components"
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
func Clock(c *RenderContext) UI {
|
|
11
|
-
timeValue, setTime := c.UseState(time.Now())
|
|
12
|
-
running, setRunning := c.UseState(false)
|
|
13
|
-
startTimer := func() {
|
|
14
|
-
setRunning(true)
|
|
15
|
-
go func() {
|
|
16
|
-
for running().(bool) {
|
|
17
|
-
setTime(time.Now())
|
|
18
|
-
time.Sleep(time.Second * 1)
|
|
19
|
-
}
|
|
20
|
-
}()
|
|
21
|
-
}
|
|
22
|
-
stopTimer := func() {
|
|
23
|
-
setRunning(false)
|
|
24
|
-
}
|
|
25
|
-
c.UseEffect(func() func() {
|
|
26
|
-
startTimer()
|
|
27
|
-
return stopTimer
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
return Col(Css("text-3xl text-gray-700"),
|
|
31
|
-
Header(c),
|
|
32
|
-
Row(
|
|
33
|
-
Div(Css("underline"),
|
|
34
|
-
Text("Clock"),
|
|
35
|
-
),
|
|
36
|
-
),
|
|
37
|
-
Row(
|
|
38
|
-
Div(Css("mt-10"),
|
|
39
|
-
Text(timeValue().(time.Time).Format("15:04:05")),
|
|
40
|
-
),
|
|
41
|
-
),
|
|
42
|
-
Row(
|
|
43
|
-
Button(Css("btn m-20"), OnClick(startTimer),
|
|
44
|
-
Text("Start"),
|
|
45
|
-
),
|
|
46
|
-
Button(Css("btn m-20"), OnClick(stopTimer),
|
|
47
|
-
Text("Stop"),
|
|
48
|
-
),
|
|
49
|
-
),
|
|
50
|
-
)
|
|
51
|
-
}
|
example/components/header.go
CHANGED
|
@@ -4,7 +4,7 @@ import (
|
|
|
4
4
|
. "github.com/pyros2097/wapp"
|
|
5
5
|
)
|
|
6
6
|
|
|
7
|
-
func Header(
|
|
7
|
+
func Header() *Element {
|
|
8
8
|
return Row(Css("w-full mb-20 font-bold text-xl text-gray-700 p-4"),
|
|
9
9
|
Div(Css("text-blue-700"),
|
|
10
10
|
A(Href("https://wapp.pyros2097.dev"), Text("wapp.pyros2097.dev")),
|
example/components/page.go
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package components
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
. "github.com/pyros2097/wapp"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
func Page(elem *Element) *Element {
|
|
8
|
+
return Html(
|
|
9
|
+
Head(
|
|
10
|
+
Title("123"),
|
|
11
|
+
Meta("description", "123"),
|
|
12
|
+
Meta("author", "123"),
|
|
13
|
+
Meta("keywords", "123"),
|
|
14
|
+
Meta("viewport", "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover"),
|
|
15
|
+
Link("icon", "/assets/icon.png"),
|
|
16
|
+
Link("apple-touch-icon", "/assets/icon.png"),
|
|
17
|
+
Link("stylesheet", "/assets/styles.css"),
|
|
18
|
+
Script(Src("/assets/alpine.js"), Defer()),
|
|
19
|
+
),
|
|
20
|
+
Body(elem),
|
|
21
|
+
)
|
|
22
|
+
}
|
example/container.go
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"strconv"
|
|
5
|
-
|
|
6
|
-
. "github.com/pyros2097/wapp"
|
|
7
|
-
. "github.com/pyros2097/wapp/example/atoms"
|
|
8
|
-
. "github.com/pyros2097/wapp/example/components"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
func AtomCounter(c *RenderContext, no string) UI {
|
|
12
|
-
count := c.UseAtom(CountAtom)
|
|
13
|
-
return Col(Css("text-3xl text-gray-700"),
|
|
14
|
-
Row(
|
|
15
|
-
Row(Css("underline"),
|
|
16
|
-
Text("Counter - "+no),
|
|
17
|
-
),
|
|
18
|
-
),
|
|
19
|
-
Row(
|
|
20
|
-
Button(Css("btn m-20"), OnClick(DecCount),
|
|
21
|
-
Text("-"),
|
|
22
|
-
),
|
|
23
|
-
Row(Css("m-20"),
|
|
24
|
-
Text(strconv.Itoa(count.(int))),
|
|
25
|
-
),
|
|
26
|
-
Button(Css("btn m-20"), OnClick(IncCount),
|
|
27
|
-
Text("+"),
|
|
28
|
-
),
|
|
29
|
-
),
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
func Container(c *RenderContext) UI {
|
|
34
|
-
return Col(
|
|
35
|
-
Header(c),
|
|
36
|
-
AtomCounter(c, "1"),
|
|
37
|
-
AtomCounter(c, "2"),
|
|
38
|
-
AtomCounter(c, "3"),
|
|
39
|
-
)
|
|
40
|
-
}
|
example/index.go
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"strconv"
|
|
5
|
-
|
|
6
|
-
. "github.com/pyros2097/wapp"
|
|
7
|
-
. "github.com/pyros2097/wapp/example/components"
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
func Index(c *RenderContext) UI {
|
|
11
|
-
count, setCount := c.UseInt(0)
|
|
12
|
-
inc := func() { setCount(count() + 1) }
|
|
13
|
-
dec := func() { setCount(count() - 1) }
|
|
14
|
-
return Col(
|
|
15
|
-
Header(c),
|
|
16
|
-
Col(Css("text-3xl text-gray-700"),
|
|
17
|
-
Row(
|
|
18
|
-
Row(Css("underline"),
|
|
19
|
-
Text("Counter"),
|
|
20
|
-
),
|
|
21
|
-
),
|
|
22
|
-
Row(
|
|
23
|
-
Button(Css("btn m-20"), OnClick(dec),
|
|
24
|
-
Text("-"),
|
|
25
|
-
),
|
|
26
|
-
Row(Css("m-20"),
|
|
27
|
-
Text(strconv.Itoa(count())),
|
|
28
|
-
),
|
|
29
|
-
Button(Css("btn m-20"), OnClick(inc),
|
|
30
|
-
Text("+"),
|
|
31
|
-
),
|
|
32
|
-
),
|
|
33
|
-
),
|
|
34
|
-
)
|
|
35
|
-
}
|
example/main.go
CHANGED
|
@@ -1,27 +1,45 @@
|
|
|
1
1
|
package main
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"embed"
|
|
5
|
+
// "log"
|
|
6
|
+
|
|
7
|
+
// sshd "github.com/jpillora/sshd-lite/server"
|
|
4
8
|
. "github.com/pyros2097/wapp"
|
|
9
|
+
|
|
5
|
-
|
|
10
|
+
"github.com/pyros2097/wapp/example/pages"
|
|
6
11
|
)
|
|
7
12
|
|
|
13
|
+
//go:embed assets/*
|
|
14
|
+
var assetsFS embed.FS
|
|
15
|
+
|
|
8
16
|
func main() {
|
|
9
17
|
// os.Setenv("HAS_WASM", "false")
|
|
10
|
-
SetErrorHandler(func(
|
|
18
|
+
// SetErrorHandler(func(w *RenderContext, err error) UI {
|
|
11
|
-
|
|
19
|
+
// return Col(Css("text-4xl text-gray-700"),
|
|
12
|
-
|
|
20
|
+
// Header(c),
|
|
13
|
-
|
|
21
|
+
// Row(
|
|
14
|
-
|
|
22
|
+
// Text("Oops something went wrong"),
|
|
15
|
-
|
|
23
|
+
// ),
|
|
16
|
-
|
|
24
|
+
// Row(Css("mt-6"),
|
|
17
|
-
|
|
25
|
+
// Text("Please check back again"),
|
|
18
|
-
|
|
26
|
+
// ),
|
|
19
|
-
|
|
27
|
+
// )
|
|
20
|
-
})
|
|
28
|
+
// })
|
|
29
|
+
// go func() {
|
|
30
|
+
// s, err := sshd.NewServer(&sshd.Config{
|
|
31
|
+
// Port: "2223",
|
|
32
|
+
// AuthType: "peter:pass",
|
|
33
|
+
// })
|
|
21
|
-
|
|
34
|
+
// if err != nil {
|
|
35
|
+
// log.Fatal(err)
|
|
36
|
+
// }
|
|
37
|
+
// err = s.Start()
|
|
38
|
+
// if err != nil {
|
|
39
|
+
// log.Fatal(err)
|
|
40
|
+
// }
|
|
41
|
+
// }()
|
|
22
|
-
Route("/about", About)
|
|
42
|
+
Route("/about", pages.About)
|
|
23
|
-
Route("/clock", Clock)
|
|
24
|
-
Route("/container", Container)
|
|
25
|
-
Route("/", Index)
|
|
43
|
+
Route("/", pages.Index)
|
|
26
|
-
Run()
|
|
44
|
+
Run(assetsFS)
|
|
27
45
|
}
|
example/pages/about.go
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package pages
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"net/http"
|
|
5
|
+
|
|
6
|
+
. "github.com/pyros2097/wapp"
|
|
7
|
+
. "github.com/pyros2097/wapp/example/components"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
func About(w http.ResponseWriter, r *http.Request) *Element {
|
|
11
|
+
return Page(
|
|
12
|
+
Col(
|
|
13
|
+
Header(),
|
|
14
|
+
Row(Css("text-5xl text-gray-700"),
|
|
15
|
+
Text("About Me"),
|
|
16
|
+
),
|
|
17
|
+
),
|
|
18
|
+
)
|
|
19
|
+
}
|
example/pages/index.go
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
package pages
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"net/http"
|
|
5
|
+
"strconv"
|
|
6
|
+
|
|
7
|
+
. "github.com/pyros2097/wapp"
|
|
8
|
+
. "github.com/pyros2097/wapp/example/components"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func Index(w http.ResponseWriter, r *http.Request) *Element {
|
|
12
|
+
return Page(
|
|
13
|
+
Col(
|
|
14
|
+
Header(),
|
|
15
|
+
H1(Text("Hello this is a h1")),
|
|
16
|
+
H2(Text("Hello this is a h2")),
|
|
17
|
+
H2(XData("{ message: 'I ❤️ Alpine' }"), XText("message"), Text("")),
|
|
18
|
+
Col(Css("text-3xl text-gray-700"),
|
|
19
|
+
Row(
|
|
20
|
+
Row(Css("underline"),
|
|
21
|
+
Text("Counter"),
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
Row(
|
|
25
|
+
Button(Css("btn m-20"),
|
|
26
|
+
Text("-"),
|
|
27
|
+
),
|
|
28
|
+
Row(Css("m-20"),
|
|
29
|
+
Text(strconv.Itoa(1)),
|
|
30
|
+
),
|
|
31
|
+
Button(Css("btn m-20"),
|
|
32
|
+
Text("+"),
|
|
33
|
+
),
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
}
|
example/panic.go
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
. "github.com/pyros2097/wapp"
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
func Panic(c *RenderContext) UI {
|
|
8
|
-
panic("Hello")
|
|
9
|
-
}
|
go.mod
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
module github.com/pyros2097/wapp
|
|
2
2
|
|
|
3
|
-
go 1.
|
|
3
|
+
go 1.16
|
|
4
4
|
|
|
5
5
|
require (
|
|
6
|
-
github.com/akrylysov/algnhsa v0.12.1
|
|
7
|
-
github.com/aws/aws-lambda-go v1.20.0
|
|
8
|
-
github.com/
|
|
6
|
+
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
9
|
-
github.com/julienschmidt/httprouter v1.3.0
|
|
10
|
-
github.com/kevinpollet/nego v0.0.0-20200702060216-3ff8e9f14a70
|
|
11
|
-
github.com/markbates/pkger v0.17.1
|
|
12
|
-
github.com/
|
|
7
|
+
github.com/kr/pretty v0.1.0 // indirect
|
|
13
8
|
github.com/stretchr/testify v1.6.1
|
|
9
|
+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
|
14
|
-
|
|
10
|
+
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
|
|
15
|
-
gopkg.in/fsnotify.v1 v1.4.7
|
|
16
11
|
)
|
go.sum
CHANGED
|
@@ -1,114 +1,19 @@
|
|
|
1
|
-
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
2
|
-
github.com/akrylysov/algnhsa v0.12.1 h1:A9Ojt4hZrL77mhBc3qGO3Sn9reyf+tvM3DmR0SfXguc=
|
|
3
|
-
github.com/akrylysov/algnhsa v0.12.1/go.mod h1:xAcJ/X8DV+81e+dUjIoB/r5CbISrSXV9//leoMDHcdk=
|
|
4
|
-
github.com/aws/aws-lambda-go v1.9.0/go.mod h1:zUsUQhAUjYzR8AuduJPCfhBuKWUaDbQiPOG+ouzmE1A=
|
|
5
|
-
github.com/aws/aws-lambda-go v1.20.0 h1:ZSweJx/Hy9BoIDXKBEh16vbHH0t0dehnF8MKpMiOWc0=
|
|
6
|
-
github.com/aws/aws-lambda-go v1.20.0/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU=
|
|
7
|
-
github.com/awslabs/goformation v1.4.1 h1:jws9kTrcI53Hq2COJAy50uAhgxB5N/Enb9Gmclr/MP4=
|
|
8
|
-
github.com/awslabs/goformation/v4 v4.15.6 h1:9F0MbtJVSMkuI19G6Fm+qHc1nqScHcOIf+3YRRv+Ohc=
|
|
9
|
-
github.com/awslabs/goformation/v4 v4.15.6/go.mod h1:wB5lKZf1J0MYH1Lt4B9w3opqz0uIjP7MMCAcib3QkwA=
|
|
10
|
-
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
11
|
-
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
12
1
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
13
2
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
14
3
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
15
|
-
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
|
16
|
-
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
|
17
|
-
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
|
18
|
-
github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI=
|
|
19
|
-
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
|
|
20
|
-
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
21
|
-
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
22
|
-
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
23
|
-
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
24
|
-
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
25
|
-
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
26
|
-
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
27
|
-
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
28
|
-
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
29
|
-
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
30
|
-
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
31
|
-
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
|
32
|
-
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
|
33
|
-
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
|
34
|
-
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
|
35
|
-
github.com/kevinpollet/nego v0.0.0-20200702060216-3ff8e9f14a70 h1:vl63cy3DUIN3iZI5oU/2yfVXEFVPc5ahbwjWk0aF3nA=
|
|
36
|
-
github.com/kevinpollet/nego v0.0.0-20200702060216-3ff8e9f14a70/go.mod h1:bST7PtmFt4otZfrYPAUmYA1v3hZBhX4ttQzBSxeRE4E=
|
|
37
4
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
38
5
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
39
6
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
40
7
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
41
8
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
42
|
-
github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno=
|
|
43
|
-
github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
|
|
44
|
-
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
|
45
|
-
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
46
|
-
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
|
47
|
-
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
|
48
|
-
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
|
49
|
-
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
|
50
|
-
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
|
51
9
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
52
10
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
53
|
-
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
54
|
-
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b h1:jUK33OXuZP/l6babJtnLo1qsGvq6G9so9KMflGAm4YA=
|
|
55
|
-
github.com/sanathkr/go-yaml v0.0.0-20170819195128-ed9d249f429b/go.mod h1:8458kAagoME2+LN5//WxE71ysZ3B7r22fdgb7qVmXSY=
|
|
56
|
-
github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522 h1:fOCp11H0yuyAt2wqlbJtbyPzSgaxHTv8uN1pMpkG1t8=
|
|
57
|
-
github.com/sanathkr/yaml v0.0.0-20170819201035-0056894fa522/go.mod h1:tQTYKOQgxoH3v6dEmdHiz4JG+nbxWwM5fgPQUpSZqVQ=
|
|
58
|
-
github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0 h1:mj/nMDAwTBiaCqMEs4cYCqF7pO6Np7vhy1D1wcQGz+E=
|
|
59
|
-
github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
|
60
|
-
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
61
11
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
62
|
-
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
63
|
-
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
64
12
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
|
65
13
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
66
|
-
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
|
67
|
-
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
|
68
|
-
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
|
69
|
-
github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
|
70
|
-
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
71
|
-
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
72
|
-
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
73
|
-
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
74
|
-
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
75
|
-
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
|
|
76
|
-
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
77
|
-
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
78
|
-
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
79
|
-
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
80
|
-
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
81
|
-
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
82
|
-
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
83
|
-
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
84
|
-
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
85
|
-
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
86
|
-
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
87
|
-
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
|
|
88
|
-
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
89
|
-
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
90
|
-
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
91
|
-
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
|
92
|
-
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
93
|
-
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
94
|
-
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
95
|
-
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
96
|
-
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
97
|
-
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
98
|
-
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
99
|
-
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
100
|
-
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
101
14
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
102
15
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
|
103
16
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
104
|
-
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
|
105
|
-
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
106
|
-
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
107
|
-
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
108
|
-
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
109
|
-
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
110
|
-
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
111
|
-
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
112
17
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
113
18
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
|
114
19
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
html.go
CHANGED
|
@@ -2,12 +2,12 @@ package app
|
|
|
2
2
|
|
|
3
3
|
import "reflect"
|
|
4
4
|
|
|
5
|
-
func Html(elems ...
|
|
5
|
+
func Html(elems ...*Element) *Element {
|
|
6
6
|
return &Element{tag: "html", body: elems}
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
func Head(elems ...
|
|
9
|
+
func Head(elems ...*Element) *Element {
|
|
10
|
-
basic := []
|
|
10
|
+
basic := []*Element{
|
|
11
11
|
&Element{tag: "meta", selfClosing: true, attrs: map[string]string{"charset": "UTF-8"}},
|
|
12
12
|
&Element{tag: "meta", selfClosing: true, attrs: map[string]string{"http-equiv": "Content-Type", "content": "text/html;charset=utf-8"}},
|
|
13
13
|
&Element{tag: "meta", selfClosing: true, attrs: map[string]string{"http-equiv": "encoding", "content": "utf-8"}},
|
|
@@ -15,39 +15,47 @@ func Head(elems ...UI) *Element {
|
|
|
15
15
|
return &Element{tag: "head", body: append(basic, elems...)}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
func Body(elems ...
|
|
18
|
+
func Body(elems ...*Element) *Element {
|
|
19
19
|
return &Element{tag: "body", body: elems}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
func Title(v string) *Element {
|
|
23
|
+
return &Element{
|
|
23
|
-
|
|
24
|
+
tag: "title",
|
|
25
|
+
body: []*Element{Text(v)},
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
func Text(v string) *Element {
|
|
29
|
+
return &Element{
|
|
30
|
+
tag: "text",
|
|
31
|
+
text: v,
|
|
32
|
+
}
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
func Meta(name, content string) *Element {
|
|
27
|
-
|
|
36
|
+
return &Element{
|
|
28
37
|
tag: "meta",
|
|
29
38
|
selfClosing: true,
|
|
39
|
+
attrs: map[string]string{
|
|
40
|
+
"name": name,
|
|
41
|
+
"content": content,
|
|
42
|
+
},
|
|
30
43
|
}
|
|
31
|
-
e.setAttr("name", name)
|
|
32
|
-
e.setAttr("content", content)
|
|
33
|
-
return e
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
func Link(rel, href string) *Element {
|
|
37
|
-
|
|
47
|
+
return &Element{
|
|
38
48
|
tag: "link",
|
|
39
49
|
selfClosing: true,
|
|
50
|
+
attrs: map[string]string{
|
|
51
|
+
"rel": rel,
|
|
52
|
+
"href": href,
|
|
53
|
+
},
|
|
40
54
|
}
|
|
41
|
-
e.setAttr("rel", rel)
|
|
42
|
-
e.setAttr("href", href)
|
|
43
|
-
return e
|
|
44
55
|
}
|
|
45
56
|
|
|
46
|
-
func Script(
|
|
57
|
+
func Script(uis ...interface{}) *Element {
|
|
47
|
-
return &Element{
|
|
48
|
-
|
|
58
|
+
return NewElement("script", false, uis...)
|
|
49
|
-
body: []UI{Text(str)},
|
|
50
|
-
}
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
func Div(uis ...interface{}) *Element {
|
|
@@ -62,6 +70,25 @@ func P(uis ...interface{}) *Element {
|
|
|
62
70
|
return NewElement("p", false, uis...)
|
|
63
71
|
}
|
|
64
72
|
|
|
73
|
+
func H1(uis ...interface{}) *Element {
|
|
74
|
+
return NewElement("h1", false, uis...)
|
|
75
|
+
}
|
|
76
|
+
func H2(uis ...interface{}) *Element {
|
|
77
|
+
return NewElement("h1", false, uis...)
|
|
78
|
+
}
|
|
79
|
+
func H3(uis ...interface{}) *Element {
|
|
80
|
+
return NewElement("h1", false, uis...)
|
|
81
|
+
}
|
|
82
|
+
func H4(uis ...interface{}) *Element {
|
|
83
|
+
return NewElement("h1", false, uis...)
|
|
84
|
+
}
|
|
85
|
+
func H5(uis ...interface{}) *Element {
|
|
86
|
+
return NewElement("h1", false, uis...)
|
|
87
|
+
}
|
|
88
|
+
func H6(uis ...interface{}) *Element {
|
|
89
|
+
return NewElement("h1", false, uis...)
|
|
90
|
+
}
|
|
91
|
+
|
|
65
92
|
func Span(uis ...interface{}) *Element {
|
|
66
93
|
return NewElement("span", false, uis...)
|
|
67
94
|
}
|
|
@@ -94,93 +121,48 @@ func Li(uis ...interface{}) *Element {
|
|
|
94
121
|
return NewElement("li", false, uis...)
|
|
95
122
|
}
|
|
96
123
|
|
|
97
|
-
func Row(uis ...interface{})
|
|
124
|
+
func Row(uis ...interface{}) *Element {
|
|
98
125
|
return Div(append([]interface{}{Css("flex flex-row justify-center items-center")}, uis...)...)
|
|
99
126
|
}
|
|
100
127
|
|
|
101
|
-
func Col(uis ...interface{})
|
|
128
|
+
func Col(uis ...interface{}) *Element {
|
|
102
129
|
return Div(append([]interface{}{Css("flex flex-col justify-center items-center")}, uis...)...)
|
|
103
130
|
}
|
|
104
131
|
|
|
105
|
-
func If(expr bool, a
|
|
132
|
+
func If(expr bool, a *Element) *Element {
|
|
106
133
|
if expr {
|
|
107
134
|
return a
|
|
108
135
|
}
|
|
109
136
|
return nil
|
|
110
137
|
}
|
|
111
138
|
|
|
112
|
-
func IfElse(expr bool, a
|
|
139
|
+
func IfElse(expr bool, a *Element, b *Element) *Element {
|
|
113
140
|
if expr {
|
|
114
141
|
return a
|
|
115
142
|
}
|
|
116
143
|
return b
|
|
117
144
|
}
|
|
118
145
|
|
|
119
|
-
func Map(source interface{}, f func(i int)
|
|
146
|
+
func Map(source interface{}, f func(i int) *Element) []*Element {
|
|
120
147
|
src := reflect.ValueOf(source)
|
|
121
148
|
if src.Kind() != reflect.Slice {
|
|
122
149
|
panic("range loop source is not a slice: " + src.Type().String())
|
|
123
150
|
}
|
|
124
|
-
body := make([]
|
|
151
|
+
body := make([]*Element, 0, src.Len())
|
|
125
152
|
for i := 0; i < src.Len(); i++ {
|
|
126
153
|
body = append(body, f(i))
|
|
127
154
|
}
|
|
128
155
|
return body
|
|
129
156
|
}
|
|
130
157
|
|
|
131
|
-
func Map2(source interface{}, f func(v interface{}, i int)
|
|
158
|
+
func Map2(source interface{}, f func(v interface{}, i int) *Element) []*Element {
|
|
132
159
|
src := reflect.ValueOf(source)
|
|
133
160
|
if src.Kind() != reflect.Slice {
|
|
134
161
|
panic("range loop source is not a slice: " + src.Type().String())
|
|
135
162
|
}
|
|
136
|
-
body := make([]
|
|
163
|
+
body := make([]*Element, 0, src.Len())
|
|
137
164
|
for i := 0; i < src.Len(); i++ {
|
|
138
165
|
body = append(body, f(src.Index(i), i))
|
|
139
166
|
}
|
|
140
167
|
return body
|
|
141
168
|
}
|
|
142
|
-
|
|
143
|
-
// func (r RangeLoop) Slice(f func(int) UI) RangeLoop {
|
|
144
|
-
// src := reflect.ValueOf(r.source)
|
|
145
|
-
// if src.Kind() != reflect.Slice && src.Kind() != reflect.Array {
|
|
146
|
-
// panic("range loop source is not a slice or array: " + src.Type().String())
|
|
147
|
-
// }
|
|
148
|
-
|
|
149
|
-
// body := make([]UI, 0, src.Len())
|
|
150
|
-
// for i := 0; i < src.Len(); i++ {
|
|
151
|
-
// body = append(body, FilterUIElems(f(i))...)
|
|
152
|
-
// }
|
|
153
|
-
|
|
154
|
-
// r.body = body
|
|
155
|
-
// return r
|
|
156
|
-
// }
|
|
157
|
-
|
|
158
|
-
// // Map sets the loop content by repeating the given function for the number
|
|
159
|
-
// // of elements in the source. Elements are ordered by keys.
|
|
160
|
-
// //
|
|
161
|
-
// // It panics if the range source is not a map or if map keys are not strings.
|
|
162
|
-
// func (r RangeLoop) Map(f func(string) UI) RangeLoop {
|
|
163
|
-
// src := reflect.ValueOf(r.source)
|
|
164
|
-
// if src.Kind() != reflect.Map {
|
|
165
|
-
// panic("range loop source is not a map: " + src.Type().String())
|
|
166
|
-
// }
|
|
167
|
-
|
|
168
|
-
// if keyType := src.Type().Key(); keyType.Kind() != reflect.String {
|
|
169
|
-
// panic("range loop source keys are not strings: " + src.Type().String() + keyType.String())
|
|
170
|
-
// }
|
|
171
|
-
|
|
172
|
-
// body := make([]UI, 0, src.Len())
|
|
173
|
-
// keys := make([]string, 0, src.Len())
|
|
174
|
-
|
|
175
|
-
// for _, k := range src.MapKeys() {
|
|
176
|
-
// keys = append(keys, k.String())
|
|
177
|
-
// }
|
|
178
|
-
// sort.Strings(keys)
|
|
179
|
-
|
|
180
|
-
// for _, k := range keys {
|
|
181
|
-
// body = append(body, FilterUIElems(f(k))...)
|
|
182
|
-
// }
|
|
183
|
-
|
|
184
|
-
// r.body = body
|
|
185
|
-
// return r
|
|
186
|
-
// }
|
js/js.go
DELETED
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
package js
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"fmt"
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
// Type represents the JavaScript type of a Value.
|
|
8
|
-
type Type int
|
|
9
|
-
|
|
10
|
-
// Constants that enumerates the JavaScript types.
|
|
11
|
-
const (
|
|
12
|
-
TypeUndefined Type = iota
|
|
13
|
-
TypeNull
|
|
14
|
-
TypeBoolean
|
|
15
|
-
TypeNumber
|
|
16
|
-
TypeString
|
|
17
|
-
TypeSymbol
|
|
18
|
-
TypeObject
|
|
19
|
-
TypeFunction
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
// Wrapper is implemented by types that are backed by a JavaScript value.
|
|
23
|
-
type Wrapper interface {
|
|
24
|
-
JSValue() Value
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Value is the interface that represents a JavaScript value. On wasm
|
|
28
|
-
// architecture, it wraps the Value from https://golang.org/pkg/syscall/js/
|
|
29
|
-
// package.
|
|
30
|
-
type Value interface {
|
|
31
|
-
// Bool returns the value v as a bool. It panics if v is not a JavaScript
|
|
32
|
-
// boolean.
|
|
33
|
-
Bool() bool
|
|
34
|
-
|
|
35
|
-
// Call does a JavaScript call to the method m of value v with the given
|
|
36
|
-
// arguments. It panics if v has no method m. The arguments get mapped to
|
|
37
|
-
// JavaScript values according to the ValueOf function.
|
|
38
|
-
Call(m string, args ...interface{}) Value
|
|
39
|
-
|
|
40
|
-
// Float returns the value v as a float64. It panics if v is not a
|
|
41
|
-
// JavaScript number.
|
|
42
|
-
Float() float64
|
|
43
|
-
|
|
44
|
-
// Get returns the JavaScript property p of value v. It panics if v is not a
|
|
45
|
-
// JavaScript object.
|
|
46
|
-
Get(p string) Value
|
|
47
|
-
|
|
48
|
-
// Index returns JavaScript index i of value v. It panics if v is not a
|
|
49
|
-
// JavaScript object.
|
|
50
|
-
Index(i int) Value
|
|
51
|
-
|
|
52
|
-
// InstanceOf reports whether v is an instance of type t according to
|
|
53
|
-
// JavaScript's instanceof operator.
|
|
54
|
-
InstanceOf(t Value) bool
|
|
55
|
-
|
|
56
|
-
// Int returns the value v truncated to an int. It panics if v is not a
|
|
57
|
-
// JavaScript number.
|
|
58
|
-
Int() int
|
|
59
|
-
|
|
60
|
-
// Invoke does a JavaScript call of the value v with the given arguments. It
|
|
61
|
-
// panics if v is not a JavaScript function. The arguments get mapped to
|
|
62
|
-
// JavaScript values according to the ValueOf function.
|
|
63
|
-
Invoke(args ...interface{}) Value
|
|
64
|
-
|
|
65
|
-
// IsNaN reports whether v is the JavaScript value "NaN".
|
|
66
|
-
IsNaN() bool
|
|
67
|
-
|
|
68
|
-
// IsNull reports whether v is the JavaScript value "null".
|
|
69
|
-
IsNull() bool
|
|
70
|
-
|
|
71
|
-
// IsUndefined reports whether v is the JavaScript value "undefined".
|
|
72
|
-
IsUndefined() bool
|
|
73
|
-
|
|
74
|
-
// JSValue implements Wrapper interface.
|
|
75
|
-
JSValue() Value
|
|
76
|
-
|
|
77
|
-
// Length returns the JavaScript property "length" of v. It panics if v is
|
|
78
|
-
// not a JavaScript object.
|
|
79
|
-
Length() int
|
|
80
|
-
|
|
81
|
-
// New uses JavaScript's "new" operator with value v as constructor and the
|
|
82
|
-
// given arguments. It panics if v is not a JavaScript function. The
|
|
83
|
-
// arguments get mapped to JavaScript values according to the ValueOf
|
|
84
|
-
// function.
|
|
85
|
-
New(args ...interface{}) Value
|
|
86
|
-
|
|
87
|
-
// Set sets the JavaScript property p of value v to ValueOf(x). It panics if
|
|
88
|
-
// v is not a JavaScript object.
|
|
89
|
-
Set(p string, x interface{})
|
|
90
|
-
|
|
91
|
-
// SetIndex sets the JavaScript index i of value v to ValueOf(x). It panics
|
|
92
|
-
// if v is not a JavaScript object.
|
|
93
|
-
SetIndex(i int, x interface{})
|
|
94
|
-
|
|
95
|
-
// String returns the value v as a string. String is a special case because
|
|
96
|
-
// of Go's String method convention. Unlike the other getters, it does not
|
|
97
|
-
// panic if v's Type is not TypeString. Instead, it returns a string of the
|
|
98
|
-
// form "<T>" or "<T: V>" where T is v's type and V is a string
|
|
99
|
-
// representation of v's value.
|
|
100
|
-
String() string
|
|
101
|
-
|
|
102
|
-
// Truthy returns the JavaScript "truthiness" of the value v. In JavaScript,
|
|
103
|
-
// false, 0, "", null, undefined, and NaN are "falsy", and everything else
|
|
104
|
-
// is "truthy". See
|
|
105
|
-
// https://developer.mozilla.org/en-US/docs/Glossary/Truthy.
|
|
106
|
-
Truthy() bool
|
|
107
|
-
|
|
108
|
-
// Type returns the JavaScript type of the value v. It is similar to
|
|
109
|
-
// JavaScript's typeof operator, except that it returns TypeNull instead of
|
|
110
|
-
// TypeObject for null.
|
|
111
|
-
Type() Type
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Null returns the JavaScript value "null".
|
|
115
|
-
func Null() Value {
|
|
116
|
-
return null()
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Undefined returns the JavaScript value "undefined".
|
|
120
|
-
func Undefined() Value {
|
|
121
|
-
return undefined()
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ValueOf returns x as a JavaScript value:
|
|
125
|
-
//
|
|
126
|
-
// | Go | JavaScript |
|
|
127
|
-
// | ---------------------- | ---------------------- |
|
|
128
|
-
// | js.Value | [its value] |
|
|
129
|
-
// | js.Func | function |
|
|
130
|
-
// | nil | null |
|
|
131
|
-
// | bool | boolean |
|
|
132
|
-
// | integers and floats | number |
|
|
133
|
-
// | string | string |
|
|
134
|
-
// | []interface{} | new array |
|
|
135
|
-
// | map[string]interface{} | new object |
|
|
136
|
-
//
|
|
137
|
-
// Panics if x is not one of the expected types.
|
|
138
|
-
func ValueOf(x interface{}) Value {
|
|
139
|
-
return valueOf(x)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Func is the interface that describes a wrapped Go function to be called by
|
|
143
|
-
// JavaScript.
|
|
144
|
-
type Func interface {
|
|
145
|
-
Value
|
|
146
|
-
|
|
147
|
-
// Release frees up resources allocated for the function. The function must
|
|
148
|
-
// not be invoked after calling Release.
|
|
149
|
-
Release()
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// FuncOf returns a wrapped function.
|
|
153
|
-
//
|
|
154
|
-
// Invoking the JavaScript function will synchronously call the Go function fn
|
|
155
|
-
// with the value of JavaScript's "this" keyword and the arguments of the
|
|
156
|
-
// invocation. The return value of the invocation is the result of the Go
|
|
157
|
-
// function mapped back to JavaScript according to ValueOf.
|
|
158
|
-
//
|
|
159
|
-
// A wrapped function triggered during a call from Go to JavaScript gets
|
|
160
|
-
// executed on the same goroutine. A wrapped function triggered by JavaScript's
|
|
161
|
-
// event loop gets executed on an extra goroutine. Blocking operations in the
|
|
162
|
-
// wrapped function will block the event loop. As a consequence, if one wrapped
|
|
163
|
-
// function blocks, other wrapped funcs will not be processed. A blocking
|
|
164
|
-
// function should therefore explicitly start a new goroutine.
|
|
165
|
-
//
|
|
166
|
-
// Func.Release must be called to free up resources when the function will not
|
|
167
|
-
// be used any more.
|
|
168
|
-
func FuncOf(fn func(this Value, args []Value) interface{}) Func {
|
|
169
|
-
return funcOf(fn)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// BrowserWindow is the interface that describes the browser window.
|
|
173
|
-
type BrowserWindow interface {
|
|
174
|
-
Value
|
|
175
|
-
|
|
176
|
-
// The window current url (window.location.href).
|
|
177
|
-
URL() string
|
|
178
|
-
|
|
179
|
-
// The window size.
|
|
180
|
-
Size() (w, h int)
|
|
181
|
-
|
|
182
|
-
// The position of the cursor (mouse or touch).
|
|
183
|
-
CursorPosition() (x, y int)
|
|
184
|
-
|
|
185
|
-
setCursorPosition(x, y int)
|
|
186
|
-
|
|
187
|
-
// Returns the HTML element with the id property that matches the given id.
|
|
188
|
-
GetElementByID(id string) Value
|
|
189
|
-
|
|
190
|
-
// Scrolls to the HTML element with the given id.
|
|
191
|
-
ScrollToID(id string)
|
|
192
|
-
|
|
193
|
-
// AddEventListener subscribes a given handler to the specified event. It
|
|
194
|
-
// returns a function that must be called to unsubscribe the handler and
|
|
195
|
-
// release allocated resources.
|
|
196
|
-
AddEventListener(event string, h EventHandler) func()
|
|
197
|
-
|
|
198
|
-
Location() Location
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Event is the interface that describes a javascript event.
|
|
202
|
-
type Event struct {
|
|
203
|
-
Src Value
|
|
204
|
-
Value
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// PreventDefault cancels the event if it is cancelable. The default action that
|
|
208
|
-
// belongs to the event will not occur.
|
|
209
|
-
func (e Event) PreventDefault() {
|
|
210
|
-
e.Call("preventDefault")
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// CopyBytesToGo copies bytes from the Uint8Array src to dst. It returns the
|
|
214
|
-
// number of bytes copied, which will be the minimum of the lengths of src and
|
|
215
|
-
// dst.
|
|
216
|
-
//
|
|
217
|
-
// CopyBytesToGo panics if src is not an Uint8Array.
|
|
218
|
-
func CopyBytesToGo(dst []byte, src Value) int {
|
|
219
|
-
return copyBytesToGo(dst, src)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// CopyBytesToJS copies bytes from src to the Uint8Array dst. It returns the
|
|
223
|
-
// number of bytes copied, which will be the minimum of the lengths of src and
|
|
224
|
-
// dst.
|
|
225
|
-
//
|
|
226
|
-
// CopyBytesToJS panics if dst is not an Uint8Array.
|
|
227
|
-
func CopyBytesToJS(dst Value, src []byte) int {
|
|
228
|
-
return copyBytesToJS(dst, src)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// EventHandler represents a function that can handle HTML events. They are
|
|
232
|
-
// always called on the UI goroutine.
|
|
233
|
-
type EventHandlerFunc func(e Event)
|
|
234
|
-
|
|
235
|
-
type EventHandler struct {
|
|
236
|
-
Event string
|
|
237
|
-
JSvalue Func
|
|
238
|
-
Value EventHandlerFunc
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
func NewEventHandler(e string, v EventHandlerFunc) EventHandler {
|
|
242
|
-
return EventHandler{
|
|
243
|
-
Event: e,
|
|
244
|
-
Value: v,
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
func (h EventHandler) Equal(o EventHandler) bool {
|
|
249
|
-
return h.Event == o.Event &&
|
|
250
|
-
fmt.Sprintf("%p", h.Value) == fmt.Sprintf("%p", o.Value)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
type Location struct {
|
|
254
|
-
value Value
|
|
255
|
-
Href string
|
|
256
|
-
Pathname string
|
|
257
|
-
}
|
js/js_nowasm.go
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
// +build !wasm
|
|
2
|
-
|
|
3
|
-
package js
|
|
4
|
-
|
|
5
|
-
var Window = &browserWindow{value: value{}}
|
|
6
|
-
|
|
7
|
-
type value struct{}
|
|
8
|
-
|
|
9
|
-
func (v value) Bool() bool {
|
|
10
|
-
panic("wasm required")
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
func (v value) Call(m string, args ...interface{}) Value {
|
|
14
|
-
panic("wasm required")
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
func (v value) Float() float64 {
|
|
18
|
-
panic("wasm required")
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
func (v value) Get(p string) Value {
|
|
22
|
-
panic("wasm required")
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
func (v value) Index(i int) Value {
|
|
26
|
-
panic("wasm required")
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
func (v value) InstanceOf(t Value) bool {
|
|
30
|
-
panic("wasm required")
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
func (v value) Int() int {
|
|
34
|
-
panic("wasm required")
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
func (v value) Invoke(args ...interface{}) Value {
|
|
38
|
-
panic("wasm required")
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
func (v value) IsNaN() bool {
|
|
42
|
-
panic("wasm required")
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
func (v value) IsNull() bool {
|
|
46
|
-
panic("wasm required")
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
func (v value) IsUndefined() bool {
|
|
50
|
-
panic("wasm required")
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
func (v value) JSValue() Value {
|
|
54
|
-
panic("wasm required")
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
func (v value) Length() int {
|
|
58
|
-
panic("wasm required")
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
func (v value) New(args ...interface{}) Value {
|
|
62
|
-
panic("wasm required")
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
func (v value) Set(p string, x interface{}) {
|
|
66
|
-
panic("wasm required")
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
func (v value) SetIndex(i int, x interface{}) {
|
|
70
|
-
panic("wasm required")
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
func (v value) String() string {
|
|
74
|
-
panic("wasm required")
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
func (v value) Truthy() bool {
|
|
78
|
-
panic("wasm required")
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
func (v value) Type() Type {
|
|
82
|
-
panic("wasm required")
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
func null() Value {
|
|
86
|
-
panic("wasm required")
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
func undefined() Value {
|
|
90
|
-
panic("wasm required")
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
func valueOf(x interface{}) Value {
|
|
94
|
-
panic("wasm required")
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
func funcOf(fn func(this Value, args []Value) interface{}) Func {
|
|
98
|
-
panic("wasm required")
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
type browserWindow struct {
|
|
102
|
-
value
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
func (w browserWindow) Size() (width, height int) {
|
|
106
|
-
panic("wasm required")
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
func (w browserWindow) CursorPosition() (x, y int) {
|
|
110
|
-
panic("wasm required")
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
func (w browserWindow) SetCursorPosition(x, y int) {
|
|
114
|
-
panic("wasm required")
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
func (w *browserWindow) GetElementByID(id string) Value {
|
|
118
|
-
panic("wasm required")
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
func (w *browserWindow) ScrollToID(id string) {
|
|
122
|
-
panic("wasm required")
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
func (w *browserWindow) AddEventListener(event string, h EventHandler) func() {
|
|
126
|
-
panic("wasm required")
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
func (w *browserWindow) Location() Location {
|
|
130
|
-
panic("wasm required")
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
func copyBytesToGo(dst []byte, src Value) int {
|
|
134
|
-
panic("wasm required")
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
func copyBytesToJS(dst Value, src []byte) int {
|
|
138
|
-
panic("wasm required")
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
func (l *Location) Reload() {
|
|
142
|
-
panic("wasm required")
|
|
143
|
-
}
|
js/js_wasm.go
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
// +build wasm
|
|
2
|
-
package js
|
|
3
|
-
|
|
4
|
-
import (
|
|
5
|
-
"syscall/js"
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
var Window = &browserWindow{value: value{Value: js.Global()}}
|
|
9
|
-
|
|
10
|
-
type value struct {
|
|
11
|
-
js.Value
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
func (v value) Call(m string, args ...interface{}) Value {
|
|
15
|
-
args = cleanArgs(args...)
|
|
16
|
-
return val(v.Value.Call(m, args...))
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
func (v value) Get(p string) Value {
|
|
20
|
-
return val(v.Value.Get(p))
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
func (v value) Set(p string, x interface{}) {
|
|
24
|
-
if wrapper, ok := x.(Wrapper); ok {
|
|
25
|
-
x = jsval(wrapper.JSValue())
|
|
26
|
-
}
|
|
27
|
-
v.Value.Set(p, x)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
func (v value) Index(i int) Value {
|
|
31
|
-
return val(v.Value.Index(i))
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
func (v value) InstanceOf(t Value) bool {
|
|
35
|
-
return v.Value.InstanceOf(jsval(t))
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
func (v value) Invoke(args ...interface{}) Value {
|
|
39
|
-
return val(v.Value.Invoke(args...))
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
func (v value) JSValue() Value {
|
|
43
|
-
return v
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
func (v value) New(args ...interface{}) Value {
|
|
47
|
-
args = cleanArgs(args...)
|
|
48
|
-
return val(v.Value.New(args...))
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
func (v value) Type() Type {
|
|
52
|
-
return Type(v.Value.Type())
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
func null() Value {
|
|
56
|
-
return val(js.Null())
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
func undefined() Value {
|
|
60
|
-
return val(js.Undefined())
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
func valueOf(x interface{}) Value {
|
|
64
|
-
switch t := x.(type) {
|
|
65
|
-
case value:
|
|
66
|
-
x = t.Value
|
|
67
|
-
|
|
68
|
-
case function:
|
|
69
|
-
x = t.fn
|
|
70
|
-
|
|
71
|
-
case *browserWindow:
|
|
72
|
-
x = t.Value
|
|
73
|
-
|
|
74
|
-
case Event:
|
|
75
|
-
return valueOf(t.Value)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return val(js.ValueOf(x))
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
type function struct {
|
|
82
|
-
value
|
|
83
|
-
fn js.Func
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
func (f function) Release() {
|
|
87
|
-
f.fn.Release()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
func funcOf(fn func(this Value, args []Value) interface{}) Func {
|
|
91
|
-
f := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
92
|
-
wargs := make([]Value, len(args))
|
|
93
|
-
for i, a := range args {
|
|
94
|
-
wargs[i] = val(a)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return fn(val(this), wargs)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
return function{
|
|
101
|
-
value: value{Value: f.Value},
|
|
102
|
-
fn: f,
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
type browserWindow struct {
|
|
107
|
-
value
|
|
108
|
-
|
|
109
|
-
cursorX int
|
|
110
|
-
cursorY int
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
func (w *browserWindow) Size() (width int, height int) {
|
|
114
|
-
getSize := func(axis string) int {
|
|
115
|
-
size := w.Get("inner" + axis)
|
|
116
|
-
if !size.Truthy() {
|
|
117
|
-
size = w.
|
|
118
|
-
Get("document").
|
|
119
|
-
Get("documentElement").
|
|
120
|
-
Get("client" + axis)
|
|
121
|
-
}
|
|
122
|
-
if !size.Truthy() {
|
|
123
|
-
size = w.
|
|
124
|
-
Get("document").
|
|
125
|
-
Get("body").
|
|
126
|
-
Get("client" + axis)
|
|
127
|
-
}
|
|
128
|
-
if size.Type() != TypeNumber {
|
|
129
|
-
return 0
|
|
130
|
-
}
|
|
131
|
-
return size.Int()
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return getSize("Width"), getSize("Height")
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
func (w *browserWindow) CursorPosition() (x, y int) {
|
|
138
|
-
return w.cursorX, w.cursorY
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
func (w *browserWindow) SetCursorPosition(x, y int) {
|
|
142
|
-
w.cursorX = x
|
|
143
|
-
w.cursorY = y
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
func (w *browserWindow) GetElementByID(id string) Value {
|
|
147
|
-
return w.Get("document").Call("getElementById", id)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
func (w *browserWindow) ScrollToID(id string) {
|
|
151
|
-
if elem := w.GetElementByID(id); elem.Truthy() {
|
|
152
|
-
elem.Call("scrollIntoView")
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
func (w *browserWindow) AddEventListener(event string, h EventHandler) func() {
|
|
157
|
-
panic("not implemented")
|
|
158
|
-
// callback := MakeJsEventHandler(body, h)
|
|
159
|
-
// w.Call("addEventListener", event, callback)
|
|
160
|
-
|
|
161
|
-
// return func() {
|
|
162
|
-
// w.Call("removeEventListener", event, callback)
|
|
163
|
-
// callback.Release()
|
|
164
|
-
// }
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
func (w *browserWindow) Location() *Location {
|
|
168
|
-
return &Location{
|
|
169
|
-
value: w.Get("location"),
|
|
170
|
-
Href: w.
|
|
171
|
-
Get("location").
|
|
172
|
-
Get("href").
|
|
173
|
-
String(),
|
|
174
|
-
Pathname: w.
|
|
175
|
-
Get("location").
|
|
176
|
-
Get("pathname").
|
|
177
|
-
String(),
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
func (l *Location) Reload() {
|
|
182
|
-
l.value.Call("reload")
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
func val(v js.Value) Value {
|
|
186
|
-
return value{Value: v}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
func jsval(v Value) js.Value {
|
|
190
|
-
switch v := v.(type) {
|
|
191
|
-
case value:
|
|
192
|
-
return v.Value
|
|
193
|
-
|
|
194
|
-
case function:
|
|
195
|
-
return v.Value
|
|
196
|
-
|
|
197
|
-
case *browserWindow:
|
|
198
|
-
return v.Value
|
|
199
|
-
|
|
200
|
-
case Event:
|
|
201
|
-
return jsval(v.Value)
|
|
202
|
-
|
|
203
|
-
default:
|
|
204
|
-
// + reflect.TypeOf(v).String()
|
|
205
|
-
println("syscall/js value conversion failed type: ", v)
|
|
206
|
-
return js.Undefined()
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// JSValue returns the underlying syscall/js value of the given Javascript
|
|
211
|
-
// value.
|
|
212
|
-
func JSValue(v Value) js.Value {
|
|
213
|
-
return jsval(v)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
func copyBytesToGo(dst []byte, src Value) int {
|
|
217
|
-
return js.CopyBytesToGo(dst, jsval(src))
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
func copyBytesToJS(dst Value, src []byte) int {
|
|
221
|
-
return js.CopyBytesToJS(jsval(dst), src)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
func cleanArgs(args ...interface{}) []interface{} {
|
|
225
|
-
for i, a := range args {
|
|
226
|
-
|
|
227
|
-
args[i] = cleanArg(a)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return args
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
func cleanArg(v interface{}) interface{} {
|
|
234
|
-
switch v := v.(type) {
|
|
235
|
-
case map[string]interface{}:
|
|
236
|
-
m := make(map[string]interface{}, len(v))
|
|
237
|
-
for key, val := range v {
|
|
238
|
-
m[key] = cleanArg(val)
|
|
239
|
-
}
|
|
240
|
-
return m
|
|
241
|
-
|
|
242
|
-
case []interface{}:
|
|
243
|
-
s := make([]interface{}, len(v))
|
|
244
|
-
for i, val := range v {
|
|
245
|
-
s[i] = cleanArgs(val)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
case Wrapper:
|
|
249
|
-
return jsval(v.JSValue())
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return v
|
|
253
|
-
|
|
254
|
-
}
|
node.go
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
package app
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"io"
|
|
5
|
-
|
|
6
|
-
"github.com/pyros2097/wapp/errors"
|
|
7
|
-
"github.com/pyros2097/wapp/js"
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
// UI is the interface that describes a user interface element such as
|
|
11
|
-
// components and HTML elements.
|
|
12
|
-
type UI interface {
|
|
13
|
-
// JSValue returns the javascript value linked to the element.
|
|
14
|
-
JSValue() js.Value
|
|
15
|
-
|
|
16
|
-
// Reports whether the element is mounted.
|
|
17
|
-
Mounted() bool
|
|
18
|
-
|
|
19
|
-
name() string
|
|
20
|
-
self() UI
|
|
21
|
-
setSelf(UI)
|
|
22
|
-
attributes() map[string]string
|
|
23
|
-
eventHandlers() map[string]js.EventHandler
|
|
24
|
-
parent() UI
|
|
25
|
-
setParent(UI)
|
|
26
|
-
children() []UI
|
|
27
|
-
mount() error
|
|
28
|
-
dismount()
|
|
29
|
-
update(UI) error
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
func trackMousePosition(e js.Event) {
|
|
33
|
-
x := e.Get("clientX")
|
|
34
|
-
if !x.Truthy() {
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
y := e.Get("clientY")
|
|
39
|
-
if !y.Truthy() {
|
|
40
|
-
return
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
js.Window.SetCursorPosition(x.Int(), y.Int())
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
func isErrReplace(err error) bool {
|
|
47
|
-
_, replace := errors.Tag(err, "replace")
|
|
48
|
-
return replace
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
func mount(n UI) error {
|
|
52
|
-
n.setSelf(n)
|
|
53
|
-
return n.mount()
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
func dismount(n UI) {
|
|
57
|
-
n.dismount()
|
|
58
|
-
n.setSelf(nil)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
func update(a, b UI) error {
|
|
62
|
-
a.setSelf(a)
|
|
63
|
-
b.setSelf(b)
|
|
64
|
-
return a.update(b)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
type WritableNode interface {
|
|
68
|
-
Html(w io.Writer)
|
|
69
|
-
HtmlWithIndent(w io.Writer, indent int)
|
|
70
|
-
}
|
node_test.go
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
package app
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"fmt"
|
|
5
|
-
"testing"
|
|
6
|
-
|
|
7
|
-
"github.com/pyros2097/wapp/errors"
|
|
8
|
-
"github.com/stretchr/testify/require"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
func TestIsErrReplace(t *testing.T) {
|
|
12
|
-
utests := []struct {
|
|
13
|
-
scenario string
|
|
14
|
-
err error
|
|
15
|
-
isErrReplace bool
|
|
16
|
-
}{
|
|
17
|
-
{
|
|
18
|
-
scenario: "error is a replace error",
|
|
19
|
-
err: errors.New("test").Tag("replace", true),
|
|
20
|
-
isErrReplace: true,
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
scenario: "error is not a replace error",
|
|
24
|
-
err: errors.New("test").Tag("test", true),
|
|
25
|
-
isErrReplace: false,
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
scenario: "standard error is not a replace error",
|
|
29
|
-
err: fmt.Errorf("test"),
|
|
30
|
-
isErrReplace: false,
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
scenario: "nil error is not a replace error",
|
|
34
|
-
err: nil,
|
|
35
|
-
isErrReplace: false,
|
|
36
|
-
},
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
for _, u := range utests {
|
|
40
|
-
t.Run(u.scenario, func(t *testing.T) {
|
|
41
|
-
res := isErrReplace(u.err)
|
|
42
|
-
require.Equal(t, u.isErrReplace, res)
|
|
43
|
-
})
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
type mountTest struct {
|
|
48
|
-
scenario string
|
|
49
|
-
node UI
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
func testMountDismount(t *testing.T, utests []mountTest) {
|
|
53
|
-
for _, u := range utests {
|
|
54
|
-
t.Run(u.scenario, func(t *testing.T) {
|
|
55
|
-
testSkipNonWasm(t)
|
|
56
|
-
|
|
57
|
-
n := u.node
|
|
58
|
-
err := mount(n)
|
|
59
|
-
require.NoError(t, err)
|
|
60
|
-
testMounted(t, n)
|
|
61
|
-
|
|
62
|
-
dismount(u.node)
|
|
63
|
-
testDismounted(t, n)
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
func testMounted(t *testing.T, n UI) {
|
|
69
|
-
require.NotNil(t, n.JSValue())
|
|
70
|
-
require.True(t, n.Mounted())
|
|
71
|
-
|
|
72
|
-
// switch n.Kind() {
|
|
73
|
-
// case HTML, Component:
|
|
74
|
-
// require.NotNil(t, n.self())
|
|
75
|
-
// }
|
|
76
|
-
|
|
77
|
-
for _, c := range n.children() {
|
|
78
|
-
require.Equal(t, n, c.parent())
|
|
79
|
-
testMounted(t, c)
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
func testDismounted(t *testing.T, n UI) {
|
|
84
|
-
require.Nil(t, n.JSValue())
|
|
85
|
-
require.False(t, n.Mounted())
|
|
86
|
-
|
|
87
|
-
// switch n.Kind() {
|
|
88
|
-
// case HTML, Component:
|
|
89
|
-
// require.Nil(t, n.self())
|
|
90
|
-
// }
|
|
91
|
-
|
|
92
|
-
for _, c := range n.children() {
|
|
93
|
-
testDismounted(t, c)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
type updateTest struct {
|
|
98
|
-
scenario string
|
|
99
|
-
a UI
|
|
100
|
-
b UI
|
|
101
|
-
matches []TestUIDescriptor
|
|
102
|
-
replaceErr bool
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
func testUpdate(t *testing.T, utests []updateTest) {
|
|
106
|
-
for _, u := range utests {
|
|
107
|
-
t.Run(u.scenario, func(t *testing.T) {
|
|
108
|
-
testSkipNonWasm(t)
|
|
109
|
-
|
|
110
|
-
err := mount(u.a)
|
|
111
|
-
require.NoError(t, err)
|
|
112
|
-
defer dismount(u.a)
|
|
113
|
-
|
|
114
|
-
err = update(u.a, u.b)
|
|
115
|
-
if u.replaceErr {
|
|
116
|
-
require.Error(t, err)
|
|
117
|
-
require.True(t, isErrReplace(err))
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
require.NoError(t, err)
|
|
122
|
-
|
|
123
|
-
for _, d := range u.matches {
|
|
124
|
-
require.NoError(t, TestMatch(u.a, d))
|
|
125
|
-
}
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
}
|
raw.go
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
package app
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"io"
|
|
5
|
-
"strings"
|
|
6
|
-
|
|
7
|
-
"github.com/pyros2097/wapp/errors"
|
|
8
|
-
"github.com/pyros2097/wapp/js"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
// Raw returns a ui element from the given raw value. HTML raw value must have a
|
|
12
|
-
// single root.
|
|
13
|
-
//
|
|
14
|
-
// It is not recommended to use this kind of node since there is no check on the
|
|
15
|
-
// raw string content.
|
|
16
|
-
func Raw(v string) UI {
|
|
17
|
-
v = strings.TrimSpace(v)
|
|
18
|
-
|
|
19
|
-
tag := rawRootTagName(v)
|
|
20
|
-
if tag == "" {
|
|
21
|
-
panic(errors.New("creating raw element failed").
|
|
22
|
-
Tag("reason", "opening tag not found"))
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return &raw{
|
|
26
|
-
value: v,
|
|
27
|
-
tag: tag,
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
type raw struct {
|
|
32
|
-
jsvalue js.Value
|
|
33
|
-
parentElem UI
|
|
34
|
-
tag string
|
|
35
|
-
value string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
func (r *raw) JSValue() js.Value {
|
|
39
|
-
return r.jsvalue
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
func (r *raw) Mounted() bool {
|
|
43
|
-
return r.jsvalue != nil
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
func (r *raw) name() string {
|
|
47
|
-
return "raw." + r.tag
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
func (r *raw) self() UI {
|
|
51
|
-
return r
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
func (r *raw) setSelf(UI) {
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
func (r *raw) attributes() map[string]string {
|
|
58
|
-
return nil
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
func (r *raw) eventHandlers() map[string]js.EventHandler {
|
|
62
|
-
return nil
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
func (r *raw) parent() UI {
|
|
66
|
-
return r.parentElem
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
func (r *raw) setParent(p UI) {
|
|
70
|
-
r.parentElem = p
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
func (r *raw) children() []UI {
|
|
74
|
-
return nil
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
func (r *raw) mount() error {
|
|
78
|
-
if r.Mounted() {
|
|
79
|
-
panic("mounting raw failed already mounted " + r.name())
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
wrapper := js.Window.Get("document").Call("createElement", "div")
|
|
83
|
-
wrapper.Set("innerHTML", r.value)
|
|
84
|
-
|
|
85
|
-
value := wrapper.Get("firstChild")
|
|
86
|
-
if !value.Truthy() {
|
|
87
|
-
panic("mounting raw failed converting raw html to html elements returned nil " + r.name() + " " + r.value)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
wrapper.Call("removeChild", value)
|
|
91
|
-
r.jsvalue = value
|
|
92
|
-
return nil
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
func (r *raw) dismount() {
|
|
96
|
-
r.jsvalue = nil
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
func (r *raw) update(n UI) error {
|
|
100
|
-
if !r.Mounted() {
|
|
101
|
-
return nil
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if r.name() != r.name() {
|
|
105
|
-
panic("updating raw element failed replace different element type current-name: " + r.name() + " updated-name: " + n.name())
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if v := n.(*raw).value; r.value != v {
|
|
109
|
-
panic("updating raw element failed replace different raw values current-value: " + r.value + " new-value: " + v)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return nil
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
func (r *raw) Html(w io.Writer) {
|
|
116
|
-
r.HtmlWithIndent(w, 0)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
func (r *raw) HtmlWithIndent(w io.Writer, indent int) {
|
|
120
|
-
writeIndent(w, indent)
|
|
121
|
-
w.Write(stob(r.value))
|
|
122
|
-
w.Write(ln())
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
func rawRootTagName(raw string) string {
|
|
126
|
-
raw = strings.TrimSpace(raw)
|
|
127
|
-
|
|
128
|
-
if strings.HasPrefix(raw, "</") || !strings.HasPrefix(raw, "<") {
|
|
129
|
-
return ""
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
end := -1
|
|
133
|
-
for i := 1; i < len(raw); i++ {
|
|
134
|
-
if raw[i] == ' ' ||
|
|
135
|
-
raw[i] == '\t' ||
|
|
136
|
-
raw[i] == '\n' ||
|
|
137
|
-
raw[i] == '>' {
|
|
138
|
-
end = i
|
|
139
|
-
break
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if end <= 0 {
|
|
144
|
-
return ""
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return raw[1:end]
|
|
148
|
-
}
|
raw_test.go
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
package app
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"testing"
|
|
5
|
-
|
|
6
|
-
"github.com/stretchr/testify/require"
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
var rawhtml = `
|
|
10
|
-
<div>
|
|
11
|
-
<p>slug: “/blog/gopibot-to-the-rescue”
|
|
12
|
-
date: “2017-10-04”</p>
|
|
13
|
-
|
|
14
|
-
<h2>title: “Gopibot To The Rescue”</h2>
|
|
15
|
-
|
|
16
|
-
<p>High Ho Gopibot away!</p>
|
|
17
|
-
|
|
18
|
-
<p>Everybody please meet Gopibot our chatops bot which I built at Numberz to help us deploy our countless microservices to QA.</p>
|
|
19
|
-
|
|
20
|
-
<p><img src="../images/posts/gopibot1.png" alt="Gopibot 1" /></p>
|
|
21
|
-
|
|
22
|
-
<p>So here is the backstory,</p>
|
|
23
|
-
|
|
24
|
-
<p>I was one of the developers who had access to our QA and Prod servers and the other person was the Head of Engineering
|
|
25
|
-
and he is generally a busy guy. So whenever there is a change that needs to be deployed everyone comes to me and tells me
|
|
26
|
-
to deploy their microservice/frontend to the QA and blatantly interrupts my awesome coding cycle.</p>
|
|
27
|
-
|
|
28
|
-
<p>Alright then, I break off from my flow, ssh into the server and start running the deploy command.
|
|
29
|
-
And all of you jsdev wannabes who have worked with react and webpack will know the horrors about deploying frontend code right.
|
|
30
|
-
It takes forever so I have to wait there looking at the console along with the dev who wanted me to deploy it (lets call him kokill for now).
|
|
31
|
-
So kokill and I patiently wait for the webpack build to finish. 1m , 2m, 3m and WTH 15m.
|
|
32
|
-
And then its built and the new frontend is deployed to QA. YES! Now I can continue with my work.
|
|
33
|
-
But wait then some other dev comes likes call him (D-Ne0) and he asks to deploy something else and again the same process
|
|
34
|
-
of ssh’ing the server and another wait. This got repetitive and irritating. Then I started searching for solutions to the problem
|
|
35
|
-
and looked high and low and thought that CI/CD is the only thing that can solve this problem. But then I saw something new called ChatOps
|
|
36
|
-
where developers have chatbots to talk to automate this manual work. Just like we have bots these days to help you out in your work
|
|
37
|
-
like getting your laundry, grocery and making orders.</p>
|
|
38
|
-
|
|
39
|
-
<p>So I decided to take a shot at this in my free time. And it seems it was simpler than I thought and decided to use Slack our primary
|
|
40
|
-
team communication platform. We used it daily for everything and I thought why not have a specific channel just where the bot resides
|
|
41
|
-
and people could talk to the bot.</p>
|
|
42
|
-
|
|
43
|
-
<p>Since we are typically a nodejs shop I decided to find a way to send messages to a slack bot. And slack has this really great sdk for nodejs.
|
|
44
|
-
<a href="https://github.com/slackapi/node-slack-sdk">https://github.com/slackapi/node-slack-sdk</a></p>
|
|
45
|
-
|
|
46
|
-
<p>First I went and created the bot in my slack team settings.
|
|
47
|
-
And then wrote a script which would allow it to read messages from the channel it was added.</p>
|
|
48
|
-
|
|
49
|
-
<p>Here is the simple script,</p>
|
|
50
|
-
</div>
|
|
51
|
-
`
|
|
52
|
-
|
|
53
|
-
func TestRawRootTagName(t *testing.T) {
|
|
54
|
-
tests := []struct {
|
|
55
|
-
scenario string
|
|
56
|
-
raw string
|
|
57
|
-
expected string
|
|
58
|
-
}{
|
|
59
|
-
{
|
|
60
|
-
scenario: "tag set",
|
|
61
|
-
raw: `
|
|
62
|
-
<div>
|
|
63
|
-
<span></span>
|
|
64
|
-
</div>`,
|
|
65
|
-
expected: "div",
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
scenario: "tag is empty",
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
scenario: "opening tag missing",
|
|
72
|
-
raw: "</div>",
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
scenario: "tag is not set",
|
|
76
|
-
raw: "div",
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
scenario: "tag is not closing",
|
|
80
|
-
raw: "<div",
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
scenario: "tag is not closing",
|
|
84
|
-
raw: "<div",
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
scenario: "tag without value",
|
|
88
|
-
raw: "<>",
|
|
89
|
-
},
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
for _, test := range tests {
|
|
93
|
-
t.Run(test.scenario, func(t *testing.T) {
|
|
94
|
-
tag := rawRootTagName(test.raw)
|
|
95
|
-
require.Equal(t, test.expected, tag)
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
func TestRawMountDismount(t *testing.T) {
|
|
101
|
-
testMountDismount(t, []mountTest{
|
|
102
|
-
{
|
|
103
|
-
scenario: "raw html element",
|
|
104
|
-
node: Raw(`<h1>Hello</h1>`),
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
scenario: "raw svg element",
|
|
108
|
-
node: Raw(`<svg></svg>`),
|
|
109
|
-
},
|
|
110
|
-
})
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
func TestRawUpdate(t *testing.T) {
|
|
114
|
-
testUpdate(t, []updateTest{
|
|
115
|
-
{
|
|
116
|
-
scenario: "raw html element returns replace error when updated with a non text-element",
|
|
117
|
-
a: Raw("<svg></svg>"),
|
|
118
|
-
b: Div(),
|
|
119
|
-
replaceErr: true,
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
scenario: "raw html element is replace by another raw html element",
|
|
123
|
-
a: Div(
|
|
124
|
-
Raw("<div></div>"),
|
|
125
|
-
),
|
|
126
|
-
b: Div(
|
|
127
|
-
Raw("<svg></svg>"),
|
|
128
|
-
),
|
|
129
|
-
matches: []TestUIDescriptor{
|
|
130
|
-
{
|
|
131
|
-
Path: TestPath(),
|
|
132
|
-
Expected: Div(),
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
Path: TestPath(0),
|
|
136
|
-
Expected: Raw("<svg></svg>"),
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
scenario: "raw html element is replace by non-raw html element",
|
|
142
|
-
a: Div(
|
|
143
|
-
Raw("<div></div>"),
|
|
144
|
-
),
|
|
145
|
-
b: Div(
|
|
146
|
-
Text("hello"),
|
|
147
|
-
),
|
|
148
|
-
matches: []TestUIDescriptor{
|
|
149
|
-
{
|
|
150
|
-
Path: TestPath(),
|
|
151
|
-
Expected: Div(),
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
Path: TestPath(0),
|
|
155
|
-
Expected: Text("hello"),
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
})
|
|
160
|
-
}
|
router.go
CHANGED
|
@@ -2,9 +2,14 @@ package app
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
|
+
"embed"
|
|
6
|
+
"errors"
|
|
7
|
+
"net/http"
|
|
8
|
+
"os"
|
|
5
9
|
"strings"
|
|
6
10
|
"unicode"
|
|
7
11
|
"unicode/utf8"
|
|
12
|
+
// "github.com/aws/aws-lambda-go/events"
|
|
8
13
|
)
|
|
9
14
|
|
|
10
15
|
func min(a, b int) int {
|
|
@@ -75,7 +80,7 @@ type node struct {
|
|
|
75
80
|
nType nodeType
|
|
76
81
|
priority uint32
|
|
77
82
|
children []*node
|
|
78
|
-
handle
|
|
83
|
+
handle RouteFn
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
// Increments priority of the given child and reorders if necessary
|
|
@@ -103,7 +108,7 @@ func (n *node) incrementChildPrio(pos int) int {
|
|
|
103
108
|
|
|
104
109
|
// addRoute adds a node with the given handle to the path.
|
|
105
110
|
// Not concurrency-safe!
|
|
106
|
-
func (n *node) addRoute(path string, handle
|
|
111
|
+
func (n *node) addRoute(path string, handle RouteFn) {
|
|
107
112
|
fullPath := path
|
|
108
113
|
n.priority++
|
|
109
114
|
|
|
@@ -211,7 +216,7 @@ walk:
|
|
|
211
216
|
}
|
|
212
217
|
}
|
|
213
218
|
|
|
214
|
-
func (n *node) insertChild(path, fullPath string, handle
|
|
219
|
+
func (n *node) insertChild(path, fullPath string, handle RouteFn) {
|
|
215
220
|
for {
|
|
216
221
|
// Find prefix until first wildcard
|
|
217
222
|
wildcard, i, valid := findWildcard(path)
|
|
@@ -320,7 +325,7 @@ func (n *node) insertChild(path, fullPath string, handle interface{}) {
|
|
|
320
325
|
// If no handle can be found, a TSR (trailing slash redirect) recommendation is
|
|
321
326
|
// made if a handle exists with an extra (without the) trailing slash for the
|
|
322
327
|
// given path.
|
|
323
|
-
func (n *node) getValue(path string, params func() *Params) (handle
|
|
328
|
+
func (n *node) getValue(path string, params func() *Params) (handle RouteFn, ps *Params, tsr bool) {
|
|
324
329
|
walk: // Outer loop for walking the tree
|
|
325
330
|
for {
|
|
326
331
|
prefix := n.path
|
|
@@ -710,6 +715,8 @@ func ParamsFromContext(ctx context.Context) Params {
|
|
|
710
715
|
return p
|
|
711
716
|
}
|
|
712
717
|
|
|
718
|
+
type RouteFn func(w http.ResponseWriter, r *http.Request) *Element
|
|
719
|
+
|
|
713
720
|
// Router is a http.Handler which can be used to dispatch requests to different
|
|
714
721
|
// handler functions via configurable routes
|
|
715
722
|
type Router struct {
|
|
@@ -723,15 +730,15 @@ type Router struct {
|
|
|
723
730
|
RedirectTrailingSlash bool
|
|
724
731
|
|
|
725
732
|
// Configurable handler which is called when no matching route is found.
|
|
726
|
-
NotFound
|
|
733
|
+
NotFound RouteFn
|
|
727
734
|
|
|
728
735
|
// Configurable handler which is called when an error occurs.
|
|
729
|
-
Error
|
|
736
|
+
Error RouteFn
|
|
730
737
|
}
|
|
731
738
|
|
|
732
739
|
var globalRouter = &Router{
|
|
733
740
|
RedirectTrailingSlash: true,
|
|
734
|
-
NotFound: func(
|
|
741
|
+
NotFound: func(w http.ResponseWriter, r *http.Request) *Element {
|
|
735
742
|
return Col(
|
|
736
743
|
Row(
|
|
737
744
|
Text("This is the default 404 - Not Found Route handler"),
|
|
@@ -741,46 +748,47 @@ var globalRouter = &Router{
|
|
|
741
748
|
),
|
|
742
749
|
)
|
|
743
750
|
},
|
|
744
|
-
Error: func(
|
|
751
|
+
Error: func(w http.ResponseWriter, r *http.Request) *Element {
|
|
752
|
+
// err.Error()s
|
|
745
753
|
return Col(
|
|
746
754
|
Row(
|
|
747
755
|
Text("This is the default 500 - Internal Server Error Route handler"),
|
|
748
756
|
),
|
|
749
757
|
Row(
|
|
750
|
-
Text("Error: "
|
|
758
|
+
Text("Error: "),
|
|
751
759
|
),
|
|
752
760
|
Row(
|
|
753
|
-
Text("SetErrorHandler(func(
|
|
761
|
+
Text("SetErrorHandler(func(r *http.Request, w http.ResponseWriter) *Element {}) to override it"),
|
|
754
762
|
),
|
|
755
763
|
)
|
|
756
764
|
},
|
|
757
765
|
}
|
|
758
766
|
|
|
759
|
-
func SetNotFoundHandler(b
|
|
767
|
+
func SetNotFoundHandler(b RouteFn) {
|
|
760
768
|
globalRouter.NotFound = b
|
|
761
769
|
}
|
|
762
770
|
|
|
763
|
-
func SetErrorHandler(b
|
|
771
|
+
func SetErrorHandler(b RouteFn) {
|
|
764
772
|
globalRouter.Error = b
|
|
765
773
|
}
|
|
766
774
|
|
|
767
775
|
// GET route
|
|
768
|
-
func (r *Router) GET(path string, handle
|
|
776
|
+
func (r *Router) GET(path string, handle RouteFn) {
|
|
769
777
|
r.Handle("GET", path, handle)
|
|
770
778
|
}
|
|
771
779
|
|
|
772
780
|
// POST route
|
|
773
|
-
func (r *Router) POST(path string, handle
|
|
781
|
+
func (r *Router) POST(path string, handle RouteFn) {
|
|
774
782
|
r.Handle("POST", path, handle)
|
|
775
783
|
}
|
|
776
784
|
|
|
777
785
|
// PUT route
|
|
778
|
-
func (r *Router) PUT(path string, handle
|
|
786
|
+
func (r *Router) PUT(path string, handle RouteFn) {
|
|
779
787
|
r.Handle("PUT", path, handle)
|
|
780
788
|
}
|
|
781
789
|
|
|
782
790
|
// DELETE route
|
|
783
|
-
func (r *Router) DELETE(path string, handle
|
|
791
|
+
func (r *Router) DELETE(path string, handle RouteFn) {
|
|
784
792
|
r.Handle("DELETE", path, handle)
|
|
785
793
|
}
|
|
786
794
|
|
|
@@ -792,7 +800,7 @@ func (r *Router) DELETE(path string, handle interface{}) {
|
|
|
792
800
|
// This function is intended for bulk loading and to allow the usage of less
|
|
793
801
|
// frequently used, non-standardized or custom methods (e.g. for internal
|
|
794
802
|
// communication with a proxy).
|
|
795
|
-
func (r *Router) Handle(method, path string, handle
|
|
803
|
+
func (r *Router) Handle(method, path string, handle RouteFn) {
|
|
796
804
|
varsCount := uint16(0)
|
|
797
805
|
|
|
798
806
|
if method == "" {
|
|
@@ -805,11 +813,6 @@ func (r *Router) Handle(method, path string, handle interface{}) {
|
|
|
805
813
|
panic("handle must not be nil")
|
|
806
814
|
}
|
|
807
815
|
|
|
808
|
-
// if r.SaveMatchedRoutePath {
|
|
809
|
-
// varsCount++
|
|
810
|
-
// handle = r.saveMatchedRoutePath(path, handle)
|
|
811
|
-
// }
|
|
812
|
-
|
|
813
816
|
if r.trees == nil {
|
|
814
817
|
r.trees = make(map[string]*node)
|
|
815
818
|
}
|
|
@@ -846,3 +849,120 @@ func (r *Router) Lookup(method, path string) (interface{}, Params, bool) {
|
|
|
846
849
|
}
|
|
847
850
|
return nil, nil, false
|
|
848
851
|
}
|
|
852
|
+
|
|
853
|
+
// ServeHTTP makes the router implement the http.Handler interface.
|
|
854
|
+
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
855
|
+
// Handle errors
|
|
856
|
+
defer func() {
|
|
857
|
+
if rcv := recover(); rcv != nil {
|
|
858
|
+
var err error
|
|
859
|
+
switch x := rcv.(type) {
|
|
860
|
+
case string:
|
|
861
|
+
err = errors.New(x)
|
|
862
|
+
case error:
|
|
863
|
+
err = x
|
|
864
|
+
default:
|
|
865
|
+
err = errors.New("unknown panic")
|
|
866
|
+
}
|
|
867
|
+
w.Header().Set("Content-Type", "text/html")
|
|
868
|
+
w.WriteHeader(http.StatusInternalServerError)
|
|
869
|
+
w.Write([]byte(err.Error()))
|
|
870
|
+
// r.Error(w, req)
|
|
871
|
+
}
|
|
872
|
+
}()
|
|
873
|
+
|
|
874
|
+
path := req.URL.Path
|
|
875
|
+
println("route: " + req.URL.Path)
|
|
876
|
+
|
|
877
|
+
if root := r.trees[req.Method]; root != nil {
|
|
878
|
+
// TODO: use _ ps save it to context for useParam()
|
|
879
|
+
if handle, _, tsr := root.getValue(path, nil); handle != nil {
|
|
880
|
+
// w.Header().Set("Content-Type", "text/html")
|
|
881
|
+
// w.WriteHeader(http.StatusOK)
|
|
882
|
+
elm := handle(w, req)
|
|
883
|
+
if elm != nil {
|
|
884
|
+
elm.HtmlWithIndent(w, 2)
|
|
885
|
+
}
|
|
886
|
+
return
|
|
887
|
+
} else if req.Method != http.MethodConnect && path != "/" {
|
|
888
|
+
// Moved Permanently, request with GET method
|
|
889
|
+
code := http.StatusMovedPermanently
|
|
890
|
+
if req.Method != http.MethodGet {
|
|
891
|
+
// Permanent Redirect, request with same method
|
|
892
|
+
code = http.StatusPermanentRedirect
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if tsr && r.RedirectTrailingSlash {
|
|
896
|
+
if len(path) > 1 && path[len(path)-1] == '/' {
|
|
897
|
+
req.URL.Path = path[:len(path)-1]
|
|
898
|
+
} else {
|
|
899
|
+
req.URL.Path = path + "/"
|
|
900
|
+
}
|
|
901
|
+
http.Redirect(w, req, req.URL.String(), code)
|
|
902
|
+
return
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Handle 404
|
|
908
|
+
w.Header().Set("Content-Type", "text/html")
|
|
909
|
+
w.WriteHeader(http.StatusNotFound)
|
|
910
|
+
r.NotFound(w, req)
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// func (r *Router) Lambda(ctx context.Context, e events.APIGatewayV2HTTPRequest) (res events.APIGatewayV2HTTPResponse, err error) {
|
|
914
|
+
// res.StatusCode = 200
|
|
915
|
+
// res.Headers = map[string]string{
|
|
916
|
+
// "Content-Type": "text/html",
|
|
917
|
+
// }
|
|
918
|
+
// // Handle errors
|
|
919
|
+
// defer func() {
|
|
920
|
+
// if rcv := recover(); rcv != nil {
|
|
921
|
+
// switch x := rcv.(type) {
|
|
922
|
+
// case string:
|
|
923
|
+
// err = errors.New(x)
|
|
924
|
+
// case error:
|
|
925
|
+
// err = x
|
|
926
|
+
// default:
|
|
927
|
+
// err = errors.New("unknown panic")
|
|
928
|
+
// }
|
|
929
|
+
// res.Body = r.getPage(r.Error(err))
|
|
930
|
+
// }
|
|
931
|
+
// }()
|
|
932
|
+
|
|
933
|
+
// println("route: " + e.RawPath)
|
|
934
|
+
// path := strings.Replace(e.RawPath, "/Prod/", "/", 1)
|
|
935
|
+
// if root := r.trees[e.RequestContext.HTTP.Method]; root != nil {
|
|
936
|
+
// if handle, _, _ := root.getValue(path, nil); handle != nil {
|
|
937
|
+
// res.Body = r.getPage(handle)
|
|
938
|
+
// return
|
|
939
|
+
// }
|
|
940
|
+
// }
|
|
941
|
+
|
|
942
|
+
// // Handle 404
|
|
943
|
+
// res.StatusCode = http.StatusNotFound
|
|
944
|
+
// res.Body = r.getPage(r.NotFound(NewRenderContext()))
|
|
945
|
+
// return
|
|
946
|
+
// }
|
|
947
|
+
|
|
948
|
+
func Run(assetsFS embed.FS) {
|
|
949
|
+
isLambda := os.Getenv("_LAMBDA_SERVER_PORT") != ""
|
|
950
|
+
if !isLambda {
|
|
951
|
+
assetsHandler := http.FileServer(http.FS(assetsFS))
|
|
952
|
+
globalRouter.GET("/assets/*filepath", func(w http.ResponseWriter, r *http.Request) *Element {
|
|
953
|
+
assetsHandler.ServeHTTP(w, r)
|
|
954
|
+
return nil
|
|
955
|
+
})
|
|
956
|
+
println("Serving on http://localhost:1234")
|
|
957
|
+
http.ListenAndServe(":1234", globalRouter)
|
|
958
|
+
} else {
|
|
959
|
+
// println("running in lambda mode")
|
|
960
|
+
// lambda.Start(globalRouter.Lambda)
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
func Route(path string, render RouteFn) {
|
|
966
|
+
println("registering route: " + path)
|
|
967
|
+
globalRouter.GET(path, render)
|
|
968
|
+
}
|
router_nowasm.go
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
// +build !wasm
|
|
2
|
-
|
|
3
|
-
package app
|
|
4
|
-
|
|
5
|
-
import (
|
|
6
|
-
"bytes"
|
|
7
|
-
"context"
|
|
8
|
-
"net/http"
|
|
9
|
-
"strings"
|
|
10
|
-
|
|
11
|
-
"github.com/aws/aws-lambda-go/events"
|
|
12
|
-
"github.com/pyros2097/wapp/errors"
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
// ServeHTTP makes the router implement the http.Handler interface.
|
|
16
|
-
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
17
|
-
// Handle errors
|
|
18
|
-
defer func() {
|
|
19
|
-
if rcv := recover(); rcv != nil {
|
|
20
|
-
var err error
|
|
21
|
-
switch x := rcv.(type) {
|
|
22
|
-
case string:
|
|
23
|
-
err = errors.New(x)
|
|
24
|
-
case error:
|
|
25
|
-
err = x
|
|
26
|
-
default:
|
|
27
|
-
err = errors.New("unknown panic")
|
|
28
|
-
}
|
|
29
|
-
w.Header().Set("Content-Type", "text/html")
|
|
30
|
-
w.WriteHeader(http.StatusInternalServerError)
|
|
31
|
-
writePage(r.Error(NewRenderContext(), err), w)
|
|
32
|
-
}
|
|
33
|
-
}()
|
|
34
|
-
|
|
35
|
-
path := req.URL.Path
|
|
36
|
-
println("route: " + req.URL.Path)
|
|
37
|
-
|
|
38
|
-
if root := r.trees[req.Method]; root != nil {
|
|
39
|
-
// TODO: use _ ps save it to context for useParam()
|
|
40
|
-
if handle, _, tsr := root.getValue(path, nil); handle != nil {
|
|
41
|
-
if render, ok := handle.(RenderFunc); ok {
|
|
42
|
-
w.Header().Set("Content-Type", "text/html")
|
|
43
|
-
w.WriteHeader(http.StatusOK)
|
|
44
|
-
writePage(render(NewRenderContext()), w)
|
|
45
|
-
return
|
|
46
|
-
} else {
|
|
47
|
-
handle.(func(w http.ResponseWriter, r *http.Request))(w, req)
|
|
48
|
-
return
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
} else if req.Method != http.MethodConnect && path != "/" {
|
|
52
|
-
// Moved Permanently, request with GET method
|
|
53
|
-
code := http.StatusMovedPermanently
|
|
54
|
-
if req.Method != http.MethodGet {
|
|
55
|
-
// Permanent Redirect, request with same method
|
|
56
|
-
code = http.StatusPermanentRedirect
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if tsr && r.RedirectTrailingSlash {
|
|
60
|
-
if len(path) > 1 && path[len(path)-1] == '/' {
|
|
61
|
-
req.URL.Path = path[:len(path)-1]
|
|
62
|
-
} else {
|
|
63
|
-
req.URL.Path = path + "/"
|
|
64
|
-
}
|
|
65
|
-
http.Redirect(w, req, req.URL.String(), code)
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Handle 404
|
|
72
|
-
w.Header().Set("Content-Type", "text/html")
|
|
73
|
-
w.WriteHeader(http.StatusNotFound)
|
|
74
|
-
writePage(r.NotFound(NewRenderContext()), w)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
func (r *Router) getPage(ui UI) string {
|
|
78
|
-
b := bytes.NewBuffer(nil)
|
|
79
|
-
writePage(ui, b)
|
|
80
|
-
return b.String()
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
func (r *Router) Lambda(ctx context.Context, e events.APIGatewayV2HTTPRequest) (res events.APIGatewayV2HTTPResponse, err error) {
|
|
84
|
-
res.StatusCode = 200
|
|
85
|
-
res.Headers = map[string]string{
|
|
86
|
-
"Content-Type": "text/html",
|
|
87
|
-
}
|
|
88
|
-
// Handle errors
|
|
89
|
-
defer func() {
|
|
90
|
-
if rcv := recover(); rcv != nil {
|
|
91
|
-
switch x := rcv.(type) {
|
|
92
|
-
case string:
|
|
93
|
-
err = errors.New(x)
|
|
94
|
-
case error:
|
|
95
|
-
err = x
|
|
96
|
-
default:
|
|
97
|
-
err = errors.New("unknown panic")
|
|
98
|
-
}
|
|
99
|
-
res.Body = r.getPage(r.Error(NewRenderContext(), err))
|
|
100
|
-
}
|
|
101
|
-
}()
|
|
102
|
-
|
|
103
|
-
println("route: " + e.RawPath)
|
|
104
|
-
path := strings.Replace(e.RawPath, "/Prod/", "/", 1)
|
|
105
|
-
if root := r.trees[e.RequestContext.HTTP.Method]; root != nil {
|
|
106
|
-
if handle, _, _ := root.getValue(path, nil); handle != nil {
|
|
107
|
-
res.Body = r.getPage(handle.(RenderFunc)(NewRenderContext()))
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Handle 404
|
|
113
|
-
res.StatusCode = http.StatusNotFound
|
|
114
|
-
res.Body = r.getPage(r.NotFound(NewRenderContext()))
|
|
115
|
-
return
|
|
116
|
-
}
|
storage.go
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
package app
|
|
2
|
-
|
|
3
|
-
var (
|
|
4
|
-
// LocalStorage is a storage that uses the browser local storage associated
|
|
5
|
-
// to the document origin. Data stored has no expiration time.
|
|
6
|
-
LocalStorage BrowserStorage
|
|
7
|
-
|
|
8
|
-
// SessionStorage is a storage that uses the browser session storage
|
|
9
|
-
// associated to the document origin. Data stored expire when the page
|
|
10
|
-
// session ends.
|
|
11
|
-
SessionStorage BrowserStorage
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
// BrowserStorage is the interface that describes a web browser storage.
|
|
15
|
-
type BrowserStorage interface {
|
|
16
|
-
// Set sets the value to the given key. The value must be json convertible.
|
|
17
|
-
Set(k string, v interface{}) error
|
|
18
|
-
|
|
19
|
-
// Get gets the item associated to the given key and store it in the given
|
|
20
|
-
// value.
|
|
21
|
-
// It returns an error if v is not a pointer.
|
|
22
|
-
Get(k string, v interface{}) error
|
|
23
|
-
|
|
24
|
-
// Del deletes the item associated with the given key.
|
|
25
|
-
Del(k string)
|
|
26
|
-
|
|
27
|
-
// Clear deletes all items.
|
|
28
|
-
Clear()
|
|
29
|
-
}
|
storage_nowasm.go
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// +build !wasm
|
|
2
|
-
|
|
3
|
-
package app
|
|
4
|
-
|
|
5
|
-
import "encoding/json"
|
|
6
|
-
|
|
7
|
-
func init() {
|
|
8
|
-
LocalStorage = make(memoryStorage)
|
|
9
|
-
SessionStorage = make(memoryStorage)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type memoryStorage map[string][]byte
|
|
13
|
-
|
|
14
|
-
func (s memoryStorage) Set(k string, v interface{}) error {
|
|
15
|
-
b, err := json.Marshal(v)
|
|
16
|
-
if err != nil {
|
|
17
|
-
return err
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
s[k] = b
|
|
21
|
-
return nil
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
func (s memoryStorage) Get(k string, v interface{}) error {
|
|
25
|
-
if _, ok := s[k]; !ok {
|
|
26
|
-
return nil
|
|
27
|
-
}
|
|
28
|
-
return json.Unmarshal(s[k], v)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
func (s memoryStorage) Del(k string) {
|
|
32
|
-
delete(s, k)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
func (s memoryStorage) Clear() {
|
|
36
|
-
for k := range s {
|
|
37
|
-
delete(s, k)
|
|
38
|
-
}
|
|
39
|
-
}
|
storage_test.go
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
package app
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"fmt"
|
|
5
|
-
"testing"
|
|
6
|
-
|
|
7
|
-
"github.com/stretchr/testify/require"
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
func TestLocalStorage(t *testing.T) {
|
|
11
|
-
testBrowserStorage(t, LocalStorage)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
func TestLocalStorageFull(t *testing.T) {
|
|
15
|
-
testBrowserStorageFull(t, LocalStorage)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
func TestSessionStorage(t *testing.T) {
|
|
19
|
-
testBrowserStorage(t, SessionStorage)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
func TestSessionStorageFull(t *testing.T) {
|
|
23
|
-
testBrowserStorageFull(t, SessionStorage)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
type obj struct {
|
|
27
|
-
Foo int
|
|
28
|
-
Bar string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
func testBrowserStorage(t *testing.T, s BrowserStorage) {
|
|
32
|
-
tests := []struct {
|
|
33
|
-
scenario string
|
|
34
|
-
function func(*testing.T, BrowserStorage)
|
|
35
|
-
}{
|
|
36
|
-
{
|
|
37
|
-
scenario: "key does not exists",
|
|
38
|
-
function: testBrowserStorageGetNotExists,
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
scenario: "key is set and get",
|
|
42
|
-
function: testBrowserStorageSetGet,
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
scenario: "key is deleted",
|
|
46
|
-
function: testBrowserStorageDel,
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
scenario: "storage is cleared",
|
|
50
|
-
function: testBrowserStorageClear,
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
scenario: "set a non json value returns an error",
|
|
54
|
-
function: testBrowserStorageSetError,
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
scenario: "get with non json value receiver returns an error",
|
|
58
|
-
function: testBrowserStorageGetError,
|
|
59
|
-
},
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
for _, test := range tests {
|
|
63
|
-
t.Run(test.scenario, func(t *testing.T) {
|
|
64
|
-
test.function(t, s)
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
func testBrowserStorageGetNotExists(t *testing.T, s BrowserStorage) {
|
|
70
|
-
var o obj
|
|
71
|
-
err := s.Get("/notexists", &o)
|
|
72
|
-
require.NoError(t, err)
|
|
73
|
-
require.Zero(t, o)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
func testBrowserStorageSetGet(t *testing.T, s BrowserStorage) {
|
|
77
|
-
var o obj
|
|
78
|
-
err := s.Set("/exists", obj{
|
|
79
|
-
Foo: 42,
|
|
80
|
-
Bar: "hello",
|
|
81
|
-
})
|
|
82
|
-
require.NoError(t, err)
|
|
83
|
-
|
|
84
|
-
err = s.Get("/exists", &o)
|
|
85
|
-
require.NoError(t, err)
|
|
86
|
-
require.Equal(t, 42, o.Foo)
|
|
87
|
-
require.Equal(t, "hello", o.Bar)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
func testBrowserStorageDel(t *testing.T, s BrowserStorage) {
|
|
91
|
-
var o obj
|
|
92
|
-
err := s.Set("/deleted", obj{
|
|
93
|
-
Foo: 42,
|
|
94
|
-
Bar: "bye",
|
|
95
|
-
})
|
|
96
|
-
require.NoError(t, err)
|
|
97
|
-
|
|
98
|
-
s.Del("/deleted")
|
|
99
|
-
err = s.Get("/deleted", &o)
|
|
100
|
-
require.NoError(t, err)
|
|
101
|
-
require.Zero(t, o)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
func testBrowserStorageClear(t *testing.T, s BrowserStorage) {
|
|
105
|
-
var o obj
|
|
106
|
-
err := s.Set("/cleared", obj{
|
|
107
|
-
Foo: 42,
|
|
108
|
-
Bar: "sayonara",
|
|
109
|
-
})
|
|
110
|
-
require.NoError(t, err)
|
|
111
|
-
|
|
112
|
-
s.Clear()
|
|
113
|
-
err = s.Get("/cleared", &o)
|
|
114
|
-
require.NoError(t, err)
|
|
115
|
-
require.Zero(t, o)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
func testBrowserStorageSetError(t *testing.T, s BrowserStorage) {
|
|
119
|
-
err := s.Set("/func", func() {})
|
|
120
|
-
require.Error(t, err)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
func testBrowserStorageGetError(t *testing.T, s BrowserStorage) {
|
|
124
|
-
err := s.Set("/value", obj{
|
|
125
|
-
Foo: 42,
|
|
126
|
-
Bar: "omae",
|
|
127
|
-
})
|
|
128
|
-
require.NoError(t, err)
|
|
129
|
-
|
|
130
|
-
var f func()
|
|
131
|
-
err = s.Get("/value", &f)
|
|
132
|
-
require.Error(t, err)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
func testBrowserStorageFull(t *testing.T, s BrowserStorage) {
|
|
136
|
-
testSkipNonWasm(t)
|
|
137
|
-
|
|
138
|
-
var err error
|
|
139
|
-
data := make([]byte, 4096)
|
|
140
|
-
i := 0
|
|
141
|
-
|
|
142
|
-
for {
|
|
143
|
-
key := fmt.Sprintf("/key_%d", i)
|
|
144
|
-
|
|
145
|
-
if err = s.Set(key, data); err != nil {
|
|
146
|
-
break
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
i++
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
require.Error(t, err)
|
|
153
|
-
t.Log(err)
|
|
154
|
-
}
|
storage_wasm.go
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
package app
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"encoding/json"
|
|
5
|
-
|
|
6
|
-
"github.com/pyros2097/wapp/js"
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
func init() {
|
|
10
|
-
LocalStorage = newJSStorage("localStorage")
|
|
11
|
-
SessionStorage = newJSStorage("sessionStorage")
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type jsStorage struct {
|
|
15
|
-
name string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
func newJSStorage(name string) *jsStorage {
|
|
19
|
-
return &jsStorage{name: name}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
func (s *jsStorage) Set(k string, v interface{}) (err error) {
|
|
23
|
-
defer func() {
|
|
24
|
-
r := recover()
|
|
25
|
-
if r != nil {
|
|
26
|
-
panic("setting storage value failed storage-type: " + s.name + "key " + k)
|
|
27
|
-
}
|
|
28
|
-
}()
|
|
29
|
-
b, err := json.Marshal(v)
|
|
30
|
-
if err != nil {
|
|
31
|
-
return err
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
js.Window.Get(s.name).Call("setItem", k, btos(b))
|
|
35
|
-
return nil
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
func (s *jsStorage) Get(k string, v interface{}) error {
|
|
39
|
-
item := js.Window.Get(s.name).Call("getItem", k)
|
|
40
|
-
if !item.Truthy() {
|
|
41
|
-
return nil
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return json.Unmarshal(stob(item.String()), v)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
func (s *jsStorage) Del(k string) {
|
|
48
|
-
js.Window.Get(s.name).Call("removeItem", k)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
func (s *jsStorage) Clear() {
|
|
52
|
-
js.Window.Get(s.name).Call("clear")
|
|
53
|
-
}
|
testing.go
DELETED
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
package app
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"fmt"
|
|
5
|
-
|
|
6
|
-
"github.com/pyros2097/wapp/errors"
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
// import (
|
|
10
|
-
// "fmt"
|
|
11
|
-
|
|
12
|
-
// "github.com/pyros2097/wapp/errors"
|
|
13
|
-
// )
|
|
14
|
-
|
|
15
|
-
// TestUIDescriptor represents a descriptor that describes a UI element and its
|
|
16
|
-
// location from its parents.
|
|
17
|
-
type TestUIDescriptor struct {
|
|
18
|
-
// The location of the node. It is used by the TestMatch to find the
|
|
19
|
-
// element to test.
|
|
20
|
-
//
|
|
21
|
-
// If empty, the expected UI element is compared with the root of the tree.
|
|
22
|
-
//
|
|
23
|
-
// Otherwise, each integer represents the index of the element to traverse,
|
|
24
|
-
// from the root's children to the element to compare
|
|
25
|
-
Path []int
|
|
26
|
-
|
|
27
|
-
// The element to compare with the element targeted by Path. Compare
|
|
28
|
-
// behavior varies depending on the element kind.
|
|
29
|
-
//
|
|
30
|
-
// Simple text elements only have their text value compared.
|
|
31
|
-
//
|
|
32
|
-
// HTML elements have their attribute compared and check if their event
|
|
33
|
-
// handlers are set.
|
|
34
|
-
//
|
|
35
|
-
// Components have their exported field values compared.
|
|
36
|
-
Expected UI
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// TestPath is a helper function that returns a path to use in a
|
|
40
|
-
// TestUIDescriptor.
|
|
41
|
-
func TestPath(p ...int) []int {
|
|
42
|
-
return p
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// TestMatch looks for the element targeted by the descriptor in the given tree
|
|
46
|
-
// and reports whether it matches with the expected element.
|
|
47
|
-
//
|
|
48
|
-
// Eg:
|
|
49
|
-
// tree := app.Div().Body(
|
|
50
|
-
// app.H2().Body(
|
|
51
|
-
// app.Text("foo"),
|
|
52
|
-
// ),
|
|
53
|
-
// app.P().Body(
|
|
54
|
-
// app.Text("bar"),
|
|
55
|
-
// ),
|
|
56
|
-
// )
|
|
57
|
-
//
|
|
58
|
-
// // Testing root:
|
|
59
|
-
// err := app.TestMatch(tree, app.TestUIDescriptor{
|
|
60
|
-
// Path: TestPath(),
|
|
61
|
-
// Expected: app.Div(),
|
|
62
|
-
// })
|
|
63
|
-
// // OK => err == nil
|
|
64
|
-
//
|
|
65
|
-
// // Testing h2:
|
|
66
|
-
// err := app.TestMatch(tree, app.TestUIDescriptor{
|
|
67
|
-
// Path: TestPath(0),
|
|
68
|
-
// Expected: app.H3(),
|
|
69
|
-
// })
|
|
70
|
-
// // KO => err != nil because we ask h2 to match with h3
|
|
71
|
-
//
|
|
72
|
-
// // Testing text from p:
|
|
73
|
-
// err = app.TestMatch(tree, app.TestUIDescriptor{
|
|
74
|
-
// Path: TestPath(1, 0),
|
|
75
|
-
// Expected: app.Text("bar"),
|
|
76
|
-
// })
|
|
77
|
-
// // OK => err == nil
|
|
78
|
-
func TestMatch(tree UI, d TestUIDescriptor) error {
|
|
79
|
-
if !tree.Mounted() {
|
|
80
|
-
if err := mount(tree); err != nil {
|
|
81
|
-
return err
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if d.Expected != nil {
|
|
86
|
-
d.Expected.setSelf(d.Expected)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if len(d.Path) != 0 {
|
|
90
|
-
idx := d.Path[0]
|
|
91
|
-
|
|
92
|
-
if idx < 0 || idx >= len(tree.children()) {
|
|
93
|
-
// Check that the element does not exists.
|
|
94
|
-
if d.Expected == nil {
|
|
95
|
-
return nil
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return errors.New("ui element to match is out of range").
|
|
99
|
-
Tag("name", d.Expected.name()).
|
|
100
|
-
Tag("parent-name", tree.name()).
|
|
101
|
-
Tag("parent-children-count", len(tree.children())).
|
|
102
|
-
Tag("index", idx)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
c := tree.children()[idx]
|
|
106
|
-
p := c.parent()
|
|
107
|
-
|
|
108
|
-
if p != tree {
|
|
109
|
-
return errors.New("unexpected ui element parent").
|
|
110
|
-
Tag("name", d.Expected.name()).
|
|
111
|
-
Tag("parent-name", p.name()).
|
|
112
|
-
Tag("parent-addr", fmt.Sprintf("%p", p)).
|
|
113
|
-
Tag("expected-parent-name", tree.name()).
|
|
114
|
-
Tag("expected-parent-addr", fmt.Sprintf("%p", tree))
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
d.Path = d.Path[1:]
|
|
118
|
-
return TestMatch(c, d)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if d.Expected.name() != tree.name() {
|
|
122
|
-
return errors.New("the UI element is not matching the descriptor").
|
|
123
|
-
Tag("expected-name", d.Expected.name()).
|
|
124
|
-
Tag("current-name", tree.name())
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// switch d.Expected.Kind() {
|
|
128
|
-
// case SimpleText:
|
|
129
|
-
// return matchText(tree, d)
|
|
130
|
-
|
|
131
|
-
// case HTML:
|
|
132
|
-
// if err := matchHTMLElemAttrs(tree, d); err != nil {
|
|
133
|
-
// return err
|
|
134
|
-
// }
|
|
135
|
-
// return matchHTMLElemEventHandlers(tree, d)
|
|
136
|
-
|
|
137
|
-
// // case Component:
|
|
138
|
-
// // return matchComponent(tree, d)
|
|
139
|
-
|
|
140
|
-
// case RawHTML:
|
|
141
|
-
// return matchRaw(tree, d)
|
|
142
|
-
|
|
143
|
-
// default:
|
|
144
|
-
// return errors.New("the UI element is not matching the descriptor").
|
|
145
|
-
// Tag("reason", "unavailable matching for the kind").
|
|
146
|
-
// Tag("kind", d.Expected.Kind())
|
|
147
|
-
// }
|
|
148
|
-
return nil
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// func matchText(n UI, d TestUIDescriptor) error {
|
|
152
|
-
// a := n.(*text)
|
|
153
|
-
// b := d.Expected.(*text)
|
|
154
|
-
|
|
155
|
-
// if a.value != b.value {
|
|
156
|
-
// return errors.New("the text element is not matching the descriptor").
|
|
157
|
-
// Tag("name", a.name()).
|
|
158
|
-
// Tag("reason", "unexpected text value").
|
|
159
|
-
// Tag("expected-value", b.value).
|
|
160
|
-
// Tag("current-value", a.value)
|
|
161
|
-
// }
|
|
162
|
-
// return nil
|
|
163
|
-
// }
|
|
164
|
-
|
|
165
|
-
// func matchHTMLElemAttrs(n UI, d TestUIDescriptor) error {
|
|
166
|
-
// aAttrs := n.attributes()
|
|
167
|
-
// bAttrs := d.Expected.attributes()
|
|
168
|
-
|
|
169
|
-
// if len(aAttrs) != len(bAttrs) {
|
|
170
|
-
// return errors.New("the html element is not matching the descriptor").
|
|
171
|
-
// Tag("name", n.name()).
|
|
172
|
-
// Tag("reason", "unexpected attributes length").
|
|
173
|
-
// Tag("expected-attributes-length", len(bAttrs)).
|
|
174
|
-
// Tag("current-attributes-length", len(aAttrs))
|
|
175
|
-
// }
|
|
176
|
-
|
|
177
|
-
// for k, b := range bAttrs {
|
|
178
|
-
// a, exists := aAttrs[k]
|
|
179
|
-
// if !exists {
|
|
180
|
-
// return errors.New("the html element is not matching the descriptor").
|
|
181
|
-
// Tag("name", n.name()).
|
|
182
|
-
// Tag("reason", "an attribute is missing").
|
|
183
|
-
// Tag("attribute", k)
|
|
184
|
-
// }
|
|
185
|
-
|
|
186
|
-
// if a != b {
|
|
187
|
-
// return errors.New("the html element is not matching the descriptor").
|
|
188
|
-
// Tag("name", n.name()).
|
|
189
|
-
// Tag("reason", "unexpected attribute value").
|
|
190
|
-
// Tag("attribute", k).
|
|
191
|
-
// Tag("expected-value", b).
|
|
192
|
-
// Tag("current-value", a)
|
|
193
|
-
// }
|
|
194
|
-
// }
|
|
195
|
-
|
|
196
|
-
// for k := range bAttrs {
|
|
197
|
-
// _, exists := bAttrs[k]
|
|
198
|
-
// if !exists {
|
|
199
|
-
// return errors.New("the html element is not matching the descriptor").
|
|
200
|
-
// Tag("name", n.name()).
|
|
201
|
-
// Tag("reason", "an unexpected attribute is present").
|
|
202
|
-
// Tag("attribute", k)
|
|
203
|
-
// }
|
|
204
|
-
// }
|
|
205
|
-
|
|
206
|
-
// return nil
|
|
207
|
-
// }
|
|
208
|
-
|
|
209
|
-
// func matchHTMLElemEventHandlers(n UI, d TestUIDescriptor) error {
|
|
210
|
-
// aevents := n.eventHandlers()
|
|
211
|
-
// bevents := d.Expected.eventHandlers()
|
|
212
|
-
|
|
213
|
-
// if len(aevents) != len(bevents) {
|
|
214
|
-
// return errors.New("the html element is not matching the descriptor").
|
|
215
|
-
// Tag("name", n.name()).
|
|
216
|
-
// Tag("reason", "unexpected event handlers length").
|
|
217
|
-
// Tag("expected-event-handlers-length", len(bevents)).
|
|
218
|
-
// Tag("current-event-handlers-length", len(aevents))
|
|
219
|
-
// }
|
|
220
|
-
|
|
221
|
-
// for k := range bevents {
|
|
222
|
-
// _, exists := aevents[k]
|
|
223
|
-
// if !exists {
|
|
224
|
-
// return errors.New("the html element is not matching the descriptor").
|
|
225
|
-
// Tag("name", n.name()).
|
|
226
|
-
// Tag("reason", "an event handler is missing").
|
|
227
|
-
// Tag("event-handler", k)
|
|
228
|
-
// }
|
|
229
|
-
// }
|
|
230
|
-
|
|
231
|
-
// for k := range bevents {
|
|
232
|
-
// _, exists := aevents[k]
|
|
233
|
-
// if !exists {
|
|
234
|
-
// return errors.New("the html element is not matching the descriptor").
|
|
235
|
-
// Tag("name", n.name()).
|
|
236
|
-
// Tag("reason", "an unexpected event handler is present").
|
|
237
|
-
// Tag("event-handler", k)
|
|
238
|
-
// }
|
|
239
|
-
// }
|
|
240
|
-
|
|
241
|
-
// return nil
|
|
242
|
-
|
|
243
|
-
// }
|
|
244
|
-
|
|
245
|
-
// // func matchComponent(n UI, d TestUIDescriptor) error {
|
|
246
|
-
// // aval := reflect.ValueOf(n).Elem()
|
|
247
|
-
// // bval := reflect.ValueOf(d.Expected).Elem()
|
|
248
|
-
|
|
249
|
-
// // compotype := reflect.TypeOf(Compo{})
|
|
250
|
-
|
|
251
|
-
// // for i := 0; i < bval.NumField(); i++ {
|
|
252
|
-
// // a := aval.Field(i)
|
|
253
|
-
// // b := bval.Field(i)
|
|
254
|
-
|
|
255
|
-
// // if a.Type() == compotype {
|
|
256
|
-
// // continue
|
|
257
|
-
// // }
|
|
258
|
-
|
|
259
|
-
// // if !a.CanSet() {
|
|
260
|
-
// // continue
|
|
261
|
-
// // }
|
|
262
|
-
|
|
263
|
-
// // if !reflect.DeepEqual(a.Interface(), b.Interface()) {
|
|
264
|
-
// // return errors.New("the component is not matching with the descriptor").
|
|
265
|
-
// // Tag("name", n.name()).
|
|
266
|
-
// // Tag("reason", "unexpected field value").
|
|
267
|
-
// // Tag("field", bval.Type().Field(i).Name).
|
|
268
|
-
// // Tag("expected-value", b.Interface()).
|
|
269
|
-
// // Tag("current-value", a.Interface())
|
|
270
|
-
// // }
|
|
271
|
-
// // }
|
|
272
|
-
|
|
273
|
-
// // return nil
|
|
274
|
-
// // }
|
|
275
|
-
|
|
276
|
-
// func matchRaw(n UI, d TestUIDescriptor) error {
|
|
277
|
-
// a := n.(*raw)
|
|
278
|
-
// b := d.Expected.(*raw)
|
|
279
|
-
|
|
280
|
-
// if a.value != b.value {
|
|
281
|
-
// return errors.New("the raw html element is not matching with the descriptor").
|
|
282
|
-
// Tag("name", n.name()).
|
|
283
|
-
// Tag("reason", "unexpected value").
|
|
284
|
-
// Tag("expected-value", b.value).
|
|
285
|
-
// Tag("current-value", a.value)
|
|
286
|
-
// }
|
|
287
|
-
|
|
288
|
-
// return nil
|
|
289
|
-
// }
|
testing_test.go
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
package app
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"runtime"
|
|
5
|
-
"testing"
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
// import (
|
|
9
|
-
// "io/ioutil"
|
|
10
|
-
// "os"
|
|
11
|
-
// "runtime"
|
|
12
|
-
// "testing"
|
|
13
|
-
|
|
14
|
-
// "github.com/stretchr/testify/require"
|
|
15
|
-
// )
|
|
16
|
-
|
|
17
|
-
func testSkipNonWasm(t *testing.T) {
|
|
18
|
-
if goarch := runtime.GOARCH; goarch != "wasm" {
|
|
19
|
-
t.Skip("skipping test")
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// func testSkipWasm(t *testing.T) {
|
|
24
|
-
// if goarch := runtime.GOARCH; goarch == "wasm" {
|
|
25
|
-
// t.Skip("skipping test")
|
|
26
|
-
// // t.Skip(logs.New("skipping test").
|
|
27
|
-
// // Tag("reason", "unsupported architecture").
|
|
28
|
-
// // Tag("required-architecture", "!= than wasm").
|
|
29
|
-
// // Tag("current-architecture", goarch),
|
|
30
|
-
// // )
|
|
31
|
-
// }
|
|
32
|
-
// }
|
|
33
|
-
|
|
34
|
-
// func testCreateDir(t *testing.T, path string) func() {
|
|
35
|
-
// err := os.MkdirAll(path, 0755)
|
|
36
|
-
// require.NoError(t, err)
|
|
37
|
-
|
|
38
|
-
// return func() {
|
|
39
|
-
// os.RemoveAll(path)
|
|
40
|
-
// }
|
|
41
|
-
// }
|
|
42
|
-
|
|
43
|
-
// func testCreateFile(t *testing.T, path, content string) {
|
|
44
|
-
// err := ioutil.WriteFile(path, stob(content), 0666)
|
|
45
|
-
// require.NoError(t, err)
|
|
46
|
-
// }
|
utils.go
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
package app
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"io"
|
|
5
|
-
"unsafe"
|
|
6
|
-
|
|
7
|
-
"github.com/pyros2097/wapp/js"
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
var (
|
|
11
|
-
dispatch Dispatcher = Dispatch
|
|
12
|
-
uiChan = make(chan func(), 512)
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
type Context struct {
|
|
16
|
-
// The UI element tied to the context.
|
|
17
|
-
Src UI
|
|
18
|
-
|
|
19
|
-
// The JavaScript value of the element tied to the context. This is a
|
|
20
|
-
// shorthand for:
|
|
21
|
-
// ctx.Src.JSValue()
|
|
22
|
-
JSSrc js.Value
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Dispatcher is a function that executes the given function on the goroutine
|
|
26
|
-
// dedicated to UI.
|
|
27
|
-
type Dispatcher func(func())
|
|
28
|
-
|
|
29
|
-
// Dispatch executes the given function on the UI goroutine.
|
|
30
|
-
func Dispatch(f func()) {
|
|
31
|
-
uiChan <- f
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
func writeIndent(w io.Writer, indent int) {
|
|
35
|
-
for i := 0; i < indent*4; i++ {
|
|
36
|
-
w.Write(stob(" "))
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
func ln() []byte {
|
|
41
|
-
return stob("\n")
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
func btos(b []byte) string {
|
|
45
|
-
return *(*string)(unsafe.Pointer(&b))
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
func stob(s string) []byte {
|
|
49
|
-
return *(*[]byte)(unsafe.Pointer(&s))
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
var (
|
|
53
|
-
defaultColor = "\033[00m"
|
|
54
|
-
errorColor = "\033[91m"
|
|
55
|
-
infoColor = "\033[94m"
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
// Log logs according to a format specifier using the default logger.
|
|
59
|
-
func Log(format string, v ...interface{}) {
|
|
60
|
-
errorLevel := false
|
|
61
|
-
if errorLevel {
|
|
62
|
-
println(errorColor+"ERROR ‣ "+defaultColor+format+"\n", v)
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
println(infoColor+"INFO ‣ "+defaultColor+format+"\n", v)
|
|
66
|
-
}
|