refactor: simplify signals; small optimizations
This commit is contained in:
19
context.go
19
context.go
@@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"reflect"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-via/via/h"
|
"github.com/go-via/via/h"
|
||||||
@@ -138,18 +137,15 @@ func (c *Context) Signal(v any) *signal {
|
|||||||
sigID := genRandID()
|
sigID := genRandID()
|
||||||
if v == nil {
|
if v == nil {
|
||||||
c.app.logErr(c, "failed to bind signal: nil signal value")
|
c.app.logErr(c, "failed to bind signal: nil signal value")
|
||||||
dummy := "Error"
|
|
||||||
return &signal{
|
return &signal{
|
||||||
id: sigID,
|
id: sigID,
|
||||||
v: reflect.ValueOf(dummy),
|
val: "error",
|
||||||
t: reflect.TypeOf(dummy),
|
|
||||||
err: fmt.Errorf("context '%s' failed to bind signal '%s': nil signal value", c.id, sigID),
|
err: fmt.Errorf("context '%s' failed to bind signal '%s': nil signal value", c.id, sigID),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sig := &signal{
|
sig := &signal{
|
||||||
id: sigID,
|
id: sigID,
|
||||||
v: reflect.ValueOf(v),
|
val: v,
|
||||||
t: reflect.TypeOf(v),
|
|
||||||
changed: true,
|
changed: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,15 +171,14 @@ func (c *Context) injectSignals(sigs map[string]any) {
|
|||||||
for sigID, val := range sigs {
|
for sigID, val := range sigs {
|
||||||
if _, ok := c.signals.Load(sigID); !ok {
|
if _, ok := c.signals.Load(sigID); !ok {
|
||||||
c.signals.Store(sigID, &signal{
|
c.signals.Store(sigID, &signal{
|
||||||
id: sigID,
|
id: sigID,
|
||||||
t: reflect.TypeOf(val),
|
val: val,
|
||||||
v: reflect.ValueOf(val),
|
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
item, _ := c.signals.Load(sigID)
|
item, _ := c.signals.Load(sigID)
|
||||||
if sig, ok := item.(*signal); ok {
|
if sig, ok := item.(*signal); ok {
|
||||||
sig.v = reflect.ValueOf(val)
|
sig.val = val
|
||||||
sig.changed = false
|
sig.changed = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,7 +204,7 @@ func (c *Context) prepareSignalsForPatch() map[string]any {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if sig.changed {
|
if sig.changed {
|
||||||
updatedSigs[sigID.(string)] = fmt.Sprintf("%v", sig.v)
|
updatedSigs[sigID.(string)] = fmt.Sprintf("%v", sig.val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -288,7 +283,7 @@ func (c *Context) SyncSignals() {
|
|||||||
c.app.logWarn(c, "signal out of sync'%s': %v", sig.id, sig.err)
|
c.app.logWarn(c, "signal out of sync'%s': %v", sig.id, sig.err)
|
||||||
}
|
}
|
||||||
if sig.changed && sig.err == nil {
|
if sig.changed && sig.err == nil {
|
||||||
updatedSigs[id] = fmt.Sprintf("%v", sig.v)
|
updatedSigs[id] = fmt.Sprintf("%v", sig.val)
|
||||||
sig.changed = false
|
sig.changed = false
|
||||||
}
|
}
|
||||||
return true // continue iteration
|
return true // continue iteration
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -5,7 +5,6 @@ go 1.25.4
|
|||||||
require maragu.dev/gomponents v1.2.0
|
require maragu.dev/gomponents v1.2.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.2.0
|
|
||||||
github.com/fsnotify/fsnotify v1.9.0
|
github.com/fsnotify/fsnotify v1.9.0
|
||||||
github.com/go-via/via-plugin-picocss v0.1.0
|
github.com/go-via/via-plugin-picocss v0.1.0
|
||||||
github.com/starfederation/datastar-go v1.0.3
|
github.com/starfederation/datastar-go v1.0.3
|
||||||
@@ -14,6 +13,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/CAFxX/httpcompression v0.0.9 // indirect
|
github.com/CAFxX/httpcompression v0.0.9 // indirect
|
||||||
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
|
|||||||
@@ -153,9 +153,7 @@ func main() {
|
|||||||
chat := currentRoom.GetData(func(c *Chat) Chat {
|
chat := currentRoom.GetData(func(c *Chat) Chat {
|
||||||
n := len(c.Entries)
|
n := len(c.Entries)
|
||||||
start := n - 50
|
start := n - 50
|
||||||
if start < 0 {
|
start = max(start, 0)
|
||||||
start = 0
|
|
||||||
}
|
|
||||||
trimmed := make([]ChatEntry, n-start)
|
trimmed := make([]ChatEntry, n-start)
|
||||||
copy(trimmed, c.Entries[start:])
|
copy(trimmed, c.Entries[start:])
|
||||||
out := *c
|
out := *c
|
||||||
|
|||||||
102
signal.go
102
signal.go
@@ -2,7 +2,6 @@ package via
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -16,8 +15,7 @@ import (
|
|||||||
// reactively on an html element.
|
// reactively on an html element.
|
||||||
type signal struct {
|
type signal struct {
|
||||||
id string
|
id string
|
||||||
v reflect.Value
|
val any
|
||||||
t reflect.Type
|
|
||||||
changed bool
|
changed bool
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
@@ -45,7 +43,7 @@ func (s *signal) Bind() h.H {
|
|||||||
return h.Data("bind", s.id)
|
return h.Data("bind", s.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text binds the signal value to an html element as text.
|
// Text binds the signal value to an html span element as text.
|
||||||
//
|
//
|
||||||
// Example:
|
// Example:
|
||||||
//
|
//
|
||||||
@@ -57,130 +55,52 @@ func (s *signal) Text() h.H {
|
|||||||
// SetValue updates the signal’s value and marks it for synchronization with the browser.
|
// SetValue updates the signal’s value and marks it for synchronization with the browser.
|
||||||
// The change will be propagated to the browser using *Context.Sync() or *Context.SyncSignals().
|
// The change will be propagated to the browser using *Context.Sync() or *Context.SyncSignals().
|
||||||
func (s *signal) SetValue(v any) {
|
func (s *signal) SetValue(v any) {
|
||||||
val := reflect.ValueOf(v)
|
s.val = v
|
||||||
typ := reflect.TypeOf(v)
|
|
||||||
if typ != s.t {
|
|
||||||
s.err = fmt.Errorf("expected type '%s', got '%s'", s.t.String(), typ.String())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.v = val
|
|
||||||
s.changed = true
|
s.changed = true
|
||||||
s.err = nil
|
s.err = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// String return the signal value as a string.
|
// String return the signal value as a string.
|
||||||
func (s *signal) String() string {
|
func (s *signal) String() string {
|
||||||
return fmt.Sprintf("%v", s.v)
|
return fmt.Sprintf("%v", s.val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bool tries to read the signal value as a bool.
|
// Bool tries to read the signal value as a bool.
|
||||||
// Returns the value or false on failure.
|
// Returns the value or false on failure.
|
||||||
func (s *signal) Bool() bool {
|
func (s *signal) Bool() bool {
|
||||||
switch s.v.Kind() {
|
val := strings.ToLower(s.String())
|
||||||
case reflect.Bool:
|
return val == "true" || val == "1" || val == "yes" || val == "on"
|
||||||
return s.v.Bool()
|
|
||||||
case reflect.String:
|
|
||||||
val := strings.ToLower(s.v.String())
|
|
||||||
return val == "true" || val == "1" || val == "yes" || val == "on"
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return s.v.Int() != 0
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return s.v.Float() != 0
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Int tries to read the signal value as an int.
|
// Int tries to read the signal value as an int.
|
||||||
// Returns the value or 0 on failure.
|
// Returns the value or 0 on failure.
|
||||||
func (s *signal) Int() int {
|
func (s *signal) Int() int {
|
||||||
if n, err := strconv.Atoi(s.v.String()); err == nil {
|
if n, err := strconv.Atoi(s.String()); err == nil {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
if s.v.CanInt() {
|
|
||||||
return int(s.v.Int())
|
|
||||||
}
|
|
||||||
if s.v.CanFloat() {
|
|
||||||
return int(s.v.Float())
|
|
||||||
}
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Int64 tries to read the signal value as an int64.
|
// Int64 tries to read the signal value as an int64.
|
||||||
// Returns the value or 0 on failure.
|
// Returns the value or 0 on failure.
|
||||||
func (s *signal) Int64() int64 {
|
func (s *signal) Int64() int64 {
|
||||||
if n, err := strconv.ParseInt(s.v.String(), 10, 64); err == nil {
|
if n, err := strconv.ParseInt(s.String(), 10, 64); err == nil {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
if s.v.CanInt() {
|
|
||||||
return s.v.Int()
|
|
||||||
}
|
|
||||||
if s.v.CanFloat() {
|
|
||||||
return int64(s.v.Float())
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uint64 tries to read the signal value as an uint64.
|
|
||||||
// Returns the value or 0 on failure.
|
|
||||||
func (s *signal) Uint64() uint64 {
|
|
||||||
if n, err := strconv.ParseUint(s.v.String(), 10, 64); err == nil {
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
if s.v.CanUint() {
|
|
||||||
return s.v.Uint()
|
|
||||||
}
|
|
||||||
if s.v.CanFloat() {
|
|
||||||
return uint64(s.v.Float())
|
|
||||||
}
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Float64 tries to read the signal value as a float64.
|
// Float64 tries to read the signal value as a float64.
|
||||||
// Returns the value or 0.0 on failure.
|
// Returns the value or 0.0 on failure.
|
||||||
func (s *signal) Float64() float64 {
|
func (s *signal) Float() float64 {
|
||||||
if n, err := strconv.ParseFloat(s.v.String(), 64); err == nil {
|
if n, err := strconv.ParseFloat(s.String(), 64); err == nil {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
if s.v.CanFloat() {
|
|
||||||
return s.v.Float()
|
|
||||||
}
|
|
||||||
if s.v.CanInt() {
|
|
||||||
return float64(s.v.Int())
|
|
||||||
}
|
|
||||||
return 0.0
|
return 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complex128 tries to read the signal value as a complex128.
|
|
||||||
// Returns the value or 0 on failure.
|
|
||||||
func (s *signal) Complex128() complex128 {
|
|
||||||
if s.v.Kind() == reflect.Complex128 {
|
|
||||||
return s.v.Complex()
|
|
||||||
}
|
|
||||||
if s.v.Kind() == reflect.String {
|
|
||||||
if n, err := strconv.ParseComplex(s.v.String(), 128); err == nil {
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.v.CanFloat() {
|
|
||||||
return complex(s.v.Float(), 0)
|
|
||||||
}
|
|
||||||
if s.v.CanInt() {
|
|
||||||
return complex(float64(s.v.Int()), 0)
|
|
||||||
}
|
|
||||||
return complex(0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes tries to read the signal value as a []byte
|
// Bytes tries to read the signal value as a []byte
|
||||||
// Returns the value or an empty []byte on failure.
|
// Returns the value or an empty []byte on failure.
|
||||||
func (s *signal) Bytes() []byte {
|
func (s *signal) Bytes() []byte {
|
||||||
switch s.v.Kind() {
|
return []byte(s.String())
|
||||||
case reflect.Slice:
|
|
||||||
if s.v.Type().Elem().Kind() == reflect.Uint8 {
|
|
||||||
return s.v.Bytes()
|
|
||||||
}
|
|
||||||
case reflect.String:
|
|
||||||
return []byte(s.v.String())
|
|
||||||
}
|
|
||||||
return make([]byte, 0)
|
|
||||||
}
|
}
|
||||||
|
|||||||
25
via.go
25
via.go
@@ -145,24 +145,21 @@ func (v *V) Page(route string, initContextFn func(c *Context)) {
|
|||||||
if v.cfg.DevMode {
|
if v.cfg.DevMode {
|
||||||
v.devModePersist(c)
|
v.devModePersist(c)
|
||||||
}
|
}
|
||||||
headElements := v.documentHeadIncludes
|
v.AppendToHead(h.Meta(h.Data("signals", fmt.Sprintf("{'via-ctx':'%s'}", id))))
|
||||||
headElements = append(headElements, h.Meta(h.Data("signals", fmt.Sprintf("{'via-ctx':'%s'}", id))))
|
v.AppendToHead(h.Meta(h.Data("init", "@get('/_sse')")))
|
||||||
unloadJS := fmt.Sprintf(`
|
v.AppendToHead(h.Meta(h.Data("init", fmt.Sprintf(`window.addEventListener('beforeunload', (evt) => {
|
||||||
window.addEventListener('beforeunload', (evt) => {
|
navigator.sendBeacon('/_session/close', '%s');});`, c.id))))
|
||||||
navigator.sendBeacon('/_session/close', '%s');
|
bodyElements := []h.H{c.view()}
|
||||||
});`, c.id)
|
bodyElements = append(bodyElements, v.documentFootIncludes...)
|
||||||
headElements = append(headElements, h.Meta(h.Data("init", unloadJS)))
|
|
||||||
headElements = append(headElements, h.Meta(h.Data("init", "@get('/_sse')")))
|
|
||||||
bottomBodyElements := []h.H{c.view()}
|
|
||||||
bottomBodyElements = append(bottomBodyElements, v.documentFootIncludes...)
|
|
||||||
if v.cfg.DevMode {
|
if v.cfg.DevMode {
|
||||||
bottomBodyElements = append(bottomBodyElements, h.Script(h.Type("module"), h.Src("https://cdn.jsdelivr.net/gh/dataSPA/dataSPA-inspector@latest/dataspa-inspector.bundled.js")))
|
bodyElements = append(bodyElements, h.Script(h.Type("module"),
|
||||||
bottomBodyElements = append(bottomBodyElements, h.Raw("<dataspa-inspector/>"))
|
h.Src("https://cdn.jsdelivr.net/gh/dataSPA/dataSPA-inspector@latest/dataspa-inspector.bundled.js")))
|
||||||
|
bodyElements = append(bodyElements, h.Raw("<dataspa-inspector/>"))
|
||||||
}
|
}
|
||||||
view := h.HTML5(h.HTML5Props{
|
view := h.HTML5(h.HTML5Props{
|
||||||
Title: v.cfg.DocumentTitle,
|
Title: v.cfg.DocumentTitle,
|
||||||
Head: headElements,
|
Head: v.documentHeadIncludes,
|
||||||
Body: bottomBodyElements,
|
Body: bodyElements,
|
||||||
HTMLAttrs: []h.H{},
|
HTMLAttrs: []h.H{},
|
||||||
})
|
})
|
||||||
_ = view.Render(w)
|
_ = view.Render(w)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func TestSignal(t *testing.T) {
|
|||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
v.mux.ServeHTTP(w, req)
|
v.mux.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, "test", sig.v.Interface())
|
assert.Equal(t, "test", sig.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAction(t *testing.T) {
|
func TestAction(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user