forked from mbk-lab/rui_orig
2
0
Fork 0

Compare commits

...

5 Commits
main ... 0.14

Author SHA1 Message Date
anoshenko 6c49f37f68 Bug fixing 2024-03-13 15:01:02 +03:00
anoshenko ebcba7f9c2 Added NoSocket parameter of the app 2024-03-12 19:32:22 +03:00
Alexei Anoshenko 30c915d73b Bug fixing 2024-02-27 17:08:05 +03:00
Alexei Anoshenko d07b24c953 Added writeMutex to wsBridge 2024-02-10 12:25:01 +03:00
Alexei Anoshenko 5354ec6ea1 Refactoring of js code 2024-01-15 08:13:46 -05:00
11 changed files with 675 additions and 438 deletions

View File

@ -20,6 +20,9 @@ import (
//go:embed app_socket.js
var socketScripts string
//go:embed app_post.js
var httpPostScripts string
func debugLog(text string) {
log.Println("\033[34m" + text)
}
@ -28,11 +31,16 @@ func errorLog(text string) {
log.Println("\033[31m" + text)
}
type sessionInfo struct {
session Session
response chan string
}
type application struct {
server *http.Server
params AppParams
createContentFunc func(Session) SessionContent
sessions map[int]Session
sessions map[int]sessionInfo
}
func (app *application) getStartPage() string {
@ -40,14 +48,18 @@ func (app *application) getStartPage() string {
defer freeStringBuilder(buffer)
buffer.WriteString("<!DOCTYPE html>\n<html>\n")
getStartPage(buffer, app.params, socketScripts)
getStartPage(buffer, app.params)
buffer.WriteString("\n</html>")
return buffer.String()
}
func (app *application) Finish() {
for _, session := range app.sessions {
session.close()
session.session.close()
if session.response != nil {
close(session.response)
session.response = nil
}
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
@ -69,7 +81,12 @@ func (app *application) nextSessionID() int {
}
func (app *application) removeSession(id int) {
delete(app.sessions, id)
if info, ok := app.sessions[id]; ok {
if info.response != nil {
close(info.response)
}
delete(app.sessions, id)
}
}
func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
@ -79,6 +96,11 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
switch req.Method {
case "POST":
if req.URL.Path == "/" {
app.postHandler(w, req)
}
case "GET":
switch req.URL.Path {
case "/":
@ -86,10 +108,20 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, app.getStartPage())
case "/ws":
if bridge := CreateSocketBridge(w, req); bridge != nil {
if bridge := createSocketBridge(w, req); bridge != nil {
go app.socketReader(bridge)
}
case "/script.js":
w.WriteHeader(http.StatusOK)
if app.params.NoSocket {
io.WriteString(w, httpPostScripts)
} else {
io.WriteString(w, socketScripts)
}
io.WriteString(w, "\n")
io.WriteString(w, defaultScripts)
default:
filename := req.URL.Path[1:]
if size := len(filename); size > 0 && filename[size-1] == '/' {
@ -104,7 +136,87 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
}
func (app *application) socketReader(bridge webBridge) {
func setSessionIDCookie(w http.ResponseWriter, sessionID int) {
cookie := http.Cookie{
Name: "session",
Value: strconv.Itoa(sessionID),
HttpOnly: true,
}
http.SetCookie(w, &cookie)
}
func (app *application) postHandler(w http.ResponseWriter, req *http.Request) {
if reqBody, err := io.ReadAll(req.Body); err == nil {
message := string(reqBody)
if ProtocolInDebugLog {
DebugLog(message)
}
if obj := ParseDataText(message); obj != nil {
var session Session = nil
var response chan string = nil
if cookie, err := req.Cookie("session"); err == nil {
sessionID, err := strconv.Atoi(cookie.Value)
if err != nil {
ErrorLog(err.Error())
} else if info, ok := app.sessions[sessionID]; ok && info.response != nil {
response = info.response
session = info.session
}
}
command := obj.Tag()
startSession := false
if session == nil || command == "startSession" {
events := make(chan DataObject, 1024)
bridge := createHttpBridge(req)
response = bridge.response
answer := ""
session, answer = app.startSession(obj, events, bridge, response)
bridge.writeMessage(answer)
session.onStart()
if command == "session-resume" {
session.onResume()
}
bridge.sendResponse()
setSessionIDCookie(w, session.ID())
startSession = true
go sessionEventHandler(session, events, bridge)
}
if !startSession {
switch command {
case "nop":
session.sendResponse()
case "session-close":
session.onFinish()
session.App().removeSession(session.ID())
return
default:
if !session.handleAnswer(command, obj) {
session.addToEventsQueue(obj)
}
}
}
io.WriteString(w, <-response)
for len(response) > 0 {
io.WriteString(w, <-response)
}
}
}
}
func (app *application) socketReader(bridge *wsBridge) {
var session Session
events := make(chan DataObject, 1024)
@ -116,7 +228,7 @@ func (app *application) socketReader(bridge webBridge) {
}
if ProtocolInDebugLog {
DebugLog(message)
DebugLog("🖥️ -> " + message)
}
if obj := ParseDataText(message); obj != nil {
@ -124,7 +236,7 @@ func (app *application) socketReader(bridge webBridge) {
switch command {
case "startSession":
answer := ""
if session, answer = app.startSession(obj, events, bridge); session != nil {
if session, answer = app.startSession(obj, events, bridge, nil); session != nil {
if !bridge.writeMessage(answer) {
return
}
@ -135,7 +247,8 @@ func (app *application) socketReader(bridge webBridge) {
case "reconnect":
if sessionText, ok := obj.PropertyValue("session"); ok {
if sessionID, err := strconv.Atoi(sessionText); err == nil {
if session = app.sessions[sessionID]; session != nil {
if info, ok := app.sessions[sessionID]; ok {
session := info.session
session.setBridge(events, bridge)
answer := allocStringBuilder()
defer freeStringBuilder(answer)
@ -157,7 +270,7 @@ func (app *application) socketReader(bridge webBridge) {
}
answer := ""
if session, answer = app.startSession(obj, events, bridge); session != nil {
if session, answer = app.startSession(obj, events, bridge, nil); session != nil {
if !bridge.writeMessage(answer) {
return
}
@ -165,23 +278,16 @@ func (app *application) socketReader(bridge webBridge) {
go sessionEventHandler(session, events, bridge)
}
case "answer":
session.handleAnswer(obj)
case "imageLoaded":
session.imageManager().imageLoaded(obj, session)
case "imageError":
session.imageManager().imageLoadError(obj, session)
default:
events <- obj
if !session.handleAnswer(command, obj) {
events <- obj
}
}
}
}
}
func sessionEventHandler(session Session, events chan DataObject, bridge webBridge) {
func sessionEventHandler(session Session, events chan DataObject, bridge bridge) {
for {
data := <-events
@ -201,7 +307,9 @@ func sessionEventHandler(session Session, events chan DataObject, bridge webBrid
}
}
func (app *application) startSession(params DataObject, events chan DataObject, bridge webBridge) (Session, string) {
func (app *application) startSession(params DataObject, events chan DataObject,
bridge bridge, response chan string) (Session, string) {
if app.createContentFunc == nil {
return nil, ""
}
@ -212,7 +320,10 @@ func (app *application) startSession(params DataObject, events chan DataObject,
return nil, ""
}
app.sessions[session.ID()] = session
app.sessions[session.ID()] = sessionInfo{
session: session,
response: response,
}
answer := allocStringBuilder()
defer freeStringBuilder(answer)
@ -236,7 +347,7 @@ var apps = []*application{}
func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) {
app := new(application)
app.params = params
app.sessions = map[int]Session{}
app.sessions = map[int]sessionInfo{}
app.createContentFunc = createContentFunc
apps = append(apps, app)
@ -295,7 +406,7 @@ func OpenBrowser(url string) bool {
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 {
if err = exec.Command(provider, url).Start(); err == nil {
return true
}
}

View File

@ -17,7 +17,7 @@ type wasmApp struct {
params AppParams
createContentFunc func(Session) SessionContent
session Session
bridge webBridge
bridge bridge
close chan DataObject
}
@ -44,17 +44,10 @@ func (app *wasmApp) handleMessage(this js.Value, args []js.Value) any {
case "session-close":
app.close <- obj
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)
if !app.session.handleAnswer(command, obj) {
app.session.handleEvent(command, obj)
}
}
}
}

23
app_post.js Normal file
View File

@ -0,0 +1,23 @@
function sendMessage(message) {
let xhr = new XMLHttpRequest();
xhr.open('POST', '/', true);
xhr.onreadystatechange = function() {
const script = this.responseText
if (script != "") {
window.eval(script)
//sendMessage("nop{session=" + sessionID +"}")
}
}
xhr.send(message);
}
window.onload = function() {
sendMessage( sessionInfo() );
}
window.onfocus = function() {
windowFocus = true
sendMessage( "session-resume{}" );
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
var socket
var socketUrl
let socket
let socketUrl
function sendMessage(message) {
if (socket) {
@ -10,7 +10,7 @@ function sendMessage(message) {
window.onload = function() {
socketUrl = document.location.protocol == "https:" ? "wss://" : "ws://"
socketUrl += document.location.hostname
var port = document.location.port
const port = document.location.port
if (port) {
socketUrl += ":" + port
}

View File

@ -38,9 +38,12 @@ type AppParams struct {
KeyFile string
// Redirect80 - if true then the function of redirect from port 80 to 443 is created
Redirect80 bool
// NoSocket - if true then WebSockets will not be used and information exchange
// between the client and the server will be carried out only via http.
NoSocket bool
}
func getStartPage(buffer *strings.Builder, params AppParams, addScripts string) {
func getStartPage(buffer *strings.Builder, params AppParams) {
buffer.WriteString(`<head>
<meta charset="utf-8">
<title>`)
@ -67,11 +70,7 @@ func getStartPage(buffer *strings.Builder, params AppParams, addScripts string)
buffer.WriteString(appStyles)
buffer.WriteString(`</style>
<style id="ruiAnimations"></style>
<script>
`)
buffer.WriteString(defaultScripts)
buffer.WriteString(addScripts)
buffer.WriteString(`</script>
<script src="/script.js"></script>
</head>
<body id="body" onkeydown="keyDownEvent(this, event)">
<div class="ruiRoot" id="ruiRootView"></div>

View File

@ -80,10 +80,11 @@ func (manager *imageManager) loadImage(url string, onLoaded func(Image), session
manager.images[url] = image
session.callFunc("loadImage", url)
session.sendResponse()
return image
}
func (manager *imageManager) imageLoaded(obj DataObject, session Session) {
func (manager *imageManager) imageLoaded(obj DataObject) {
if manager.images == nil {
manager.images = make(map[string]*imageData)
return
@ -109,7 +110,7 @@ func (manager *imageManager) imageLoaded(obj DataObject, session Session) {
}
}
func (manager *imageManager) imageLoadError(obj DataObject, session Session) {
func (manager *imageManager) imageLoadError(obj DataObject) {
if manager.images == nil {
manager.images = make(map[string]*imageData)
return

View File

@ -589,7 +589,7 @@ func (listView *listViewData) getItemFrames() []Frame {
return listView.itemFrame
}
func (listView *listViewData) itemAlign(self View, buffer *strings.Builder) {
func (listView *listViewData) itemAlign(buffer *strings.Builder) {
values := enumProperties[ItemHorizontalAlign].cssValues
if hAlign := GetListItemHorizontalAlign(listView); hAlign >= 0 && hAlign < len(values) {
buffer.WriteString(" justify-items: ")
@ -605,7 +605,7 @@ func (listView *listViewData) itemAlign(self View, buffer *strings.Builder) {
}
}
func (listView *listViewData) itemSize(self View, buffer *strings.Builder) {
func (listView *listViewData) itemSize(buffer *strings.Builder) {
if itemWidth := GetListItemWidth(listView); itemWidth.Type != Auto {
buffer.WriteString(` min-width: `)
buffer.WriteString(itemWidth.cssString("", listView.Session()))
@ -619,14 +619,14 @@ func (listView *listViewData) itemSize(self View, buffer *strings.Builder) {
}
}
func (listView *listViewData) getDivs(self View, checkbox, hCheckboxAlign, vCheckboxAlign int) (string, string, string) {
func (listView *listViewData) getDivs(checkbox, hCheckboxAlign, vCheckboxAlign int) (string, string, string) {
session := listView.Session()
contentBuilder := allocStringBuilder()
defer freeStringBuilder(contentBuilder)
contentBuilder.WriteString(`<div style="display: grid;`)
listView.itemAlign(self, contentBuilder)
listView.itemAlign(contentBuilder)
onDivBuilder := allocStringBuilder()
defer freeStringBuilder(onDivBuilder)
@ -681,7 +681,7 @@ func (listView *listViewData) getDivs(self View, checkbox, hCheckboxAlign, vChec
return onDivBuilder.String(), offDivBuilder.String(), contentBuilder.String()
}
func (listView *listViewData) checkboxItemDiv(self View, checkbox, hCheckboxAlign, vCheckboxAlign int) string {
func (listView *listViewData) checkboxItemDiv(checkbox, hCheckboxAlign, vCheckboxAlign int) string {
itemStyleBuilder := allocStringBuilder()
defer freeStringBuilder(itemStyleBuilder)
@ -760,15 +760,15 @@ func (listView *listViewData) currentInactiveStyle() string {
return listView.itemStyle(CurrentInactiveStyle, "ruiListItemSelected")
}
func (listView *listViewData) checkboxSubviews(self View, buffer *strings.Builder, checkbox int) {
func (listView *listViewData) checkboxSubviews(buffer *strings.Builder, checkbox int) {
count := listView.adapter.ListSize()
listViewID := listView.htmlID()
hCheckboxAlign := GetListViewCheckboxHorizontalAlign(listView)
vCheckboxAlign := GetListViewCheckboxVerticalAlign(listView)
itemDiv := listView.checkboxItemDiv(self, checkbox, hCheckboxAlign, vCheckboxAlign)
onDiv, offDiv, contentDiv := listView.getDivs(self, checkbox, hCheckboxAlign, vCheckboxAlign)
itemDiv := listView.checkboxItemDiv(checkbox, hCheckboxAlign, vCheckboxAlign)
onDiv, offDiv, contentDiv := listView.getDivs(checkbox, hCheckboxAlign, vCheckboxAlign)
current := GetCurrent(listView)
checkedItems := GetListViewCheckedItems(listView)
@ -784,7 +784,7 @@ func (listView *listViewData) checkboxSubviews(self View, buffer *strings.Builde
buffer.WriteString(listView.currentInactiveStyle())
}
buffer.WriteString(`" onclick="listItemClickEvent(this, event)" data-left="0" data-top="0" data-width="0" data-height="0" style="display: grid; justify-items: stretch; align-items: stretch;`)
listView.itemSize(self, buffer)
listView.itemSize(buffer)
if !listView.adapter.IsListItemEnabled(i) {
buffer.WriteString(`" data-disabled="1`)
}
@ -815,7 +815,7 @@ func (listView *listViewData) checkboxSubviews(self View, buffer *strings.Builde
}
}
func (listView *listViewData) noneCheckboxSubviews(self View, buffer *strings.Builder) {
func (listView *listViewData) noneCheckboxSubviews(buffer *strings.Builder) {
count := listView.adapter.ListSize()
listViewID := listView.htmlID()
@ -824,8 +824,8 @@ func (listView *listViewData) noneCheckboxSubviews(self View, buffer *strings.Bu
itemStyleBuilder.WriteString(`data-left="0" data-top="0" data-width="0" data-height="0" style="max-width: 100%; max-height: 100%; display: grid;`)
listView.itemAlign(self, itemStyleBuilder)
listView.itemSize(self, itemStyleBuilder)
listView.itemAlign(itemStyleBuilder)
listView.itemSize(itemStyleBuilder)
itemStyleBuilder.WriteString(`" onclick="listItemClickEvent(this, event)"`)
itemStyle := itemStyleBuilder.String()
@ -865,12 +865,12 @@ func (listView *listViewData) updateCheckboxItem(index int, checked bool) {
checkbox := GetListViewCheckbox(listView)
hCheckboxAlign := GetListViewCheckboxHorizontalAlign(listView)
vCheckboxAlign := GetListViewCheckboxVerticalAlign(listView)
onDiv, offDiv, contentDiv := listView.getDivs(listView, checkbox, hCheckboxAlign, vCheckboxAlign)
onDiv, offDiv, contentDiv := listView.getDivs(checkbox, hCheckboxAlign, vCheckboxAlign)
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
buffer.WriteString(listView.checkboxItemDiv(listView, checkbox, hCheckboxAlign, vCheckboxAlign))
buffer.WriteString(listView.checkboxItemDiv(checkbox, hCheckboxAlign, vCheckboxAlign))
if checked {
buffer.WriteString(onDiv)
} else {
@ -1061,9 +1061,9 @@ func (listView *listViewData) htmlSubviews(self View, buffer *strings.Builder) {
checkbox := GetListViewCheckbox(listView)
if checkbox == NoneCheckbox {
listView.noneCheckboxSubviews(self, buffer)
listView.noneCheckboxSubviews(buffer)
} else {
listView.checkboxSubviews(self, buffer, checkbox)
listView.checkboxSubviews(buffer, checkbox)
}
buffer.WriteString(`</div>`)

View File

@ -7,7 +7,7 @@ import (
"strings"
)
type webBridge interface {
type bridge interface {
startUpdateScript(htmlID string) bool
finishUpdateScript(htmlID string)
callFunc(funcName string, args ...any) bool
@ -16,10 +16,9 @@ type webBridge interface {
updateCSSProperty(htmlID, property, value string)
updateProperty(htmlID, property string, value any)
removeProperty(htmlID, property string)
readMessage() (string, bool)
writeMessage(text string) bool
addAnimationCSS(css string)
clearAnimation()
sendResponse()
setAnimationCSS(css string)
appendAnimationCSS(css string)
canvasStart(htmlID string)
callCanvasFunc(funcName string, args ...any)
callCanvasVarFunc(v any, funcName string, args ...any)
@ -124,7 +123,7 @@ type Session interface {
nextViewID() string
styleProperty(styleTag, property string) any
setBridge(events chan DataObject, bridge webBridge)
setBridge(events chan DataObject, bridge bridge)
writeInitScript(writer *strings.Builder)
callFunc(funcName string, args ...any)
updateInnerHTML(htmlID, html string)
@ -134,6 +133,7 @@ type Session interface {
removeProperty(htmlID, property string)
startUpdateScript(htmlID string) bool
finishUpdateScript(htmlID string)
sendResponse()
addAnimationCSS(css string)
clearAnimation()
canvasStart(htmlID string)
@ -145,7 +145,8 @@ type Session interface {
canvasFinish()
canvasTextMetrics(htmlID, font, text string) TextMetrics
htmlPropertyValue(htmlID, name string) string
handleAnswer(data DataObject)
addToEventsQueue(data DataObject)
handleAnswer(command string, data DataObject) bool
handleRootSize(data DataObject)
handleResize(data DataObject)
handleEvent(command string, data DataObject)
@ -189,7 +190,7 @@ type sessionData struct {
ignoreUpdates bool
popups *popupManager
images *imageManager
bridge webBridge
bridge bridge
events chan DataObject
animationCounter int
animationCSS string
@ -237,7 +238,7 @@ func (session *sessionData) ID() int {
return session.sessionID
}
func (session *sessionData) setBridge(events chan DataObject, bridge webBridge) {
func (session *sessionData) setBridge(events chan DataObject, bridge bridge) {
session.events = events
session.bridge = bridge
}
@ -330,23 +331,19 @@ func (session *sessionData) updateTooltipConstants() {
}
func (session *sessionData) reload() {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
css := appStyles + session.getCurrentTheme().cssText(session) + session.animationCSS
css = strings.ReplaceAll(css, "\n", `\n`)
css = strings.ReplaceAll(css, "\t", `\t`)
buffer.WriteString(`document.querySelector('style').textContent = "`)
buffer.WriteString(css)
buffer.WriteString("\";\n")
session.bridge.callFunc("setStyles", css)
if session.rootView != nil {
buffer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`)
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
viewHTML(session.rootView, buffer)
buffer.WriteString("';\nscanElementsSize();")
session.bridge.updateInnerHTML("ruiRootView", buffer.String())
session.bridge.callFunc("scanElementsSize")
}
session.bridge.writeMessage(buffer.String())
session.updateTooltipConstants()
}
@ -447,15 +444,21 @@ func (session *sessionData) finishUpdateScript(htmlID string) {
}
}
func (session *sessionData) sendResponse() {
if session.bridge != nil {
session.bridge.sendResponse()
}
}
func (session *sessionData) addAnimationCSS(css string) {
if session.bridge != nil {
session.bridge.addAnimationCSS(css)
session.bridge.appendAnimationCSS(css)
}
}
func (session *sessionData) clearAnimation() {
if session.bridge != nil {
session.bridge.clearAnimation()
session.bridge.setAnimationCSS("")
}
}
@ -520,8 +523,27 @@ func (session *sessionData) htmlPropertyValue(htmlID, name string) string {
return ""
}
func (session *sessionData) handleAnswer(data DataObject) {
session.bridge.answerReceived(data)
func (session *sessionData) handleAnswer(command string, data DataObject) bool {
switch command {
case "answer":
if session.bridge != nil {
session.bridge.answerReceived(data)
}
case "imageLoaded":
session.imageManager().imageLoaded(data)
case "imageError":
session.imageManager().imageLoadError(data)
default:
return false
}
if session.bridge != nil {
session.bridge.sendResponse()
}
return true
}
func (session *sessionData) handleRootSize(data DataObject) {
@ -672,6 +694,8 @@ func (session *sessionData) handleEvent(command string, data DataObject) {
ErrorLog(`"id" property not found. Event: ` + command)
}
}
session.bridge.sendResponse()
}
func (session *sessionData) hotKey(event KeyEvent) {
@ -769,3 +793,7 @@ func (session *sessionData) RemoveAllClientItems() {
session.clientStorage = map[string]string{}
session.bridge.callFunc("localStorageClear")
}
func (session *sessionData) addToEventsQueue(data DataObject) {
session.events <- data
}

View File

@ -16,7 +16,7 @@ type wasmBridge struct {
closeEvent chan DataObject
}
func createWasmBridge(close chan DataObject) webBridge {
func createWasmBridge(close chan DataObject) bridge {
bridge := new(wasmBridge)
bridge.answerID = 1
bridge.answer = make(map[int]chan DataObject)
@ -102,10 +102,6 @@ func (bridge *wasmBridge) close() {
bridge.closeEvent <- NewDataObject("close")
}
func (bridge *wasmBridge) readMessage() (string, bool) {
return "", false
}
func (bridge *wasmBridge) writeMessage(script string) bool {
if ProtocolInDebugLog {
DebugLog("Run script:")
@ -118,21 +114,24 @@ func (bridge *wasmBridge) writeMessage(script string) bool {
return true
}
func (bridge *wasmBridge) addAnimationCSS(css string) {
func (bridge *wasmBridge) prepareCSS(css string) string {
css = strings.ReplaceAll(css, `\t`, "\t")
css = strings.ReplaceAll(css, `\n`, "\n")
css = strings.ReplaceAll(css, `\'`, "'")
css = strings.ReplaceAll(css, `\"`, "\"")
css = strings.ReplaceAll(css, `\\`, "\\")
styles := js.Global().Get("document").Call("getElementById", "ruiAnimations")
content := styles.Get("textContent").String()
styles.Set("textContent", content+"\n"+css)
return css
}
func (bridge *wasmBridge) clearAnimation() {
func (bridge *wasmBridge) appendAnimationCSS(css string) {
styles := js.Global().Get("document").Call("getElementById", "ruiAnimations")
styles.Set("textContent", "")
content := styles.Get("textContent").String()
styles.Set("textContent", content+"\n"+bridge.prepareCSS(css))
}
func (bridge *wasmBridge) setAnimationCSS(css string) {
styles := js.Global().Get("document").Call("getElementById", "ruiAnimations")
styles.Set("textContent", bridge.prepareCSS(css))
}
func (bridge *wasmBridge) canvasStart(htmlID string) {
@ -276,3 +275,6 @@ func (bridge *wasmBridge) answerReceived(answer DataObject) {
func (bridge *wasmBridge) remoteAddr() string {
return "localhost"
}
func (bridge *wasmBridge) sendResponse() {
}

View File

@ -12,16 +12,30 @@ import (
"github.com/gorilla/websocket"
)
type webBridge struct {
answer map[int]chan DataObject
answerID int
answerMutex sync.Mutex
writeMutex sync.Mutex
closed bool
canvasBuffer strings.Builder
canvasVarNumber int
updateScripts map[string]*strings.Builder
writeMessage func(string) bool
callFuncImmediately func(funcName string, args ...any) bool
}
type wsBridge struct {
conn *websocket.Conn
answer map[int]chan DataObject
answerID int
answerMutex sync.Mutex
closed bool
buffer strings.Builder
canvasBuffer strings.Builder
canvasVarNumber int
updateScripts map[string]*strings.Builder
webBridge
conn *websocket.Conn
}
type httpBridge struct {
webBridge
responseBuffer strings.Builder
response chan string
remoteAddress string
//conn *websocket.Conn
}
type canvasVar struct {
@ -33,7 +47,7 @@ var upgrader = websocket.Upgrader{
WriteBufferSize: 8096,
}
func CreateSocketBridge(w http.ResponseWriter, req *http.Request) webBridge {
func createSocketBridge(w http.ResponseWriter, req *http.Request) *wsBridge {
conn, err := upgrader.Upgrade(w, req, nil)
if err != nil {
ErrorLog(err.Error())
@ -41,41 +55,84 @@ func CreateSocketBridge(w http.ResponseWriter, req *http.Request) webBridge {
}
bridge := new(wsBridge)
bridge.answerID = 1
bridge.answer = make(map[int]chan DataObject)
bridge.initBridge()
bridge.conn = conn
bridge.closed = false
bridge.updateScripts = map[string]*strings.Builder{}
bridge.writeMessage = func(script string) bool {
if ProtocolInDebugLog {
DebugLog("🖥️ <- " + script)
}
if bridge.conn == nil {
ErrorLog("No connection")
return false
}
bridge.writeMutex.Lock()
err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(script))
bridge.writeMutex.Unlock()
if err != nil {
ErrorLog(err.Error())
return false
}
return true
}
bridge.callFuncImmediately = bridge.callFunc
return bridge
}
func (bridge *wsBridge) close() {
bridge.closed = true
bridge.conn.Close()
func createHttpBridge(req *http.Request) *httpBridge {
bridge := new(httpBridge)
bridge.initBridge()
bridge.response = make(chan string, 10)
bridge.writeMessage = func(script string) bool {
if script != "" {
if ProtocolInDebugLog {
DebugLog(script)
}
if bridge.responseBuffer.Len() > 0 {
bridge.responseBuffer.WriteRune('\n')
}
bridge.responseBuffer.WriteString(script)
}
return true
}
bridge.callFuncImmediately = bridge.callImmediately
bridge.remoteAddress = req.RemoteAddr
return bridge
}
func (bridge *wsBridge) startUpdateScript(htmlID string) bool {
func (bridge *webBridge) initBridge() {
bridge.answerID = 1
bridge.answer = make(map[int]chan DataObject)
bridge.closed = false
bridge.updateScripts = map[string]*strings.Builder{}
}
func (bridge *webBridge) startUpdateScript(htmlID string) bool {
if _, ok := bridge.updateScripts[htmlID]; ok {
return false
}
buffer := allocStringBuilder()
bridge.updateScripts[htmlID] = buffer
buffer.WriteString("var element = document.getElementById('")
buffer.WriteString("{\nlet element = document.getElementById('")
buffer.WriteString(htmlID)
buffer.WriteString("');\nif (element) {\n")
return true
}
func (bridge *wsBridge) finishUpdateScript(htmlID string) {
func (bridge *webBridge) finishUpdateScript(htmlID string) {
if buffer, ok := bridge.updateScripts[htmlID]; ok {
buffer.WriteString("scanElementsSize();\n}\n")
buffer.WriteString("scanElementsSize();\n}\n}\n")
bridge.writeMessage(buffer.String())
freeStringBuilder(buffer)
delete(bridge.updateScripts, htmlID)
}
}
func (bridge *wsBridge) argToString(arg any) (string, bool) {
func (bridge *webBridge) argToString(arg any) (string, bool) {
switch arg := arg.(type) {
case string:
arg = strings.ReplaceAll(arg, "\\", `\\`)
@ -150,43 +207,44 @@ func (bridge *wsBridge) argToString(arg any) (string, bool) {
return "", false
}
func (bridge *wsBridge) callFunc(funcName string, args ...any) bool {
bridge.buffer.Reset()
bridge.buffer.WriteString(funcName)
bridge.buffer.WriteRune('(')
func (bridge *webBridge) callFuncScript(funcName string, args ...any) (string, bool) {
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
buffer.WriteString(funcName)
buffer.WriteRune('(')
for i, arg := range args {
argText, ok := bridge.argToString(arg)
if !ok {
return false
return "", false
}
if i > 0 {
bridge.buffer.WriteString(", ")
buffer.WriteString(", ")
}
bridge.buffer.WriteString(argText)
buffer.WriteString(argText)
}
bridge.buffer.WriteString(");")
buffer.WriteString(");")
funcText := bridge.buffer.String()
if ProtocolInDebugLog {
DebugLog("Run func: " + funcText)
}
if err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(funcText)); err != nil {
ErrorLog(err.Error())
return false
}
return true
return buffer.String(), true
}
func (bridge *wsBridge) updateInnerHTML(htmlID, html string) {
func (bridge *webBridge) callFunc(funcName string, args ...any) bool {
if funcText, ok := bridge.callFuncScript(funcName, args...); ok {
return bridge.writeMessage(funcText)
}
return false
}
func (bridge *webBridge) updateInnerHTML(htmlID, html string) {
bridge.callFunc("updateInnerHTML", htmlID, html)
}
func (bridge *wsBridge) appendToInnerHTML(htmlID, html string) {
func (bridge *webBridge) appendToInnerHTML(htmlID, html string) {
bridge.callFunc("appendToInnerHTML", htmlID, html)
}
func (bridge *wsBridge) updateCSSProperty(htmlID, property, value string) {
func (bridge *webBridge) updateCSSProperty(htmlID, property, value string) {
if buffer, ok := bridge.updateScripts[htmlID]; ok {
buffer.WriteString(`element.style['`)
buffer.WriteString(property)
@ -198,7 +256,7 @@ func (bridge *wsBridge) updateCSSProperty(htmlID, property, value string) {
}
}
func (bridge *wsBridge) updateProperty(htmlID, property string, value any) {
func (bridge *webBridge) updateProperty(htmlID, property string, value any) {
if buffer, ok := bridge.updateScripts[htmlID]; ok {
if val, ok := bridge.argToString(value); ok {
buffer.WriteString(`element.setAttribute('`)
@ -212,7 +270,7 @@ func (bridge *wsBridge) updateProperty(htmlID, property string, value any) {
}
}
func (bridge *wsBridge) removeProperty(htmlID, property string) {
func (bridge *webBridge) removeProperty(htmlID, property string) {
if buffer, ok := bridge.updateScripts[htmlID]; ok {
buffer.WriteString(`if (element.hasAttribute('`)
buffer.WriteString(property)
@ -224,28 +282,34 @@ func (bridge *wsBridge) removeProperty(htmlID, property string) {
}
}
func (bridge *wsBridge) addAnimationCSS(css string) {
bridge.writeMessage(`var styles = document.getElementById('ruiAnimations');
if (styles) {
styles.textContent += '` + css + `';
func (bridge *webBridge) appendAnimationCSS(css string) {
//bridge.callFunc("appendAnimationCSS", css)
bridge.writeMessage(`{
let styles = document.getElementById('ruiAnimations');
if (styles) {
styles.textContent += '` + css + `';
}
}`)
}
func (bridge *wsBridge) clearAnimation() {
bridge.writeMessage(`var styles = document.getElementById('ruiAnimations');
if (styles) {
styles.textContent = '';
func (bridge *webBridge) setAnimationCSS(css string) {
//bridge.callFunc("setAnimationCSS", css)
bridge.writeMessage(`{
let styles = document.getElementById('ruiAnimations');
if (styles) {
styles.textContent = '` + css + `';
}
}`)
}
func (bridge *wsBridge) canvasStart(htmlID string) {
func (bridge *webBridge) canvasStart(htmlID string) {
bridge.canvasBuffer.Reset()
bridge.canvasBuffer.WriteString(`const ctx = getCanvasContext('`)
bridge.canvasBuffer.WriteString("{\nconst ctx = getCanvasContext('")
bridge.canvasBuffer.WriteString(htmlID)
bridge.canvasBuffer.WriteString(`');`)
}
func (bridge *wsBridge) callCanvasFunc(funcName string, args ...any) {
func (bridge *webBridge) callCanvasFunc(funcName string, args ...any) {
bridge.canvasBuffer.WriteString("\nctx.")
bridge.canvasBuffer.WriteString(funcName)
bridge.canvasBuffer.WriteRune('(')
@ -259,7 +323,7 @@ func (bridge *wsBridge) callCanvasFunc(funcName string, args ...any) {
bridge.canvasBuffer.WriteString(");")
}
func (bridge *wsBridge) updateCanvasProperty(property string, value any) {
func (bridge *webBridge) updateCanvasProperty(property string, value any) {
bridge.canvasBuffer.WriteString("\nctx.")
bridge.canvasBuffer.WriteString(property)
bridge.canvasBuffer.WriteString(" = ")
@ -268,10 +332,10 @@ func (bridge *wsBridge) updateCanvasProperty(property string, value any) {
bridge.canvasBuffer.WriteString(";")
}
func (bridge *wsBridge) createCanvasVar(funcName string, args ...any) any {
func (bridge *webBridge) createCanvasVar(funcName string, args ...any) any {
bridge.canvasVarNumber++
result := canvasVar{name: fmt.Sprintf("v%d", bridge.canvasVarNumber)}
bridge.canvasBuffer.WriteString("\nvar ")
bridge.canvasBuffer.WriteString("\nlet ")
bridge.canvasBuffer.WriteString(result.name)
bridge.canvasBuffer.WriteString(" = ctx.")
bridge.canvasBuffer.WriteString(funcName)
@ -287,7 +351,7 @@ func (bridge *wsBridge) createCanvasVar(funcName string, args ...any) any {
return result
}
func (bridge *wsBridge) callCanvasVarFunc(v any, funcName string, args ...any) {
func (bridge *webBridge) callCanvasVarFunc(v any, funcName string, args ...any) {
varName, ok := v.(canvasVar)
if !ok {
return
@ -307,7 +371,7 @@ func (bridge *wsBridge) callCanvasVarFunc(v any, funcName string, args ...any) {
bridge.canvasBuffer.WriteString(");")
}
func (bridge *wsBridge) callCanvasImageFunc(url string, property string, funcName string, args ...any) {
func (bridge *webBridge) callCanvasImageFunc(url string, property string, funcName string, args ...any) {
bridge.canvasBuffer.WriteString("\nimg = images.get('")
bridge.canvasBuffer.WriteString(url)
@ -328,51 +392,12 @@ func (bridge *wsBridge) callCanvasImageFunc(url string, property string, funcNam
bridge.canvasBuffer.WriteString(");\n}")
}
func (bridge *wsBridge) canvasFinish() {
bridge.canvasBuffer.WriteString("\n")
script := bridge.canvasBuffer.String()
if ProtocolInDebugLog {
DebugLog("Run script:")
DebugLog(script)
}
if bridge.conn == nil {
ErrorLog("No connection")
} else if err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil {
ErrorLog(err.Error())
}
func (bridge *webBridge) canvasFinish() {
bridge.canvasBuffer.WriteString("\n}\n")
bridge.writeMessage(bridge.canvasBuffer.String())
}
func (bridge *wsBridge) readMessage() (string, bool) {
_, p, err := bridge.conn.ReadMessage()
if err != nil {
if !bridge.closed {
ErrorLog(err.Error())
}
return "", false
}
return string(p), true
}
func (bridge *wsBridge) writeMessage(script string) bool {
if ProtocolInDebugLog {
DebugLog("Run script:")
DebugLog(script)
}
if bridge.conn == nil {
ErrorLog("No connection")
return false
}
if err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil {
ErrorLog(err.Error())
return false
}
return true
}
func (bridge *wsBridge) canvasTextMetrics(htmlID, font, text string) TextMetrics {
result := TextMetrics{}
func (bridge *webBridge) removeValue(funcName, htmlID string, args ...string) (DataObject, bool) {
bridge.answerMutex.Lock()
answerID := bridge.answerID
bridge.answerID++
@ -381,36 +406,40 @@ func (bridge *wsBridge) canvasTextMetrics(htmlID, font, text string) TextMetrics
answer := make(chan DataObject)
bridge.answer[answerID] = answer
if bridge.callFunc("canvasTextMetrics", answerID, htmlID, font, text) {
data := <-answer
result.Width = dataFloatProperty(data, "width")
funcArgs := []any{answerID, htmlID}
for _, arg := range args {
funcArgs = append(funcArgs, arg)
}
var result DataObject = nil
ok := bridge.callFuncImmediately(funcName, funcArgs...)
if ok {
result = <-answer
}
close(answer)
delete(bridge.answer, answerID)
return result, true
}
func (bridge *webBridge) canvasTextMetrics(htmlID, font, text string) TextMetrics {
result := TextMetrics{}
if data, ok := bridge.removeValue("canvasTextMetrics", htmlID, font, text); ok {
result.Width = dataFloatProperty(data, "width")
}
return result
}
func (bridge *wsBridge) htmlPropertyValue(htmlID, name string) string {
bridge.answerMutex.Lock()
answerID := bridge.answerID
bridge.answerID++
bridge.answerMutex.Unlock()
answer := make(chan DataObject)
bridge.answer[answerID] = answer
if bridge.callFunc("getPropertyValue", answerID, htmlID, name) {
data := <-answer
func (bridge *webBridge) htmlPropertyValue(htmlID, name string) string {
if data, ok := bridge.removeValue("getPropertyValue", htmlID, name); ok {
if value, ok := data.PropertyValue("value"); ok {
return value
}
}
delete(bridge.answer, answerID)
return ""
}
func (bridge *wsBridge) answerReceived(answer DataObject) {
func (bridge *webBridge) answerReceived(answer DataObject) {
if text, ok := answer.PropertyValue("answerID"); ok {
if id, err := strconv.Atoi(text); err == nil {
if chanel, ok := bridge.answer[id]; ok {
@ -427,6 +456,55 @@ func (bridge *wsBridge) answerReceived(answer DataObject) {
}
}
func (bridge *wsBridge) close() {
bridge.closed = true
defer bridge.conn.Close()
bridge.conn = nil
}
func (bridge *wsBridge) readMessage() (string, bool) {
_, p, err := bridge.conn.ReadMessage()
if err != nil {
if !bridge.closed {
ErrorLog(err.Error())
}
return "", false
}
return string(p), true
}
func (bridge *wsBridge) sendResponse() {
}
func (bridge *wsBridge) remoteAddr() string {
return bridge.conn.RemoteAddr().String()
}
func (bridge *httpBridge) close() {
bridge.closed = true
// TODO
}
func (bridge *httpBridge) callImmediately(funcName string, args ...any) bool {
if funcText, ok := bridge.callFuncScript(funcName, args...); ok {
if ProtocolInDebugLog {
DebugLog("Run func: " + funcText)
}
bridge.response <- funcText
return true
}
return false
}
func (bridge *httpBridge) sendResponse() {
bridge.writeMutex.Lock()
text := bridge.responseBuffer.String()
bridge.responseBuffer.Reset()
bridge.writeMutex.Unlock()
bridge.response <- text
}
func (bridge *httpBridge) remoteAddr() string {
return bridge.remoteAddress
}