node build fixed
This commit is contained in:
380
seanime-2.9.10/internal/plugin/ui/command.go
Normal file
380
seanime-2.9.10/internal/plugin/ui/command.go
Normal file
@@ -0,0 +1,380 @@
|
||||
package plugin_ui
|
||||
|
||||
import (
|
||||
goja_util "seanime/internal/util/goja"
|
||||
"seanime/internal/util/result"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CommandPaletteManager is a manager for the command palette.
|
||||
// Unlike the Tray, command palette items are not reactive to state changes.
|
||||
// They are only rendered when the setItems function is called or the refresh function is called.
|
||||
type CommandPaletteManager struct {
|
||||
ctx *Context
|
||||
updateMutex sync.Mutex
|
||||
lastUpdated time.Time
|
||||
componentManager *ComponentManager
|
||||
|
||||
placeholder string
|
||||
keyboardShortcut string
|
||||
|
||||
// registered is true if the command palette has been registered
|
||||
registered bool
|
||||
|
||||
items *result.Map[string, *commandItem]
|
||||
renderedItems []*CommandItemJSON // Store rendered items when setItems is called
|
||||
}
|
||||
|
||||
type (
|
||||
commandItem struct {
|
||||
index int
|
||||
id string
|
||||
label string
|
||||
value string
|
||||
filterType string // "includes" or "startsWith" or ""
|
||||
heading string
|
||||
renderFunc func(goja.FunctionCall) goja.Value
|
||||
onSelectFunc func(goja.FunctionCall) goja.Value
|
||||
}
|
||||
|
||||
// CommandItemJSON is the JSON representation of a command item.
|
||||
// It is used to send the command item to the client.
|
||||
CommandItemJSON struct {
|
||||
Index int `json:"index"`
|
||||
ID string `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Value string `json:"value"`
|
||||
FilterType string `json:"filterType"`
|
||||
Heading string `json:"heading"`
|
||||
Components interface{} `json:"components"`
|
||||
}
|
||||
)
|
||||
|
||||
func NewCommandPaletteManager(ctx *Context) *CommandPaletteManager {
|
||||
return &CommandPaletteManager{
|
||||
ctx: ctx,
|
||||
componentManager: &ComponentManager{ctx: ctx},
|
||||
items: result.NewResultMap[string, *commandItem](),
|
||||
renderedItems: make([]*CommandItemJSON, 0),
|
||||
}
|
||||
}
|
||||
|
||||
type NewCommandPaletteOptions struct {
|
||||
Placeholder string `json:"placeholder,omitempty"`
|
||||
KeyboardShortcut string `json:"keyboardShortcut,omitempty"`
|
||||
}
|
||||
|
||||
// sendInfoToClient sends the command palette info to the client after it's been requested.
|
||||
func (c *CommandPaletteManager) sendInfoToClient() {
|
||||
if c.registered {
|
||||
c.ctx.SendEventToClient(ServerCommandPaletteInfoEvent, ServerCommandPaletteInfoEventPayload{
|
||||
Placeholder: c.placeholder,
|
||||
KeyboardShortcut: c.keyboardShortcut,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandPaletteManager) jsNewCommandPalette(options NewCommandPaletteOptions) goja.Value {
|
||||
c.registered = true
|
||||
c.keyboardShortcut = options.KeyboardShortcut
|
||||
c.placeholder = options.Placeholder
|
||||
|
||||
cmdObj := c.ctx.vm.NewObject()
|
||||
|
||||
_ = cmdObj.Set("setItems", func(items []interface{}) {
|
||||
c.items.Clear()
|
||||
|
||||
for idx, item := range items {
|
||||
itemMap := item.(map[string]interface{})
|
||||
id := uuid.New().String()
|
||||
label, _ := itemMap["label"].(string)
|
||||
value, ok := itemMap["value"].(string)
|
||||
if !ok {
|
||||
c.ctx.handleTypeError("value must be a string")
|
||||
return
|
||||
}
|
||||
filterType, _ := itemMap["filterType"].(string)
|
||||
if filterType != "includes" && filterType != "startsWith" && filterType != "" {
|
||||
c.ctx.handleTypeError("filterType must be 'includes', 'startsWith'")
|
||||
return
|
||||
}
|
||||
heading, _ := itemMap["heading"].(string)
|
||||
renderFunc, ok := itemMap["render"].(func(goja.FunctionCall) goja.Value)
|
||||
if len(label) == 0 && !ok {
|
||||
c.ctx.handleTypeError("label or render function must be provided")
|
||||
return
|
||||
}
|
||||
onSelectFunc, ok := itemMap["onSelect"].(func(goja.FunctionCall) goja.Value)
|
||||
if !ok {
|
||||
c.ctx.handleTypeError("onSelect must be a function")
|
||||
return
|
||||
}
|
||||
|
||||
c.items.Set(id, &commandItem{
|
||||
index: idx,
|
||||
id: id,
|
||||
label: label,
|
||||
value: value,
|
||||
filterType: filterType,
|
||||
heading: heading,
|
||||
renderFunc: renderFunc,
|
||||
onSelectFunc: onSelectFunc,
|
||||
})
|
||||
}
|
||||
|
||||
// Convert the items to JSON
|
||||
itemsJSON := make([]*CommandItemJSON, 0)
|
||||
c.items.Range(func(key string, value *commandItem) bool {
|
||||
itemsJSON = append(itemsJSON, value.ToJSON(c.ctx, c.componentManager, c.ctx.scheduler))
|
||||
return true
|
||||
})
|
||||
// Store the converted items
|
||||
c.renderedItems = itemsJSON
|
||||
|
||||
c.renderCommandPaletteScheduled()
|
||||
})
|
||||
|
||||
_ = cmdObj.Set("refresh", func() {
|
||||
// Convert the items to JSON
|
||||
itemsJSON := make([]*CommandItemJSON, 0)
|
||||
c.items.Range(func(key string, value *commandItem) bool {
|
||||
itemsJSON = append(itemsJSON, value.ToJSON(c.ctx, c.componentManager, c.ctx.scheduler))
|
||||
return true
|
||||
})
|
||||
|
||||
c.renderedItems = itemsJSON
|
||||
|
||||
c.renderCommandPaletteScheduled()
|
||||
})
|
||||
|
||||
_ = cmdObj.Set("setPlaceholder", func(placeholder string) {
|
||||
c.placeholder = placeholder
|
||||
c.renderCommandPaletteScheduled()
|
||||
})
|
||||
|
||||
_ = cmdObj.Set("open", func() {
|
||||
c.ctx.SendEventToClient(ServerCommandPaletteOpenEvent, ServerCommandPaletteOpenEventPayload{})
|
||||
})
|
||||
|
||||
_ = cmdObj.Set("close", func() {
|
||||
c.ctx.SendEventToClient(ServerCommandPaletteCloseEvent, ServerCommandPaletteCloseEventPayload{})
|
||||
})
|
||||
|
||||
_ = cmdObj.Set("setInput", func(input string) {
|
||||
c.ctx.SendEventToClient(ServerCommandPaletteSetInputEvent, ServerCommandPaletteSetInputEventPayload{
|
||||
Value: input,
|
||||
})
|
||||
})
|
||||
|
||||
_ = cmdObj.Set("getInput", func() string {
|
||||
c.ctx.SendEventToClient(ServerCommandPaletteGetInputEvent, ServerCommandPaletteGetInputEventPayload{})
|
||||
|
||||
eventListener := c.ctx.RegisterEventListener(ClientCommandPaletteInputEvent)
|
||||
defer c.ctx.UnregisterEventListener(eventListener.ID)
|
||||
|
||||
timeout := time.After(1500 * time.Millisecond)
|
||||
input := make(chan string)
|
||||
|
||||
eventListener.SetCallback(func(event *ClientPluginEvent) {
|
||||
payload := ClientCommandPaletteInputEventPayload{}
|
||||
if event.ParsePayloadAs(ClientCommandPaletteInputEvent, &payload) {
|
||||
input <- payload.Value
|
||||
}
|
||||
})
|
||||
|
||||
// go func() {
|
||||
// for event := range eventListener.Channel {
|
||||
// if event.ParsePayloadAs(ClientCommandPaletteInputEvent, &payload) {
|
||||
// input <- payload.Value
|
||||
// }
|
||||
// }
|
||||
// }()
|
||||
|
||||
select {
|
||||
case <-timeout:
|
||||
return ""
|
||||
case input := <-input:
|
||||
return input
|
||||
}
|
||||
})
|
||||
|
||||
// jsOnOpen
|
||||
//
|
||||
// Example:
|
||||
// commandPalette.onOpen(() => {
|
||||
// console.log("command palette opened by the user")
|
||||
// })
|
||||
_ = cmdObj.Set("onOpen", func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
c.ctx.handleTypeError("onOpen requires a callback function")
|
||||
}
|
||||
|
||||
callback, ok := goja.AssertFunction(call.Argument(0))
|
||||
if !ok {
|
||||
c.ctx.handleTypeError("onOpen requires a callback function")
|
||||
}
|
||||
|
||||
eventListener := c.ctx.RegisterEventListener(ClientCommandPaletteOpenedEvent)
|
||||
|
||||
eventListener.SetCallback(func(event *ClientPluginEvent) {
|
||||
payload := ClientCommandPaletteOpenedEventPayload{}
|
||||
if event.ParsePayloadAs(ClientCommandPaletteOpenedEvent, &payload) {
|
||||
c.ctx.scheduler.ScheduleAsync(func() error {
|
||||
_, err := callback(goja.Undefined(), c.ctx.vm.ToValue(map[string]interface{}{}))
|
||||
return err
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// go func() {
|
||||
// for event := range eventListener.Channel {
|
||||
// if event.ParsePayloadAs(ClientCommandPaletteOpenedEvent, &payload) {
|
||||
// c.ctx.scheduler.ScheduleAsync(func() error {
|
||||
// _, err := callback(goja.Undefined(), c.ctx.vm.ToValue(map[string]interface{}{}))
|
||||
// if err != nil {
|
||||
// c.ctx.logger.Error().Err(err).Msg("plugin: Error running command palette open callback")
|
||||
// }
|
||||
// return err
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }()
|
||||
return goja.Undefined()
|
||||
})
|
||||
|
||||
// jsOnClose
|
||||
//
|
||||
// Example:
|
||||
// commandPalette.onClose(() => {
|
||||
// console.log("command palette closed by the user")
|
||||
// })
|
||||
_ = cmdObj.Set("onClose", func(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) < 1 {
|
||||
c.ctx.handleTypeError("onClose requires a callback function")
|
||||
}
|
||||
|
||||
callback, ok := goja.AssertFunction(call.Argument(0))
|
||||
if !ok {
|
||||
c.ctx.handleTypeError("onClose requires a callback function")
|
||||
}
|
||||
|
||||
eventListener := c.ctx.RegisterEventListener(ClientCommandPaletteClosedEvent)
|
||||
|
||||
eventListener.SetCallback(func(event *ClientPluginEvent) {
|
||||
payload := ClientCommandPaletteClosedEventPayload{}
|
||||
if event.ParsePayloadAs(ClientCommandPaletteClosedEvent, &payload) {
|
||||
c.ctx.scheduler.ScheduleAsync(func() error {
|
||||
_, err := callback(goja.Undefined(), c.ctx.vm.ToValue(map[string]interface{}{}))
|
||||
return err
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// go func() {
|
||||
// for event := range eventListener.Channel {
|
||||
// if event.ParsePayloadAs(ClientCommandPaletteClosedEvent, &payload) {
|
||||
// c.ctx.scheduler.ScheduleAsync(func() error {
|
||||
// _, err := callback(goja.Undefined(), c.ctx.vm.ToValue(map[string]interface{}{}))
|
||||
// if err != nil {
|
||||
// c.ctx.logger.Error().Err(err).Msg("plugin: Error running command palette close callback")
|
||||
// }
|
||||
// return err
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }()
|
||||
return goja.Undefined()
|
||||
})
|
||||
|
||||
eventListener := c.ctx.RegisterEventListener(ClientCommandPaletteItemSelectedEvent)
|
||||
eventListener.SetCallback(func(event *ClientPluginEvent) {
|
||||
payload := ClientCommandPaletteItemSelectedEventPayload{}
|
||||
if event.ParsePayloadAs(ClientCommandPaletteItemSelectedEvent, &payload) {
|
||||
c.ctx.scheduler.ScheduleAsync(func() error {
|
||||
item, found := c.items.Get(payload.ItemID)
|
||||
if found {
|
||||
_ = item.onSelectFunc(goja.FunctionCall{})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
})
|
||||
// go func() {
|
||||
// eventListener := c.ctx.RegisterEventListener(ClientCommandPaletteItemSelectedEvent)
|
||||
// payload := ClientCommandPaletteItemSelectedEventPayload{}
|
||||
|
||||
// for event := range eventListener.Channel {
|
||||
// if event.ParsePayloadAs(ClientCommandPaletteItemSelectedEvent, &payload) {
|
||||
// item, found := c.items.Get(payload.ItemID)
|
||||
// if found {
|
||||
// c.ctx.scheduler.ScheduleAsync(func() error {
|
||||
// _ = item.onSelectFunc(goja.FunctionCall{})
|
||||
// return nil
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }()
|
||||
|
||||
// Register components
|
||||
_ = cmdObj.Set("div", c.componentManager.jsDiv)
|
||||
_ = cmdObj.Set("flex", c.componentManager.jsFlex)
|
||||
_ = cmdObj.Set("stack", c.componentManager.jsStack)
|
||||
_ = cmdObj.Set("text", c.componentManager.jsText)
|
||||
_ = cmdObj.Set("button", c.componentManager.jsButton)
|
||||
_ = cmdObj.Set("anchor", c.componentManager.jsAnchor)
|
||||
|
||||
return cmdObj
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (c *commandItem) ToJSON(ctx *Context, componentManager *ComponentManager, scheduler *goja_util.Scheduler) *CommandItemJSON {
|
||||
|
||||
var components interface{}
|
||||
if c.renderFunc != nil {
|
||||
var err error
|
||||
components, err = componentManager.renderComponents(c.renderFunc)
|
||||
if err != nil {
|
||||
ctx.logger.Error().Err(err).Msg("plugin: Failed to render command palette item")
|
||||
ctx.handleException(err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the last rendered components, we don't care about diffing
|
||||
componentManager.lastRenderedComponents = nil
|
||||
|
||||
return &CommandItemJSON{
|
||||
Index: c.index,
|
||||
ID: c.id,
|
||||
Label: c.label,
|
||||
Value: c.value,
|
||||
FilterType: c.filterType,
|
||||
Heading: c.heading,
|
||||
Components: components,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandPaletteManager) renderCommandPaletteScheduled() {
|
||||
c.updateMutex.Lock()
|
||||
defer c.updateMutex.Unlock()
|
||||
|
||||
if !c.registered {
|
||||
return
|
||||
}
|
||||
|
||||
slices.SortFunc(c.renderedItems, func(a, b *CommandItemJSON) int {
|
||||
return a.Index - b.Index
|
||||
})
|
||||
|
||||
c.ctx.SendEventToClient(ServerCommandPaletteUpdatedEvent, ServerCommandPaletteUpdatedEventPayload{
|
||||
Placeholder: c.placeholder,
|
||||
Items: c.renderedItems,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user