node build fixed

This commit is contained in:
ra_ma
2025-09-20 14:08:38 +01:00
parent c6ebbe069d
commit 3d298fa434
1516 changed files with 535727 additions and 2 deletions

View File

@@ -0,0 +1,361 @@
package plugin_ui
import (
"sync"
"time"
"github.com/dop251/goja"
"github.com/samber/mo"
)
type TrayManager struct {
ctx *Context
tray mo.Option[*Tray]
lastUpdatedAt time.Time
updateMutex sync.Mutex
componentManager *ComponentManager
}
func NewTrayManager(ctx *Context) *TrayManager {
return &TrayManager{
ctx: ctx,
tray: mo.None[*Tray](),
componentManager: &ComponentManager{ctx: ctx},
}
}
// renderTrayScheduled renders the new component tree.
// This function is unsafe because it is not thread-safe and should be scheduled.
func (t *TrayManager) renderTrayScheduled() {
t.updateMutex.Lock()
defer t.updateMutex.Unlock()
tray, registered := t.tray.Get()
if !registered {
return
}
if !tray.WithContent {
return
}
// Rate limit updates
//if time.Since(t.lastUpdatedAt) < time.Millisecond*200 {
// return
//}
t.lastUpdatedAt = time.Now()
t.ctx.scheduler.ScheduleAsync(func() error {
// t.ctx.logger.Trace().Msg("plugin: Rendering tray")
newComponents, err := t.componentManager.renderComponents(tray.renderFunc)
if err != nil {
t.ctx.logger.Error().Err(err).Msg("plugin: Failed to render tray")
t.ctx.handleException(err)
return nil
}
// t.ctx.logger.Trace().Msg("plugin: Sending tray update to client")
// Send the JSON value to the client
t.ctx.SendEventToClient(ServerTrayUpdatedEvent, ServerTrayUpdatedEventPayload{
Components: newComponents,
})
return nil
})
}
// sendIconToClient sends the tray icon to the client after it's been requested.
func (t *TrayManager) sendIconToClient() {
if tray, registered := t.tray.Get(); registered {
t.ctx.SendEventToClient(ServerTrayIconEvent, ServerTrayIconEventPayload{
ExtensionID: t.ctx.ext.ID,
ExtensionName: t.ctx.ext.Name,
IconURL: tray.IconURL,
WithContent: tray.WithContent,
TooltipText: tray.TooltipText,
BadgeNumber: tray.BadgeNumber,
BadgeIntent: tray.BadgeIntent,
Width: tray.Width,
MinHeight: tray.MinHeight,
})
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tray
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type Tray struct {
// WithContent is used to determine if the tray has any content
// If false, only the tray icon will be rendered and tray.render() will be ignored
WithContent bool `json:"withContent"`
IconURL string `json:"iconUrl"`
TooltipText string `json:"tooltipText"`
BadgeNumber int `json:"badgeNumber"`
BadgeIntent string `json:"badgeIntent"`
Width string `json:"width,omitempty"`
MinHeight string `json:"minHeight,omitempty"`
renderFunc func(goja.FunctionCall) goja.Value
trayManager *TrayManager
}
type Component struct {
ID string `json:"id"`
Type string `json:"type"`
Props map[string]interface{} `json:"props"`
Key string `json:"key,omitempty"`
}
// jsNewTray
//
// Example:
// const tray = ctx.newTray()
func (t *TrayManager) jsNewTray(call goja.FunctionCall) goja.Value {
tray := &Tray{
renderFunc: nil,
trayManager: t,
WithContent: true,
}
props := call.Arguments
if len(props) > 0 {
propsObj := props[0].Export().(map[string]interface{})
if propsObj["withContent"] != nil {
tray.WithContent, _ = propsObj["withContent"].(bool)
}
if propsObj["iconUrl"] != nil {
tray.IconURL, _ = propsObj["iconUrl"].(string)
}
if propsObj["tooltipText"] != nil {
tray.TooltipText, _ = propsObj["tooltipText"].(string)
}
if propsObj["width"] != nil {
tray.Width, _ = propsObj["width"].(string)
}
if propsObj["minHeight"] != nil {
tray.MinHeight, _ = propsObj["minHeight"].(string)
}
}
t.tray = mo.Some(tray)
// Create a new tray object
trayObj := t.ctx.vm.NewObject()
_ = trayObj.Set("render", tray.jsRender)
_ = trayObj.Set("update", tray.jsUpdate)
_ = trayObj.Set("onOpen", tray.jsOnOpen)
_ = trayObj.Set("onClose", tray.jsOnClose)
_ = trayObj.Set("onClick", tray.jsOnClick)
_ = trayObj.Set("open", tray.jsOpen)
_ = trayObj.Set("close", tray.jsClose)
_ = trayObj.Set("updateBadge", tray.jsUpdateBadge)
// Register components
_ = trayObj.Set("div", t.componentManager.jsDiv)
_ = trayObj.Set("flex", t.componentManager.jsFlex)
_ = trayObj.Set("stack", t.componentManager.jsStack)
_ = trayObj.Set("text", t.componentManager.jsText)
_ = trayObj.Set("button", t.componentManager.jsButton)
_ = trayObj.Set("anchor", t.componentManager.jsAnchor)
_ = trayObj.Set("input", t.componentManager.jsInput)
_ = trayObj.Set("radioGroup", t.componentManager.jsRadioGroup)
_ = trayObj.Set("switch", t.componentManager.jsSwitch)
_ = trayObj.Set("checkbox", t.componentManager.jsCheckbox)
_ = trayObj.Set("select", t.componentManager.jsSelect)
return trayObj
}
/////
// jsRender registers a function to be called when the tray is rendered/updated
//
// Example:
// tray.render(() => flex)
func (t *Tray) jsRender(call goja.FunctionCall) goja.Value {
funcRes, ok := call.Argument(0).Export().(func(goja.FunctionCall) goja.Value)
if !ok {
t.trayManager.ctx.handleTypeError("render requires a function")
}
// Set the render function
t.renderFunc = funcRes
return goja.Undefined()
}
// jsUpdate schedules a re-render on the client
//
// Example:
// tray.update()
func (t *Tray) jsUpdate(call goja.FunctionCall) goja.Value {
// Update the context's lastUIUpdateAt to prevent duplicate updates
t.trayManager.ctx.uiUpdateMu.Lock()
t.trayManager.ctx.lastUIUpdateAt = time.Now()
t.trayManager.ctx.uiUpdateMu.Unlock()
t.trayManager.renderTrayScheduled()
return goja.Undefined()
}
// jsOpen
//
// Example:
// tray.open()
func (t *Tray) jsOpen(call goja.FunctionCall) goja.Value {
t.trayManager.ctx.SendEventToClient(ServerTrayOpenEvent, ServerTrayOpenEventPayload{
ExtensionID: t.trayManager.ctx.ext.ID,
})
return goja.Undefined()
}
// jsClose
//
// Example:
// tray.close()
func (t *Tray) jsClose(call goja.FunctionCall) goja.Value {
t.trayManager.ctx.SendEventToClient(ServerTrayCloseEvent, ServerTrayCloseEventPayload{
ExtensionID: t.trayManager.ctx.ext.ID,
})
return goja.Undefined()
}
// jsUpdateBadge
//
// Example:
// tray.updateBadge({ number: 1, intent: "success" })
func (t *Tray) jsUpdateBadge(call goja.FunctionCall) goja.Value {
if len(call.Arguments) < 1 {
t.trayManager.ctx.handleTypeError("updateBadge requires a callback function")
}
propsObj, ok := call.Argument(0).Export().(map[string]interface{})
if !ok {
t.trayManager.ctx.handleTypeError("updateBadge requires a callback function")
}
number, ok := propsObj["number"].(int64)
if !ok {
t.trayManager.ctx.handleTypeError("updateBadge: number must be an integer")
}
intent, ok := propsObj["intent"].(string)
if !ok {
intent = "info"
}
t.BadgeNumber = int(number)
t.BadgeIntent = intent
t.trayManager.ctx.SendEventToClient(ServerTrayBadgeUpdatedEvent, ServerTrayBadgeUpdatedEventPayload{
BadgeNumber: t.BadgeNumber,
BadgeIntent: t.BadgeIntent,
})
return goja.Undefined()
}
// jsOnOpen
//
// Example:
// tray.onOpen(() => {
// console.log("tray opened by the user")
// })
func (t *Tray) jsOnOpen(call goja.FunctionCall) goja.Value {
if len(call.Arguments) < 1 {
t.trayManager.ctx.handleTypeError("onOpen requires a callback function")
}
callback, ok := goja.AssertFunction(call.Argument(0))
if !ok {
t.trayManager.ctx.handleTypeError("onOpen requires a callback function")
}
eventListener := t.trayManager.ctx.RegisterEventListener(ClientTrayOpenedEvent)
payload := ClientTrayOpenedEventPayload{}
eventListener.SetCallback(func(event *ClientPluginEvent) {
if event.ParsePayloadAs(ClientTrayOpenedEvent, &payload) {
t.trayManager.ctx.scheduler.ScheduleAsync(func() error {
_, err := callback(goja.Undefined(), t.trayManager.ctx.vm.ToValue(map[string]interface{}{}))
if err != nil {
t.trayManager.ctx.logger.Error().Err(err).Msg("plugin: Error running tray open callback")
}
return err
})
}
})
return goja.Undefined()
}
// jsOnClick
//
// Example:
// tray.onClick(() => {
// console.log("tray clicked by the user")
// })
func (t *Tray) jsOnClick(call goja.FunctionCall) goja.Value {
if len(call.Arguments) < 1 {
t.trayManager.ctx.handleTypeError("onClick requires a callback function")
}
callback, ok := goja.AssertFunction(call.Argument(0))
if !ok {
t.trayManager.ctx.handleTypeError("onClick requires a callback function")
}
eventListener := t.trayManager.ctx.RegisterEventListener(ClientTrayClickedEvent)
payload := ClientTrayClickedEventPayload{}
eventListener.SetCallback(func(event *ClientPluginEvent) {
if event.ParsePayloadAs(ClientTrayClickedEvent, &payload) {
t.trayManager.ctx.scheduler.ScheduleAsync(func() error {
_, err := callback(goja.Undefined(), t.trayManager.ctx.vm.ToValue(map[string]interface{}{}))
if err != nil {
t.trayManager.ctx.logger.Error().Err(err).Msg("plugin: Error running tray click callback")
}
return err
})
}
})
return goja.Undefined()
}
// jsOnClose
//
// Example:
// tray.onClose(() => {
// console.log("tray closed by the user")
// })
func (t *Tray) jsOnClose(call goja.FunctionCall) goja.Value {
if len(call.Arguments) < 1 {
t.trayManager.ctx.handleTypeError("onClose requires a callback function")
}
callback, ok := goja.AssertFunction(call.Argument(0))
if !ok {
t.trayManager.ctx.handleTypeError("onClose requires a callback function")
}
eventListener := t.trayManager.ctx.RegisterEventListener(ClientTrayClosedEvent)
payload := ClientTrayClosedEventPayload{}
eventListener.SetCallback(func(event *ClientPluginEvent) {
if event.ParsePayloadAs(ClientTrayClosedEvent, &payload) {
t.trayManager.ctx.scheduler.ScheduleAsync(func() error {
_, err := callback(goja.Undefined(), t.trayManager.ctx.vm.ToValue(map[string]interface{}{}))
if err != nil {
t.trayManager.ctx.logger.Error().Err(err).Msg("plugin: Error running tray close callback")
}
return err
})
}
})
return goja.Undefined()
}