Added ClientSession interface

* Added ClientSession interface
* Added ClientSession method to Session interface
* Removed ClientItem, SetClientItem, RemoveClientItem, and RemoveAllClientItems methods from Session interface
This commit is contained in:
Alexei Anoshenko 2026-07-09 11:47:14 +03:00
parent 7b442dea72
commit 85e244babf
4 changed files with 199 additions and 48 deletions

View File

@ -3,7 +3,9 @@
* Removed "style-disabled" property and GetDisabledStyle function * Removed "style-disabled" property and GetDisabledStyle function
* Added GoogleFonts field to AppParams * Added GoogleFonts field to AppParams
* Added functions: GetWhiteSpace, GetWordBreak, ScrollIntoViewIfNeeded * Added functions: GetWhiteSpace, GetWordBreak, ScrollIntoViewIfNeeded
* Added Popups, PopupDefault, PopupDefaultsSeq, and SetPopupDefaults methods to Session interface * Added ClientSession interface
* Added ClientSession, Popups, PopupDefault, PopupDefaultsSeq, and SetPopupDefaults methods to Session interface
* Removed ClientItem, SetClientItem, RemoveClientItem, and RemoveAllClientItems methods from Session interface
* Added DismissWithoutAnimation add SetHotKey methods to Popup interface * Added DismissWithoutAnimation add SetHotKey methods to Popup interface
* Added "outside-color" add "outside-filter" properties to Popup interface * Added "outside-color" add "outside-filter" properties to Popup interface
* Added ViewSeq add ViewCount methods to ParentView interface * Added ViewSeq add ViewCount methods to ParentView interface

View File

