~repos /gromer

#golang#htmx#ssr

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 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 OnClick(cb func()) OnClickAttribute {
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 OnChangeAttribute{cb: cb}
100
+ return Attribute{"x-data", v}
111
101
  }
112
102
 
113
- type OnInputAttribute struct {
103
+ func XText(v string) Attribute {
114
- cb js.EventHandlerFunc
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 := []UI{}
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 HelmetTitle:
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.setBody(elems)
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
- "strconv"
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 []UI
11
+ body []*Element
13
- events map[string]js.EventHandler
14
- jsvalue js.Value
15
- parentElem UI
16
12
  selfClosing bool
17
- tag string
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 (e *Element) updateEventHandler(handlers map[string]js.EventHandler) {
72
+ func writeIndent(w io.Writer, indent int) {
270
- for k, current := range e.events {
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
- e.setJsEventHandler(k, new)
74
+ w.Write(stob(" "))
288
- }
289
75
  }
290
76
  }
291
77
 
292
- func (e *Element) setEventHandler(k string, h js.EventHandlerFunc) {
293
- if e.events == nil {
78
+ func ln() []byte {
294
- e.events = make(map[string]js.EventHandler)
295
- }
296
-
297
- e.events[k] = js.NewEventHandler(k, h)
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
- if !e.self().Mounted() {
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
- e.JSValue().Call("addEventListener", k, jshandler)
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 (e *Element) setBody(body []UI) {
86
+ func stob(s string) []byte {
327
- if e.selfClosing {
328
- panic("setting html element body failed: self closing element can't have children" + e.name())
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.(WritableNode).HtmlWithIndent(w, indent+1)
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(c *RenderContext) UI {
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
- . "github.com/pyros2097/wapp/example/components"
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(c *RenderContext, err error) UI {
18
+ // SetErrorHandler(func(w *RenderContext, err error) UI {
11
- return Col(Css("text-4xl text-gray-700"),
19
+ // return Col(Css("text-4xl text-gray-700"),
12
- Header(c),
20
+ // Header(c),
13
- Row(
21
+ // Row(
14
- Text("Oops something went wrong"),
22
+ // Text("Oops something went wrong"),
15
- ),
23
+ // ),
16
- Row(Css("mt-6"),
24
+ // Row(Css("mt-6"),
17
- Text("Please check back again"),
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
- Route("/panic", Panic)
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.15
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/awslabs/goformation/v4 v4.15.6
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/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0
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
- golang.org/x/sys v0.0.0-20201112073958-5cba982894dd // indirect
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 ...UI) *Element {
5
+ func Html(elems ...*Element) *Element {
6
6
  return &Element{tag: "html", body: elems}
7
7
  }
8
8
 
9
- func Head(elems ...UI) *Element {
9
+ func Head(elems ...*Element) *Element {
10
- basic := []UI{
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 ...UI) *Element {
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
- return &Element{tag: "title", body: []UI{Text(v)}}
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
- e := &Element{
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
- e := &Element{
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(str string) *Element {
57
+ func Script(uis ...interface{}) *Element {
47
- return &Element{
48
- tag: "script",
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{}) UI {
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{}) UI {
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 UI) UI {
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 UI, b UI) UI {
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) UI) []UI {
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([]UI, 0, src.Len())
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) UI) []UI {
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([]UI, 0, src.Len())
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: &ldquo;/blog/gopibot-to-the-rescue&rdquo;
12
- date: &ldquo;2017-10-04&rdquo;</p>
13
-
14
- <h2>title: &ldquo;Gopibot To The Rescue&rdquo;</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 interface{}
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 interface{}) {
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 interface{}) {
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 interface{}, ps *Params, tsr bool) {
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 RenderFunc
733
+ NotFound RouteFn
727
734
 
728
735
  // Configurable handler which is called when an error occurs.
729
- Error func(*RenderContext, error) UI
736
+ Error RouteFn
730
737
  }
731
738
 
732
739
  var globalRouter = &Router{
733
740
  RedirectTrailingSlash: true,
734
- NotFound: func(c *RenderContext) UI {
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(c *RenderContext, err error) UI {
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: "+err.Error()),
758
+ Text("Error: "),
751
759
  ),
752
760
  Row(
753
- Text("SetErrorHandler(func(c *RenderContext, err error) UI {}) to override it"),
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 func(c *RenderContext) UI) {
767
+ func SetNotFoundHandler(b RouteFn) {
760
768
  globalRouter.NotFound = b
761
769
  }
762
770
 
763
- func SetErrorHandler(b func(c *RenderContext, err error) UI) {
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 interface{}) {
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 interface{}) {
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 interface{}) {
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 interface{}) {
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 interface{}) {
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
- }