//go:build js && wasm package ultraviolet import ( "context" "fmt" "sync" fw "git.buran.team/main/fairwind" ) type Parameters map[string]any type NodeBuilder func(uvc *UVContext, parameters Parameters) (*Node, error) type Route struct { Default bool Builder NodeBuilder name string // NOTE: sets automatically parameters Parameters // NOTE: sets automatically } type Routing map[string]*Route type Router struct { ctx context.Context log *fw.Log mutex sync.Mutex uvc *UVContext root *Node routing Routing stack []Route } func NewRouter(ctx context.Context, log *fw.Log, uvc *UVContext, routing Routing) (*Router, error) { defaultRoute := "" for name := range routing { if routing[name].Default { defaultRoute = name } routing[name].name = name } if defaultRoute == "" { return nil, ErrNoDefaultRoute } this := &Router{ ctx: ctx, log: log, uvc: uvc, root: &Node{ Component: uvc.Factory().NewRoot( RootComponentOptions{}, ), }, routing: routing, stack: []Route{}, } this.Push(defaultRoute, Parameters{}) // NOTE: default router haven't parameters return this, nil } func (this *Router) Update() { this.root.Update() } func (this *Router) Push(name string, parameters Parameters) error { this.mutex.Lock() defer this.mutex.Unlock() route, ok := this.routing[name] if !ok { return ErrRouteNotFound } this.stack = append( this.stack, Route{ name: name, parameters: parameters, Builder: route.Builder, }, ) go func() { err := this.refresh() if err != nil { this.log.Error("can't refresh", fw.LogError(err)) } }() return nil } func (this *Router) Pop() (Route, error) { this.mutex.Lock() defer this.mutex.Unlock() if len(this.stack) == 0 { return Route{}, ErrStackUnderflow } result := this.stack[len(this.stack)-1] this.stack = this.stack[:len(this.stack)-1] go func() { err := this.refresh() if err != nil { this.log.Error("can't refresh", fw.LogError(err)) } }() return result, nil } func (this *Router) Back() { _, err := this.Pop() if err != nil { this.log.Error("can't back on router stack", fw.LogError(err)) } } func (this *Router) Set(name string, parameters Parameters) error { this.mutex.Lock() defer this.mutex.Unlock() route, ok := this.routing[name] if !ok { return ErrRouteNotFound } this.stack = []Route{ { name: name, parameters: parameters, Builder: route.Builder, }, } go func() { err := this.refresh() if err != nil { this.log.Error("can't refresh", fw.LogError(err)) } }() return nil } func (this *Router) refresh() error { if len(this.stack) == 0 { return ErrStackUnderflow } // Get route route := this.stack[len(this.stack)-1] // Build new window child, err := route.Builder( this.uvc, route.parameters, ) if err != nil { return fmt.Errorf("can't refresh router: %w", err) } // Set new window this.root.Build(child) return nil }