Skip to content

Commit 0cfa0f8

Browse files
committed
default client + working on better type conversions for useatom...
1 parent 12318a4 commit 0cfa0f8

5 files changed

Lines changed: 175 additions & 22 deletions

File tree

tsunami/app/defaultclient.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2025, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package app
5+
6+
import (
7+
"context"
8+
"net/http"
9+
10+
"github.com/wavetermdev/waveterm/tsunami/vdom"
11+
)
12+
13+
var defaultClient = MakeClient(AppOpts{})
14+
15+
// Default client methods that operate on the global defaultClient
16+
17+
func DefineComponent[P any](name string, renderFn func(ctx context.Context, props P) any) vdom.Component[P] {
18+
return DefineComponentEx(defaultClient, name, renderFn)
19+
}
20+
21+
func SetGlobalEventHandler(handler func(client *Client, event vdom.VDomEvent)) {
22+
defaultClient.SetGlobalEventHandler(handler)
23+
}
24+
25+
func SetAppOpts(appOpts AppOpts) {
26+
defaultClient.SetAppOpts(appOpts)
27+
}
28+
29+
func AddSetupFn(fn func()) {
30+
defaultClient.AddSetupFn(fn)
31+
}
32+
33+
func RunMain() {
34+
defaultClient.RunMain()
35+
}
36+
37+
func SendAsyncInitiation() error {
38+
return defaultClient.SendAsyncInitiation()
39+
}
40+
41+
func SetAtomVals(m map[string]any) {
42+
defaultClient.SetAtomVals(m)
43+
}
44+
45+
func SetAtomVal(name string, val any) {
46+
defaultClient.SetAtomVal(name, val)
47+
}
48+
49+
func GetAtomVal(name string) any {
50+
return defaultClient.GetAtomVal(name)
51+
}
52+
53+
func RegisterUrlPathHandler(path string, handler http.Handler) {
54+
defaultClient.RegisterUrlPathHandler(path, handler)
55+
}
56+
57+
func RegisterFilePrefixHandler(prefix string, optionProvider func(path string) (*FileHandlerOption, error)) {
58+
defaultClient.RegisterFilePrefixHandler(prefix, optionProvider)
59+
}
60+
61+
func RegisterFileHandler(path string, option FileHandlerOption) {
62+
defaultClient.RegisterFileHandler(path, option)
63+
}

tsunami/app/tsunamiapp.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,14 +246,14 @@ func makeNullVDom() *vdom.VDomElem {
246246
return &vdom.VDomElem{WaveId: uuid.New().String(), Tag: vdom.WaveNullTag}
247247
}
248248