@ -56,6 +56,7 @@ function sessionInfo() {
message += ",pixel-ratio=" + pixelRatio; message += ",pixel-ratio=" + pixelRatio;
} }
/*
if (localStorage.length > 0) { if (localStorage.length > 0) {
message += ",storage=" message += ",storage="
lead = "_{" lead = "_{"
@ -73,7 +74,8 @@ function sessionInfo() {
} }
message += "}" message += "}"
} }
*/
return message + "}"; return message + "}";
} }
@ -1984,6 +1986,38 @@ function getCanvasContext(elementId) {
return null; return null;
} }
function localStorageGet(request, keys) {
const key_set = keys.split(",");
var result = "";
for (const key of key_set) {
result += key;
result += ":";
try {
let value = localStorage.getItem(key);
if (value) {
result += value;
}
} catch (err) {}
result += ";";
}
sendMessage("storageValues{session=" + sessionID + ", request=" + request + ", values=`" + result + "`}")
}
function localStorageGetAll(request) {
var result = "";
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i);
let value = localStorage.getItem(key);
if (value) {
result += key;
result += ":";
result += value;
result += ";";
}
}
sendMessage("storageValues{session=" + sessionID + ", request=" + request + ", values=`" + result + "`}")
}
function localStorageSet(key, value) { function localStorageSet(key, value) {
try { try {
localStorage.setItem(key, value); localStorage.setItem(key, value);

155
clientStorage.go Normal file
View File

@ -0,0 +1,155 @@
package rui
import (
"encoding/base64"
"strconv"
"strings"
)
// ClientStorage is an interface for accessing client-side key-value storage.
type ClientStorage interface {
// Request performs an asynchronous request to obtain key-value pairs from client-side storage.
//
// The first argument specifies the function that is called for each key-value pair
// (if the pair is not in storage, then the function is called with an empty string as its value).
//
// The second argument specifies the set of keys to request.
// If no key is specified, then a query is performed for all possible key-value pairs.
Request(result func(key, value string), key ...string)
// Get returns a value by key from the client-side storage.
//
// If the key-value pair is not in the client-side storage, then the function returns an empty string.
Get(key string) string
// Set stores a key-value pair in the client-side storage.
//
// An empty string as a value removes the key-value pair.
Set(key, value string)
// RemoveAll removes all key-value pair from the client-side storage
RemoveAll()
handleEvent(command string, data DataObject)
}
type clientStorageData struct {
bridge bridge
getResult map[int]func(string, string)
lastRequest int
}
func (session *sessionData) ClientStorage() ClientStorage {
if session.clientStorage == nil {
storage := new(clientStorageData)
storage.bridge = session.bridge
session.clientStorage = storage
}
return session.clientStorage
}
func encodeClientStorageText(text string) string {
return base64.URLEncoding.EncodeToString([]byte(text))
}
func decodeClientStorageText(text string) string {
result, _ := base64.URLEncoding.DecodeString(text)
return string(result)
}
func (storage *clientStorageData) Get(key string) string {
result := make(chan string)
defer close(result)
storage.Request(func(key, value string) {
result <- value
}, key)
return <-result
}
func (storage *clientStorageData) Request(result func(key, value string), key ...string) {
if result == nil {
return
}
storage.lastRequest++
request := storage.lastRequest
storage.getResult[request] = result
buffer := allocStringBuilder()
defer freeStringBuilder(buffer)
for _, k := range key {
if k != "" {
if buffer.Len() > 0 {
buffer.WriteRune(',')
}
buffer.WriteString(encodeClientStorageText(k))
}
}
if buffer.Len() == 0 {
storage.bridge.callFunc("localStorageGetAll", request)
} else {
storage.bridge.callFunc("localStorageGet", request, buffer.String())
}
}
func (storage *clientStorageData) Set(key, value string) {
key = encodeClientStorageText(key)
if value != "" {
storage.bridge.callFunc("localStorageSet", key, encodeClientStorageText(value))
} else {
storage.bridge.callFunc("localStorageRemove", key)
}
}
func (storage *clientStorageData) RemoveAll() {
storage.bridge.callFunc("localStorageClear")
}
func (storage *clientStorageData) handleEvent(command string, data DataObject) {
request := 0
if text, ok := data.PropertyValue("request"); ok {
var err error
if request, err = strconv.Atoi(text); err != nil {
ErrorLog(err.Error())
return
}
} else {
ErrorLog("'request' property not found (command: " + command + ")")
return
}
switch command {
case "storageError":
if text, ok := data.PropertyValue("error"); ok {
ErrorLog(text)
}
//case "storageSuccess":
case "storageValues":
fn, ok := storage.getResult[request]
if !ok {
return
}
delete(storage.getResult, request)
text, ok := data.PropertyValue("error")
if !ok {
ErrorLog("'values' property not found (command: storageValues)")
return
}
for pair := range strings.SplitSeq(text, ";") {
if data := strings.Split(pair, ":"); len(data) == 2 {
key := decodeClientStorageText(data[0])
value := decodeClientStorageText(data[1])
fn(key, value)
}
}
}
}

View File

@ -135,17 +135,8 @@ 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)
// ClientItem reads value by key from the client-side storage // ClientStorage returns an interface for accessing client-side key-value storage.
ClientItem(key string) (string, bool) ClientStorage() ClientStorage
// SetClientItem stores a key-value pair in the client-side storage
SetClientItem(key, value string)
// RemoveClientItem removes a key-value pair in the client-side storage
RemoveClientItem(key string)
// RemoveAllClientItems removes all key-value pair from the client-side storage
RemoveAllClientItems()
// SetHotKey sets the function that will be called when the given hotkey is pressed. // SetHotKey sets the function that will be called when the given hotkey is pressed.
// Invoke SetHotKey(..., ..., nil) for remove hotkey function. // Invoke SetHotKey(..., ..., nil) for remove hotkey function.
@ -248,12 +239,12 @@ type sessionData struct {
animationCounter int animationCounter int
animationCSS string animationCSS string
updateScripts map[string]*strings.Builder updateScripts map[string]*strings.Builder
clientStorage map[string]string
hotkeys map[string]func(Session) hotkeys map[string]func(Session)
timers map[int]func(Session) timers map[int]func(Session)
nextTimerID int nextTimerID int
pauseTime int64 pauseTime int64
popupDefaults Params popupDefaults Params
clientStorage ClientStorage
} }
func newSession(app Application, id int, customTheme string, params DataObject) Session { func newSession(app Application, id int, customTheme string, params DataObject) Session {
@ -270,7 +261,6 @@ func newSession(app Application, id int, customTheme string, params DataObject)
session.animationCounter = 0 session.animationCounter = 0
session.animationCSS = "" session.animationCSS = ""
session.updateScripts = map[string]*strings.Builder{} session.updateScripts = map[string]*strings.Builder{}
session.clientStorage = map[string]string{}
session.hotkeys = map[string]func(Session){} session.hotkeys = map[string]func(Session){}
session.timers = map[int]func(Session){} session.timers = map[int]func(Session){}
session.nextTimerID = 1 session.nextTimerID = 1
@ -781,16 +771,6 @@ func (session *sessionData) handleSessionInfo(params DataObject) {
session.pixelRatio = f session.pixelRatio = f
} }
} }
if node := params.PropertyByTag("storage"); node != nil && node.Type() == ObjectNode {
if obj := node.Object(); obj != nil {
for element := range obj.Properties() {
if element.Type() == TextNode {
session.clientStorage[element.Tag()] = element.Text()
}
}
}
}
} }
func (session *sessionData) handleEvent(command string, data DataObject) { func (session *sessionData) handleEvent(command string, data DataObject) {
@ -826,9 +806,9 @@ func (session *sessionData) handleEvent(command string, data DataObject) {
case "sessionInfo": case "sessionInfo":
session.handleSessionInfo(data) session.handleSessionInfo(data)
case "storageError": case "storageError", "storageSuccess", "storageValues":
if text, ok := data.PropertyValue("error"); ok { if session.clientStorage != nil {
ErrorLog(text) session.clientStorage.handleEvent(command, data)
} }
default: default:
@ -932,26 +912,6 @@ func (session *sessionData) OpenURL(urlStr string) {
session.callFunc("openURL", urlStr) session.callFunc("openURL", urlStr)
} }
func (session *sessionData) ClientItem(key string) (string, bool) {
value, ok := session.clientStorage[key]
return value, ok
}
func (session *sessionData) SetClientItem(key, value string) {
session.clientStorage[key] = value
session.bridge.callFunc("localStorageSet", key, value)
}
func (session *sessionData) RemoveClientItem(key string) {
delete(session.clientStorage, key)
session.bridge.callFunc("localStorageRemove", key)
}
func (session *sessionData) RemoveAllClientItems() {
session.clientStorage = map[string]string{}
session.bridge.callFunc("localStorageClear")
}
func (session *sessionData) addToEventsQueue(data DataObject) { func (session *sessionData) addToEventsQueue(data DataObject) {
session.events <- data session.events <- data
} }