js_wasm.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. package app
  2. import (
  3. "net/url"
  4. "reflect"
  5. "syscall/js"
  6. "github.com/maxence-charriere/go-app/v9/pkg/errors"
  7. )
  8. var (
  9. window = &browserWindow{value: value{Value: js.Global()}}
  10. )
  11. type value struct {
  12. js.Value
  13. }
  14. func (v value) Call(m string, args ...any) Value {
  15. args = cleanArgs(args...)
  16. return val(v.Value.Call(m, args...))
  17. }
  18. func (v value) Delete(p string) {
  19. v.Value.Delete(p)
  20. }
  21. func (v value) Equal(w Value) bool {
  22. return v.Value.Equal(jsval(w))
  23. }
  24. func (v value) Get(p string) Value {
  25. return val(v.Value.Get(p))
  26. }
  27. func (v value) Set(p string, x any) {
  28. if wrapper, ok := x.(Wrapper); ok {
  29. x = jsval(wrapper.JSValue())
  30. }
  31. v.Value.Set(p, x)
  32. }
  33. func (v value) Index(i int) Value {
  34. return val(v.Value.Index(i))
  35. }
  36. func (v value) InstanceOf(t Value) bool {
  37. return v.Value.InstanceOf(jsval(t))
  38. }
  39. func (v value) Invoke(args ...any) Value {
  40. return val(v.Value.Invoke(args...))
  41. }
  42. func (v value) JSValue() Value {
  43. return v
  44. }
  45. func (v value) New(args ...any) Value {
  46. args = cleanArgs(args...)
  47. return val(v.Value.New(args...))
  48. }
  49. func (v value) Type() Type {
  50. return Type(v.Value.Type())
  51. }
  52. func (v value) Then(f func(Value)) {
  53. release := func() {}
  54. then := FuncOf(func(this Value, args []Value) any {
  55. var arg Value
  56. if len(args) > 0 {
  57. arg = args[0]
  58. }
  59. f(arg)
  60. release()
  61. return nil
  62. })
  63. release = then.Release
  64. v.Call("then", then)
  65. }
  66. func (v value) getAttr(k string) string {
  67. return v.Call("getAttribute", k).String()
  68. }
  69. func (v value) setAttr(k, val string) {
  70. v.Call("setAttribute", k, val)
  71. }
  72. func (v value) delAttr(k string) {
  73. v.Call("removeAttribute", k)
  74. }
  75. func (v value) firstChild() Value {
  76. return v.Get("firstChild")
  77. }
  78. func (v value) appendChild(c Wrapper) {
  79. v.Call("appendChild", c)
  80. }
  81. func (v value) replaceChild(new, old Wrapper) {
  82. v.Call("replaceChild", new, old)
  83. }
  84. func (v value) removeChild(c Wrapper) {
  85. v.Call("removeChild", c)
  86. }
  87. func (v value) firstElementChild() Value {
  88. return v.Get("firstElementChild")
  89. }
  90. func (v value) addEventListener(event string, fn Func) {
  91. v.Call("addEventListener", event, fn)
  92. }
  93. func (v value) removeEventListener(event string, fn Func) {
  94. v.Call("removeEventListener", event, fn)
  95. }
  96. func (v value) setNodeValue(val string) {
  97. v.Set("nodeValue", val)
  98. }
  99. func (v value) setInnerHTML(val string) {
  100. v.Set("innerHTML", val)
  101. }
  102. func (v value) setInnerText(val string) {
  103. v.Set("innerText", val)
  104. }
  105. func null() Value {
  106. return val(js.Null())
  107. }
  108. func undefined() Value {
  109. return val(js.Undefined())
  110. }
  111. func valueOf(x any) Value {
  112. switch t := x.(type) {
  113. case value:
  114. x = t.Value
  115. case function:
  116. x = t.fn
  117. case *browserWindow:
  118. x = t.Value
  119. case Event:
  120. return valueOf(t.Value)
  121. }
  122. return val(js.ValueOf(x))
  123. }
  124. type function struct {
  125. value
  126. fn js.Func
  127. }
  128. func (f function) Release() {
  129. f.fn.Release()
  130. }
  131. func funcOf(fn func(this Value, args []Value) any) Func {
  132. f := js.FuncOf(func(this js.Value, args []js.Value) any {
  133. wargs := make([]Value, len(args))
  134. for i, a := range args {
  135. wargs[i] = val(a)
  136. }
  137. return fn(val(this), wargs)
  138. })
  139. return function{
  140. value: value{Value: f.Value},
  141. fn: f,
  142. }
  143. }
  144. type browserWindow struct {
  145. value
  146. body UI
  147. cursorX int
  148. cursorY int
  149. }
  150. func (w *browserWindow) URL() *url.URL {
  151. rawurl := w.
  152. Get("location").
  153. Get("href").
  154. String()
  155. u, _ := url.Parse(rawurl)
  156. return u
  157. }
  158. func (w *browserWindow) Size() (width int, height int) {
  159. getSize := func(axis string) int {
  160. size := w.Get("inner" + axis)
  161. if !size.Truthy() {
  162. size = w.
  163. Get("document").
  164. Get("documentElement").
  165. Get("client" + axis)
  166. }
  167. if !size.Truthy() {
  168. size = w.
  169. Get("document").
  170. Get("body").
  171. Get("client" + axis)
  172. }
  173. if size.Type() != TypeNumber {
  174. return 0
  175. }
  176. return size.Int()
  177. }
  178. return getSize("Width"), getSize("Height")
  179. }
  180. func (w *browserWindow) CursorPosition() (x, y int) {
  181. return w.cursorX, w.cursorY
  182. }
  183. func (w *browserWindow) setCursorPosition(x, y int) {
  184. w.cursorX = x
  185. w.cursorY = y
  186. }
  187. func (w *browserWindow) GetElementByID(id string) Value {
  188. return w.Get("document").Call("getElementById", id)
  189. }
  190. func (w *browserWindow) ScrollToID(id string) {
  191. if elem := w.GetElementByID(id); elem.Truthy() {
  192. elem.Call("scrollIntoView")
  193. }
  194. }
  195. func (w *browserWindow) AddEventListener(event string, h EventHandler) func() {
  196. callback := makeJSEventHandler(w.body, func(ctx Context, e Event) {
  197. h(ctx, e)
  198. // Trigger children components updates:
  199. if len(w.body.getChildren()) == 0 {
  200. return
  201. }
  202. compo, ok := w.body.getChildren()[0].(Composer)
  203. if !ok {
  204. return
  205. }
  206. ctx.Dispatcher().Dispatch(Dispatch{
  207. Mode: Update,
  208. Source: compo,
  209. })
  210. })
  211. w.addEventListener(event, callback)
  212. return func() {
  213. w.removeEventListener(event, callback)
  214. callback.Release()
  215. }
  216. }
  217. func (w *browserWindow) setBody(body UI) {
  218. w.body = body
  219. }
  220. func (w *browserWindow) createElement(tag, xmlns string) (Value, error) {
  221. var element Value
  222. if xmlns == "" {
  223. element = w.Get("document").Call("createElement", tag)
  224. } else {
  225. element = w.Get("document").Call("createElementNS", xmlns, tag)
  226. }
  227. if !element.Truthy() {
  228. return nil, errors.New("creating javascript element failed").
  229. Tag("tag", tag).
  230. Tag("xmlns", xmlns)
  231. }
  232. return element, nil
  233. }
  234. func (w *browserWindow) createTextNode(v string) Value {
  235. return w.Get("document").Call("createTextNode", v)
  236. }
  237. func (w *browserWindow) addHistory(u *url.URL) {
  238. w.Get("history").Call("pushState", nil, "", u.String())
  239. lastURLVisited = u
  240. }
  241. func (w *browserWindow) replaceHistory(u *url.URL) {
  242. w.Get("history").Call("replaceState", nil, "", u.String())
  243. lastURLVisited = u
  244. }
  245. func val(v js.Value) Value {
  246. return value{Value: v}
  247. }
  248. func jsval(v Value) js.Value {
  249. switch v := v.(type) {
  250. case value:
  251. return v.Value
  252. case function:
  253. return v.Value
  254. case *browserWindow:
  255. return v.Value
  256. case Event:
  257. return jsval(v.Value)
  258. default:
  259. Log("%s", errors.New("syscall/js value conversion failed").
  260. Tag("type", reflect.TypeOf(v)),
  261. )
  262. return js.Undefined()
  263. }
  264. }
  265. // JSValue returns the underlying syscall/js value of the given Javascript
  266. // value.
  267. func JSValue(v Value) js.Value {
  268. return jsval(v)
  269. }
  270. func copyBytesToGo(dst []byte, src Value) int {
  271. return js.CopyBytesToGo(dst, jsval(src))
  272. }
  273. func copyBytesToJS(dst Value, src []byte) int {
  274. return js.CopyBytesToJS(jsval(dst), src)
  275. }
  276. func cleanArgs(args ...any) []any {
  277. for i, a := range args {
  278. args[i] = cleanArg(a)
  279. }
  280. return args
  281. }
  282. func cleanArg(v any) any {
  283. switch v := v.(type) {
  284. case map[string]any:
  285. m := make(map[string]any, len(v))
  286. for key, val := range v {
  287. m[key] = cleanArg(val)
  288. }
  289. return m
  290. case []any:
  291. s := make([]any, len(v))
  292. for i, val := range v {
  293. s[i] = cleanArgs(val)
  294. }
  295. case Wrapper:
  296. return jsval(v.JSValue())
  297. }
  298. return v
  299. }