249-
func DefineComponent[P any](client *Client, name string, renderFn func(ctx context.Context, props P) any) vdom.Component[P] {
249+
func DefineComponentEx[P any](client *Client, name string, renderFn func(ctx context.Context, props P) any) vdom.Component[P] {
250250
if name == "" {
251251
panic("Component name cannot be empty")
252252
}
253253
if !unicode.IsUpper(rune(name[0])) {
254254
panic("Component name must start with an uppercase letter")
255255
}
256-
err := client.RegisterComponent(name, renderFn)
256+
err := client.registerComponent(name, renderFn)
257257
if err != nil {
258258
panic(err)
259259
}
@@ -262,7 +262,7 @@ func DefineComponent[P any](client *Client, name string, renderFn func(ctx conte
262262
}
263263
}
264264

265-
func (c *Client) RegisterComponent(name string, cfunc any) error {
265+
func (c *Client) registerComponent(name string, cfunc any) error {
266266
return c.Root.RegisterComponent(name, cfunc)
267267
}
268268

tsunami/demo/todo/main-todo.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ import (
1212
//go:embed tw.css
1313
var styleCSS []byte
1414

15-
// Initialize client with embedded Tailwind styles and ctrl-c handling
16-
var AppClient = app.MakeClient(app.AppOpts{
17-
CloseOnCtrlC: true,
18-
GlobalStyles: styleCSS,
19-
Title: "Todo App (Tsunami Demo)",
20-
})
15+
func init() {
16+
// Set up the default client with embedded Tailwind styles and ctrl-c handling
17+
app.SetAppOpts(app.AppOpts{
18+
CloseOnCtrlC: true,
19+
GlobalStyles: styleCSS,
20+
Title: "Todo App (Tsunami Demo)",
21+
})
22+
}
2123

2224
// Basic domain types with json tags for props
2325
type Todo struct {
@@ -46,7 +48,7 @@ type InputFieldProps struct {
4648
}
4749

4850
// Reusable input component showing keyboard event handling
49-
var InputField = app.DefineComponent(AppClient, "InputField",
51+
var InputField = app.DefineComponent("InputField",
5052
func(ctx context.Context, props InputFieldProps) any {
5153
// Example of special key handling with VDomFunc
5254
keyDown := &vdom.VDomFunc{
@@ -71,7 +73,7 @@ var InputField = app.DefineComponent(AppClient, "InputField",
7173
)
7274

7375
// Item component showing conditional classes and event handling
74-
var TodoItem = app.DefineComponent(AppClient, "TodoItem",
76+
var TodoItem = app.DefineComponent("TodoItem",
7577
func(ctx context.Context, props TodoItemProps) any {
7678
return vdom.H("div", map[string]any{
7779
"className": vdom.Classes("flex items-center gap-2.5 p-2 border border-border rounded", vdom.If(props.Todo.Completed, "opacity-70")),
@@ -94,7 +96,7 @@ var TodoItem = app.DefineComponent(AppClient, "TodoItem",
9496
)
9597

9698
// List component demonstrating mapping over data, using WithKey to set key on a component
97-
var TodoList = app.DefineComponent(AppClient, "TodoList",
99+
var TodoList = app.DefineComponent("TodoList",
98100
func(ctx context.Context, props TodoListProps) any {
99101
return vdom.H("div", map[string]any{
100102
"className": "flex flex-col gap-2",
@@ -109,7 +111,7 @@ var TodoList = app.DefineComponent(AppClient, "TodoList",
109111
)
110112

111113
// Root component showing state management and composition
112-
var App = app.DefineComponent(AppClient, "App",
114+
var App = app.DefineComponent("App",
113115
func(ctx context.Context, _ any) any {
114116
// Multiple state hooks example
115117
todos, setTodos, _ := vdom.UseState(ctx, []Todo{
@@ -187,5 +189,5 @@ var App = app.DefineComponent(AppClient, "App",
187189
)
188190

189191
func main() {
190-
AppClient.RunMain()
192+
app.RunMain()
191193
}

tsunami/util/compare.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package util
55

66
import (
7+
"math"
78
"reflect"
89
"strconv"
910
)
@@ -163,4 +164,77 @@ func NumToString[T any](value T) (string, bool) {
163164
default:
164165
return "", false
165166
}
167+
}
168+
169+
// FromFloat64 converts a float64 to the specified numeric type T
170+
// Returns the converted value and a bool indicating if the conversion was successful
171+
func FromFloat64[T any](val float64) (T, bool) {
172+
var zero T
173+
174+
// Check for NaN or infinity
175+
if math.IsNaN(val) || math.IsInf(val, 0) {
176+
return zero, false
177+
}
178+
179+
switch any(zero).(type) {
180+
case int:
181+
if val != float64(int64(val)) || val < math.MinInt || val > math.MaxInt {
182+
return zero, false
183+
}
184+
return any(int(val)).(T), true
185+
case int8:
186+
if val != float64(int64(val)) || val < math.MinInt8 || val > math.MaxInt8 {
187+
return zero, false
188+
}
189+
return any(int8(val)).(T), true
190+
case int16:
191+
if val != float64(int64(val)) || val < math.MinInt16 || val > math.MaxInt16 {
192+
return zero, false
193+
}
194+
return any(int16(val)).(T), true
195+
case int32:
196+
if val != float64(int64(val)) || val < math.MinInt32 || val > math.MaxInt32 {
197+
return zero, false
198+
}
199+
return any(int32(val)).(T), true
200+
case int64:
201+
if val != float64(int64(val)) || val < math.MinInt64 || val > math.MaxInt64 {
202+
return zero, false
203+
}
204+
return any(int64(val)).(T), true
205+
case uint:
206+
if val < 0 || val != float64(uint64(val)) || val > math.MaxUint {
207+
return zero, false
208+
}
209+
return any(uint(val)).(T), true
210+
case uint8:
211+
if val < 0 || val != float64(uint64(val)) || val > math.MaxUint8 {
212+
return zero, false
213+
}
214+
return any(uint8(val)).(T), true
215+
case uint16:
216+
if val < 0 || val != float64(uint64(val)) || val > math.MaxUint16 {
217+
return zero, false
218+
}
219+
return any(uint16(val)).(T), true
220+
case uint32:
221+
if val < 0 || val != float64(uint64(val)) || val > math.MaxUint32 {
222+
return zero, false
223+
}
224+
return any(uint32(val)).(T), true
225+
case uint64:
226+
if val < 0 || val != float64(uint64(val)) || val > math.MaxUint64 {
227+
return zero, false
228+
}
229+
return any(uint64(val)).(T), true
230+
case float32:
231+
if math.Abs(val) > math.MaxFloat32 {
232+
return zero, false
233+
}
234+
return any(float32(val)).(T), true
235+
case float64:
236+
return any(val).(T), true
237+
default:
238+
return zero, false
239+
}
166240
}

tsunami/vdom/vdom.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,26 @@ func UseState[T any](ctx context.Context, initialVal T) (T, func(T), func(func(T
313313
return rtnVal, setVal, setFuncVal
314314
}
315315

316+
func getTypedAtomValue[T any](rawVal any, atomName string) T {
317+
var result T
318+
if rawVal == nil {
319+
return *new(T)
320+
}
321+
322+
var ok bool
323+
result, ok = rawVal.(T)
324+
if !ok {
325+
// Try converting from float64 if rawVal is float64
326+
if f64Val, isFloat64 := rawVal.(float64); isFloat64 {
327+
if converted, convOk := util.FromFloat64[T](f64Val); convOk {
328+
return converted
329+
}
330+
}
331+
panic(fmt.Sprintf("UseAtom %q value type mismatch (expected %T, got %T)", atomName, *new(T), rawVal))
332+
}
333+
return result
334+
}
335+
316336
func useAtom[T any](ctx context.Context, atomName string) (T, func(T), func(func(T) T)) {
317337
vc := GetRenderContext(ctx)
318338
hookVal := vc.GetOrderedHook()
@@ -324,19 +344,13 @@ func useAtom[T any](ctx context.Context, atomName string) (T, func(T), func(func
324344
}
325345
}
326346
vc.AtomSetUsedBy(atomName, vc.GetCompWaveId(), true)
327-
atomVal, ok := vc.GetAtomVal(atomName).(T)
328-
if !ok {
329-
panic(fmt.Sprintf("UseAtom %q value type mismatch (expected %T, got %T)", atomName, atomVal, vc.GetAtomVal(atomName)))
330-
}
347+
atomVal := getTypedAtomValue[T](vc.GetAtomVal(atomName), atomName)
331348
setVal := func(newVal T) {
332349
vc.SetAtomVal(atomName, newVal, true)
333350
vc.AtomAddRenderWork(atomName)
334351
}
335352
setFuncVal := func(updateFunc func(T) T) {
336-
currentVal, ok := vc.GetAtomVal(atomName).(T)
337-
if !ok {
338-
panic(fmt.Sprintf("UseAtom %q value type mismatch in setFuncVal", atomName))
339-
}
353+
currentVal := getTypedAtomValue[T](vc.GetAtomVal(atomName), atomName)
340354
vc.SetAtomVal(atomName, updateFunc(currentVal), true)
341355
vc.AtomAddRenderWork(atomName)
342356
}

0 commit comments

Comments
 (0)