forked from mbk-lab/rui_orig
2
0
Fork 0

Updated wasm support

This commit is contained in:
anoshenko 2022-11-01 20:13:09 +03:00
parent b4032b31e0
commit b26525e892
10 changed files with 271 additions and 270 deletions

View File

@ -200,7 +200,7 @@ func (app *application) startSession(params DataObject, events chan DataObject,
session := newSession(app, app.nextSessionID(), "", params) session := newSession(app, app.nextSessionID(), "", params)
session.setBrige(events, brige) session.setBrige(events, brige)
if !session.setContent(app.createContentFunc(session), session) { if !session.setContent(app.createContentFunc(session)) {
return nil, "" return nil, ""
} }

View File

@ -4,6 +4,7 @@ package rui
import ( import (
_ "embed" _ "embed"
"strings"
"syscall/js" "syscall/js"
) )
@ -15,35 +16,88 @@ type wasmApp struct {
createContentFunc func(Session) SessionContent createContentFunc func(Session) SessionContent
session Session session Session
brige webBrige brige webBrige
close chan DataObject
} }
func (app *wasmApp) Finish() { func (app *wasmApp) Finish() {
app.session.close() app.session.close()
} }
func (app *wasmApp) startSession(this js.Value, args []js.Value) interface{} { func wasmLog(text string) {
if app.createContentFunc == nil || len(args) == 1 { js.Global().Call("log", text)
return nil }
}
params := ParseDataText(args[0].String()) func (app *wasmApp) handleMessage(this js.Value, args []js.Value) any {
session := newSession(app, 0, "", params) if len(args) > 0 {
session.setBrige(make(chan DataObject), app.brige) if obj := ParseDataText(args[0].String()); obj != nil {
if !session.setContent(app.createContentFunc(session), session) { switch command := obj.Tag(); command {
return nil /*
} case "startSession":
answer := ""
if session, answer = app.startSession(obj, events, brige); session != nil {
if !brige.writeMessage(answer) {
return
}
session.onStart()
go sessionEventHandler(session, events, brige)
}
app.session = session case "reconnect":
if sessionText, ok := obj.PropertyValue("session"); ok {
if sessionID, err := strconv.Atoi(sessionText); err == nil {
if session = app.sessions[sessionID]; session != nil {
session.setBrige(events, brige)
answer := allocStringBuilder()
defer freeStringBuilder(answer)
answer := allocStringBuilder() session.writeInitScript(answer)
defer freeStringBuilder(answer) if !brige.writeMessage(answer.String()) {
return
}
session.onReconnect()
go sessionEventHandler(session, events, brige)
return
}
DebugLogF("Session #%d not exists", sessionID)
} else {
ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error())
}
} else {
ErrorLog(`"session" key not found`)
}
session.writeInitScript(answer) answer := ""
answerText := answer.String() if session, answer = app.startSession(obj, events, brige); session != nil {
if !brige.writeMessage(answer) {
return
}
session.onStart()
go sessionEventHandler(session, events, brige)
}
if ProtocolInDebugLog { case "disconnect":
DebugLog("Start session:") session.onDisconnect()
DebugLog(answerText) return
case "session-close":
session.onFinish()
session.App().removeSession(session.ID())
brige.close()
*/
case "answer":
app.session.handleAnswer(obj)
case "imageLoaded":
app.session.imageManager().imageLoaded(obj, app.session)
case "imageError":
app.session.imageManager().imageLoadError(obj, app.session)
default:
app.session.handleEvent(command, obj)
}
}
} }
return nil return nil
} }
@ -51,117 +105,75 @@ func (app *wasmApp) startSession(this js.Value, args []js.Value) interface{} {
func (app *wasmApp) removeSession(id int) { func (app *wasmApp) removeSession(id int) {
} }
func (app *wasmApp) createSession() Session {
session := newSession(app, 0, "", ParseDataText(js.Global().Call("sessionInfo", "").String()))
session.setBrige(app.close, app.brige)
session.setContent(app.createContentFunc(session))
return session
}
func (app *wasmApp) init() {
document := js.Global().Get("document")
body := document.Call("querySelector", "body")
script := document.Call("createElement", "script")
script.Set("type", "text/javascript")
script.Set("textContent", defaultScripts+wasmScripts)
body.Call("appendChild", script)
js.Global().Set("sendMessage", js.FuncOf(app.handleMessage))
app.close = make(chan DataObject)
app.session = app.createSession()
style := document.Call("createElement", "style")
css := appStyles + app.session.getCurrentTheme().cssText(app.session)
css = strings.ReplaceAll(css, `\n`, "\n")
css = strings.ReplaceAll(css, `\t`, "\t")
style.Set("textContent", css)
document.Call("querySelector", "head").Call("appendChild", style)
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
div := document.Call("createElement", "div")
div.Set("className", "ruiRoot")
div.Set("id", "ruiRootView")
viewHTML(app.session.RootView(), buffer)
div.Set("innerHTML", buffer.String())
body.Call("appendChild", div)
div = document.Call("createElement", "div")
div.Set("className", "ruiPopupLayer")
div.Set("id", "ruiPopupLayer")
div.Set("onclick", "clickOutsidePopup(event)")
div.Set("style", "visibility: hidden;")
body.Call("appendChild", div)
div = document.Call("createElement", "a")
div.Set("id", "ruiDownloader")
div.Set("download", "")
div.Set("style", "display: none;")
body.Call("appendChild", div)
}
// StartApp - create the new wasmApp and start it // StartApp - create the new wasmApp and start it
func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) { func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) {
app := new(wasmApp) SetDebugLog(wasmLog)
app.params = params SetErrorLog(wasmLog)
app.createContentFunc = createContentFunc
if createContentFunc == nil { if createContentFunc == nil {
return return
} }
app := new(wasmApp)
app.params = params
app.createContentFunc = createContentFunc
app.brige = createWasmBrige() app.brige = createWasmBrige()
js.Global().Set("startSession", js.FuncOf(app.startSession))
/* app.init()
script := defaultScripts + wasmScripts <-app.close
script = strings.ReplaceAll(script, "\\", `\\`)
script = strings.ReplaceAll(script, "\n", `\n`)
script = strings.ReplaceAll(script, "\t", `\t`)
script = strings.ReplaceAll(script, "\"", `\"`)
script = strings.ReplaceAll(script, "'", `\'`)
js.Global().Call("execScript", `document.getElementById('ruiscript').text += "`+script+`"`)
*/
document := js.Global().Get("document")
body := document.Call("querySelector", "body")
body.Set("innerHTML", `<div class="ruiRoot" id="ruiRootView"></div>
<div class="ruiPopupLayer" id="ruiPopupLayer" style="visibility: hidden;" onclick="clickOutsidePopup(event)"></div>
<a id="ruiDownloader" download style="display: none;"></a>`)
//js.Global().Call("execScript", "initSession()")
js.Global().Call("initSession", "")
//window.Call("execScript", "initSession()")
for true {
if message, ok := app.brige.readMessage(); ok && app.session != nil {
if ProtocolInDebugLog {
DebugLog(message)
}
if obj := ParseDataText(message); obj != nil {
switch command := obj.Tag(); command {
/*
case "startSession":
answer := ""
if session, answer = app.startSession(obj, events, brige); session != nil {
if !brige.writeMessage(answer) {
return
}
session.onStart()
go sessionEventHandler(session, events, brige)
}
case "reconnect":
if sessionText, ok := obj.PropertyValue("session"); ok {
if sessionID, err := strconv.Atoi(sessionText); err == nil {
if session = app.sessions[sessionID]; session != nil {
session.setBrige(events, brige)
answer := allocStringBuilder()
defer freeStringBuilder(answer)
session.writeInitScript(answer)
if !brige.writeMessage(answer.String()) {
return
}
session.onReconnect()
go sessionEventHandler(session, events, brige)
return
}
DebugLogF("Session #%d not exists", sessionID)
} else {
ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error())
}
} else {
ErrorLog(`"session" key not found`)
}
answer := ""
if session, answer = app.startSession(obj, events, brige); session != nil {
if !brige.writeMessage(answer) {
return
}
session.onStart()
go sessionEventHandler(session, events, brige)
}
case "disconnect":
session.onDisconnect()
return
case "session-close":
session.onFinish()
session.App().removeSession(session.ID())
brige.close()
*/
case "answer":
app.session.handleAnswer(obj)
case "imageLoaded":
app.session.imageManager().imageLoaded(obj, app.session)
case "imageError":
app.session.imageManager().imageLoadError(obj, app.session)
default:
app.session.handleEvent(command, obj)
}
}
}
}
} }
func FinishApp() { func FinishApp() {
@ -171,31 +183,3 @@ func FinishApp() {
func OpenBrowser(url string) bool { func OpenBrowser(url string) bool {
return false return false
} }
/*
func OpenBrowser(url string) bool {
var err error
switch runtime.GOOS {
case "linux":
for _, provider := range []string{"xdg-open", "x-www-browser", "www-browser"} {
if _, err = exec.LookPath(provider); err == nil {
if exec.Command(provider, url).Start(); err == nil {
return true
}
}
}
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
default:
err = fmt.Errorf("unsupported platform")
}
return err != nil
}
*/

View File

@ -14,6 +14,47 @@ window.onblur = function(event) {
sendMessage( "session-pause{session=" + sessionID +"}" ); sendMessage( "session-pause{session=" + sessionID +"}" );
} }
function sessionInfo() {
const touch_screen = (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) ? "1" : "0";
var message = "startSession{touch=" + touch_screen
const style = window.getComputedStyle(document.body);
if (style) {
var direction = style.getPropertyValue('direction');
if (direction) {
message += ",direction=" + direction
}
}
const lang = window.navigator.language;
if (lang) {
message += ",language=\"" + lang + "\"";
}
const langs = window.navigator.languages;
if (langs) {
message += ",languages=\"" + langs + "\"";
}
const userAgent = window.navigator.userAgent
if (userAgent) {
message += ",user-agent=\"" + userAgent + "\"";
}
const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)");
if (darkThemeMq.matches) {
message += ",dark=1";
}
const pixelRatio = window.devicePixelRatio;
if (pixelRatio) {
message += ",pixel-ratio=" + pixelRatio;
}
return message + "}";
}
function getIntAttribute(element, tag) { function getIntAttribute(element, tag) {
let value = element.getAttribute(tag); let value = element.getAttribute(tag);
if (value) { if (value) {
@ -1276,6 +1317,10 @@ function startDowndload(url, filename) {
} }
} }
function setTitle(title) {
document.title = title;
}
function setTitleColor(color) { function setTitleColor(color) {
var metas = document.getElementsByTagName('meta'); var metas = document.getElementsByTagName('meta');
if (metas) { if (metas) {
@ -1292,6 +1337,10 @@ function setTitleColor(color) {
document.getElementsByTagName('head')[0].appendChild(meta); document.getElementsByTagName('head')[0].appendChild(meta);
} }
function openURL(url) {
window.open(url, "_blank");
}
function detailsEvent(element) { function detailsEvent(element) {
sendMessage("details-open{session=" + sessionID + ",id=" + element.id + ",open=" + (element.open ? "1}" : "0}")); sendMessage("details-open{session=" + sessionID + ",id=" + element.id + ",open=" + (element.open ? "1}" : "0}"));
} }
@ -1742,4 +1791,8 @@ function getPropertyValue(answerID, elementId, name) {
} }
sendMessage('answer{answerID=' + answerID + ', value=""}') sendMessage('answer{answerID=' + answerID + ', value=""}')
}
function appendStyles(styles) {
document.querySelector('style').textContent += styles
} }

View File

@ -26,44 +26,7 @@ window.onload = function() {
}; };
function socketOpen() { function socketOpen() {
sendMessage( sessionInfo() );
const touch_screen = (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) ? "1" : "0";
var message = "startSession{touch=" + touch_screen
const style = window.getComputedStyle(document.body);
if (style) {
var direction = style.getPropertyValue('direction');
if (direction) {
message += ",direction=" + direction
}
}
const lang = window.navigator.language;
if (lang) {
message += ",language=\"" + lang + "\"";
}
const langs = window.navigator.languages;
if (langs) {
message += ",languages=\"" + langs + "\"";
}
const userAgent = window.navigator.userAgent
if (userAgent) {
message += ",user-agent=\"" + userAgent + "\"";
}
const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)");
if (darkThemeMq.matches) {
message += ",dark=1";
}
const pixelRatio = window.devicePixelRatio;
if (pixelRatio) {
message += ",pixel-ratio=" + pixelRatio;
}
sendMessage( message + "}" );
} }
function socketReopen() { function socketReopen() {

View File

@ -1,47 +1,8 @@
function log(s) {
function initSession() { console.log(s);
const touch_screen = (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) ? "1" : "0";
var message = "sessionInfo{touch=" + touch_screen
const style = window.getComputedStyle(document.body);
if (style) {
var direction = style.getPropertyValue('direction');
if (direction) {
message += ",direction=" + direction
}
}
const lang = window.navigator.language;
if (lang) {
message += ",language=\"" + lang + "\"";
}
const langs = window.navigator.languages;
if (langs) {
message += ",languages=\"" + langs + "\"";
}
const userAgent = window.navigator.userAgent
if (userAgent) {
message += ",user-agent=\"" + userAgent + "\"";
}
const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)");
if (darkThemeMq.matches) {
message += ",dark=1";
}
const pixelRatio = window.devicePixelRatio;
if (pixelRatio) {
message += ",pixel-ratio=" + pixelRatio;
}
startSession( message + "}" );
} }
window.onfocus = function(event) { window.onfocus = function(event) {
windowFocus = true windowFocus = true
sendMessage( "session-resume{session=" + sessionID +"}" ); sendMessage( "session-resume{session=" + sessionID +"}" );

View File

@ -2,7 +2,6 @@ package rui
import ( import (
"bytes" "bytes"
"fmt"
"math/rand" "math/rand"
"net/http" "net/http"
"os" "os"
@ -24,7 +23,7 @@ func (session *sessionData) startDownload(file downloadFile) {
currentDownloadId++ currentDownloadId++
id := strconv.Itoa(currentDownloadId) id := strconv.Itoa(currentDownloadId)
downloadFiles[id] = file downloadFiles[id] = file
session.runScript(fmt.Sprintf(`startDowndload("%s", "%s")`, id, file.filename)) session.runFunc("startDowndload", id, file.filename)
} }
func serveDownloadFile(id string, w http.ResponseWriter, r *http.Request) bool { func serveDownloadFile(id string, w http.ResponseWriter, r *http.Request) bool {

View File

@ -67,7 +67,7 @@ type Session interface {
// Content returns the SessionContent of session // Content returns the SessionContent of session
Content() SessionContent Content() SessionContent
setContent(content SessionContent, self Session) bool setContent(content SessionContent) bool
// SetTitle sets the text of the browser title/tab // SetTitle sets the text of the browser title/tab
SetTitle(title string) SetTitle(title string)
@ -91,6 +91,7 @@ type Session interface {
// OpenURL opens the url in the new browser tab // OpenURL opens the url in the new browser tab
OpenURL(url string) OpenURL(url string)
getCurrentTheme() Theme
registerAnimation(props []AnimatedProperty) string registerAnimation(props []AnimatedProperty) string
resolveConstants(value string) (string, bool) resolveConstants(value string) (string, bool)
@ -245,10 +246,10 @@ func (session *sessionData) Content() SessionContent {
return session.content return session.content
} }
func (session *sessionData) setContent(content SessionContent, self Session) bool { func (session *sessionData) setContent(content SessionContent) bool {
if content != nil { if content != nil {
session.content = content session.content = content
session.rootView = content.CreateRootView(self) session.rootView = content.CreateRootView(session)
if session.rootView != nil { if session.rootView != nil {
session.rootView.setParentID("ruiRootView") session.rootView.setParentID("ruiRootView")
return true return true
@ -554,11 +555,11 @@ func (session *sessionData) handleEvent(command string, data DataObject) {
func (session *sessionData) SetTitle(title string) { func (session *sessionData) SetTitle(title string) {
title, _ = session.GetString(title) title, _ = session.GetString(title)
session.runScript(`document.title = "` + title + `";`) session.runFunc("setTitle", title)
} }
func (session *sessionData) SetTitleColor(color Color) { func (session *sessionData) SetTitleColor(color Color) {
session.runScript(`setTitleColor("` + color.cssString() + `");`) session.runFunc("setTitleColor", color.cssString())
} }
func (session *sessionData) RemoteAddr() string { func (session *sessionData) RemoteAddr() string {
@ -570,5 +571,5 @@ func (session *sessionData) OpenURL(urlStr string) {
ErrorLog(err.Error()) ErrorLog(err.Error())
return return
} }
session.runScript(`window.open("` + urlStr + `", "_blank");`) session.runFunc("openURL", urlStr)
} }

View File

@ -325,15 +325,16 @@ func (session *sessionData) SetLanguage(lang string) {
if lang != session.language { if lang != session.language {
session.language = lang session.language = lang
if session.rootView != nil { if session.rootView != nil && session.brige != nil {
buffer := allocStringBuilder() buffer := allocStringBuilder()
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
buffer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`) //buffer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`)
viewHTML(session.rootView, buffer) viewHTML(session.rootView, buffer)
buffer.WriteString("';\nscanElementsSize();") //buffer.WriteString("';\nscanElementsSize();")
session.runScript(buffer.String()) //session.runScript(buffer.String())
session.brige.updateInnerHTML("ruiRootView", buffer.String())
} }
} }
} }

View File

@ -3,11 +3,10 @@
package rui package rui
import ( import (
"fmt"
"strconv" "strconv"
"sync" "sync"
"syscall/js" "syscall/js"
"github.com/gorilla/websocket"
) )
type wasmBrige struct { type wasmBrige struct {
@ -18,11 +17,6 @@ type wasmBrige struct {
closed bool closed bool
} }
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 8096,
}
func createWasmBrige() webBrige { func createWasmBrige() webBrige {
brige := new(wasmBrige) brige := new(wasmBrige)
brige.queue = make(chan string, 1000) brige.queue = make(chan string, 1000)
@ -30,23 +24,58 @@ func createWasmBrige() webBrige {
brige.answer = make(map[int]chan DataObject) brige.answer = make(map[int]chan DataObject)
brige.closed = false brige.closed = false
js.Global().Set("sendMessage", js.FuncOf(brige.sendMessage))
return brige return brige
} }
func (brige *wasmBrige) sendMessage(this js.Value, args []js.Value) interface{} { func (brige *wasmBrige) startUpdateScript(htmlID string) bool {
if len(args) > 0 { return false
brige.queue <- args[0].String() }
func (brige *wasmBrige) finishUpdateScript(htmlID string) {
}
func (brige *wasmBrige) runFunc(funcName string, args ...any) bool {
if ProtocolInDebugLog {
text := funcName + "("
for i, arg := range args {
if i > 0 {
text += fmt.Sprintf(", `%v`", arg)
} else {
text += fmt.Sprintf("`%v`", arg)
}
}
DebugLog(text + ")")
} }
return nil
js.Global().Call(funcName, args...)
return true
}
func (brige *wasmBrige) updateInnerHTML(htmlID, html string) {
brige.runFunc("updateInnerHTML", htmlID, html)
}
func (brige *wasmBrige) appendToInnerHTML(htmlID, html string) {
brige.runFunc("appendToInnerHTML", htmlID, html)
}
func (brige *wasmBrige) updateCSSProperty(htmlID, property, value string) {
brige.runFunc("updateCSSProperty", htmlID, property, value)
}
func (brige *wasmBrige) updateProperty(htmlID, property string, value any) {
brige.runFunc("updateProperty", htmlID, property, value)
}
func (brige *wasmBrige) removeProperty(htmlID, property string) {
brige.runFunc("removeProperty", htmlID, property)
} }
func (brige *wasmBrige) close() { func (brige *wasmBrige) close() {
} }
func (brige *wasmBrige) readMessage() (string, bool) { func (brige *wasmBrige) readMessage() (string, bool) {
return <-brige.queue, true return "", false
} }
func (brige *wasmBrige) writeMessage(script string) bool { func (brige *wasmBrige) writeMessage(script string) bool {
@ -61,25 +90,36 @@ func (brige *wasmBrige) writeMessage(script string) bool {
return true return true
} }
func (brige *wasmBrige) runGetterScript(script string) DataObject { /*
brige.answerMutex.Lock() func (brige *wasmBrige) runGetterScript(script string) DataObject {
answerID := brige.answerID brige.answerMutex.Lock()
brige.answerID++ answerID := brige.answerID
brige.answerMutex.Unlock() brige.answerID++
brige.answerMutex.Unlock()
answer := make(chan DataObject) answer := make(chan DataObject)
brige.answer[answerID] = answer brige.answer[answerID] = answer
errorText := "" errorText := ""
js.Global().Set("answerID", strconv.Itoa(answerID)) js.Global().Set("answerID", strconv.Itoa(answerID))
window := js.Global().Get("window") window := js.Global().Get("window")
window.Call("execScript", script) window.Call("execScript", script)
result := NewDataObject("error") result := NewDataObject("error")
result.SetPropertyValue("text", errorText) result.SetPropertyValue("text", errorText)
delete(brige.answer, answerID) delete(brige.answer, answerID)
return result return result
}
*/
func (brige *wasmBrige) canvasTextMetrics(htmlID, font, text string) TextMetrics {
// TODO
return TextMetrics{}
}
func (brige *wasmBrige) htmlPropertyValue(htmlID, name string) string {
return ""
} }
func (brige *wasmBrige) answerReceived(answer DataObject) { func (brige *wasmBrige) answerReceived(answer DataObject) {

View File

@ -175,7 +175,6 @@ func (brige *wsBrige) updateCSSProperty(htmlID, property, value string) {
} else { } else {
brige.runFunc("updateCSSProperty", htmlID, property, value) brige.runFunc("updateCSSProperty", htmlID, property, value)
} }
} }
func (brige *wsBrige) updateProperty(htmlID, property string, value any) { func (brige *wsBrige) updateProperty(htmlID, property string, value any) {