waveterm/tsunami/engine/hooks.go
Mike Sawka d272a4ec03
New AIPanel (#2370)
Massive PR, over 13k LOC updated, 128 commits to implement the first pass at the new Wave AI panel.  Two backend adapters (OpenAI and Anthropic), layout changes to support the panel, keyboard shortcuts, and a huge focus/layout change to integrate the panel seamlessly into the UI.

Also fixes some small issues found during the Wave AI journey (zoom fixes, documentation, more scss removal, circular dependency issues, settings, etc)
2025-10-07 13:32:10 -07:00

158 lines
3.6 KiB
Go

// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package engine
import (
"strconv"
"github.com/wavetermdev/waveterm/tsunami/vdom"
)
// generic hook structure
type Hook struct {
Init bool // is initialized
Idx int // index in the hook array
Fn func() func() // for useEffect
UnmountFn func() // for useEffect
Val any // for useState, useMemo, useRef
Deps []any
}
type RenderContextImpl struct {
Root *RootElem
Comp *ComponentImpl
HookIdx int
RenderOpts *RenderOpts
UsedAtoms map[string]bool // Track atoms used during this render
}
func makeContextVal(root *RootElem, comp *ComponentImpl, opts *RenderOpts) *RenderContextImpl {
return &RenderContextImpl{
Root: root,
Comp: comp,
HookIdx: 0,
RenderOpts: opts,
UsedAtoms: make(map[string]bool),
}
}
func (vc *RenderContextImpl) GetCompWaveId() string {
if vc.Comp == nil {
return ""
}
return vc.Comp.WaveId
}
func (vc *RenderContextImpl) getOrderedHook() *Hook {
if vc.Comp == nil {
panic("tsunami hooks must be called within a component (vc.Comp is nil)")
}
for len(vc.Comp.Hooks) <= vc.HookIdx {
vc.Comp.Hooks = append(vc.Comp.Hooks, &Hook{Idx: len(vc.Comp.Hooks)})
}
hookVal := vc.Comp.Hooks[vc.HookIdx]
vc.HookIdx++
return hookVal
}
func (vc *RenderContextImpl) getCompName() string {
if vc.Comp == nil || vc.Comp.Elem == nil {
return ""
}
return vc.Comp.Elem.Tag
}
func UseRenderTs(vc *RenderContextImpl) int64 {
return vc.Root.RenderTs
}
func UseId(vc *RenderContextImpl) string {
return vc.GetCompWaveId()
}
func UseLocal(vc *RenderContextImpl, initialVal any) string {
hookVal := vc.getOrderedHook()
atomName := "$local." + vc.GetCompWaveId() + "#" + strconv.Itoa(hookVal.Idx)
if !hookVal.Init {
hookVal.Init = true
atom := MakeAtomImpl(initialVal, nil)
vc.Root.RegisterAtom(atomName, atom)
closedAtomName := atomName
hookVal.UnmountFn = func() {
vc.Root.RemoveAtom(closedAtomName)
}
}
return atomName
}
func UseVDomRef(vc *RenderContextImpl) any {
hookVal := vc.getOrderedHook()
if !hookVal.Init {
hookVal.Init = true
refId := vc.GetCompWaveId() + ":" + strconv.Itoa(hookVal.Idx)
hookVal.Val = &vdom.VDomRef{Type: vdom.ObjectType_Ref, RefId: refId}
}
refVal, ok := hookVal.Val.(*vdom.VDomRef)
if !ok {
panic("UseVDomRef hook value is not a ref (possible out of order or conditional hooks)")
}
return refVal
}
func UseRef(vc *RenderContextImpl, hookInitialVal any) any {
hookVal := vc.getOrderedHook()
if !hookVal.Init {
hookVal.Init = true
hookVal.Val = hookInitialVal
}
return hookVal.Val
}
func depsEqual(deps1 []any, deps2 []any) bool {
if len(deps1) != len(deps2) {
return false
}
for i := range deps1 {
if deps1[i] != deps2[i] {
return false
}
}
return true
}
func UseEffect(vc *RenderContextImpl, fn func() func(), deps []any) {
hookVal := vc.getOrderedHook()
compTag := ""
if vc.Comp != nil {
compTag = vc.Comp.Tag
}
if !hookVal.Init {
hookVal.Init = true
hookVal.Fn = fn
hookVal.Deps = deps
vc.Root.addEffectWork(vc.GetCompWaveId(), hookVal.Idx, compTag)
return
}
// If deps is nil, always run (like React with no dependency array)
if deps == nil {
hookVal.Fn = fn
hookVal.Deps = deps
vc.Root.addEffectWork(vc.GetCompWaveId(), hookVal.Idx, compTag)
return
}
if depsEqual(hookVal.Deps, deps) {
return
}
hookVal.Fn = fn
hookVal.Deps = deps
vc.Root.addEffectWork(vc.GetCompWaveId(), hookVal.Idx, compTag)
}
func UseResync(vc *RenderContextImpl) bool {
if vc.RenderOpts == nil {
return false
}
return vc.RenderOpts.Resync
}