Files
seanime-docker/seanime-2.9.10/internal/plugin/ui/components.go
2025-09-20 14:08:38 +01:00

281 lines
12 KiB
Go

package plugin_ui
import (
"errors"
"github.com/dop251/goja"
"github.com/goccy/go-json"
)
const (
MAX_FIELD_REFS = 100
)
// ComponentManager is used to register components.
// Any higher-order UI system must use this to register components. (Tray)
type ComponentManager struct {
ctx *Context
// Last rendered components
lastRenderedComponents interface{}
}
// jsDiv
//
// Example:
// const div = tray.div({
// items: [
// tray.text("Some text"),
// ]
// })
func (c *ComponentManager) jsDiv(call goja.FunctionCall) goja.Value {
return defineComponent(c.ctx.vm, call, "div", []ComponentProp{
{Name: "items", Type: "array", Required: false, OptionalFirstArg: true},
{Name: "style", Type: "object", Required: false, Validate: validateType("object")},
{Name: "className", Type: "string", Required: false, Validate: validateType("string")},
})
}
// jsFlex
//
// Example:
// const flex = tray.flex({
// items: [
// tray.button({ label: "A button", onClick: "my-action" }),
// true ? tray.text("Some text") : null,
// ]
// })
// tray.render(() => flex)
func (c *ComponentManager) jsFlex(call goja.FunctionCall) goja.Value {
return defineComponent(c.ctx.vm, call, "flex", []ComponentProp{
{Name: "items", Type: "array", Required: false, OptionalFirstArg: true},
{Name: "style", Type: "object", Required: false, Validate: validateType("object")},
{Name: "gap", Type: "number", Required: false, Default: 2, Validate: validateType("number")},
{Name: "direction", Type: "string", Required: false, Default: "row", Validate: validateType("string")},
{Name: "className", Type: "string", Required: false, Validate: validateType("string")},
})
}
// jsStack
//
// Example:
// const stack = tray.stack({
// items: [
// tray.text("Some text"),
// ]
// })
func (c *ComponentManager) jsStack(call goja.FunctionCall) goja.Value {
return defineComponent(c.ctx.vm, call, "stack", []ComponentProp{
{Name: "items", Type: "array", Required: false, OptionalFirstArg: true},
{Name: "style", Type: "object", Required: false, Validate: validateType("object")},
{Name: "gap", Type: "number", Required: false, Default: 2, Validate: validateType("number")},
{Name: "className", Type: "string", Required: false, Validate: validateType("string")},
})
}
// jsText
//
// Example:
// const text = tray.text("Some text")
// // or
// const text = tray.text({ text: "Some text" })
func (c *ComponentManager) jsText(call goja.FunctionCall) goja.Value {
return defineComponent(c.ctx.vm, call, "text", []ComponentProp{
{Name: "text", Type: "string", Required: true, OptionalFirstArg: true, Validate: validateType("string")},
{Name: "style", Type: "object", Required: false, Validate: validateType("object")},
{Name: "className", Type: "string", Required: false, Validate: validateType("string")},
})
}
// jsButton
//
// Example:
// const button = tray.button("Click me")
// // or
// const button = tray.button({ label: "Click me", onClick: "my-action" })
func (c *ComponentManager) jsButton(call goja.FunctionCall) goja.Value {
return defineComponent(c.ctx.vm, call, "button", []ComponentProp{
{Name: "label", Type: "string", Required: true, OptionalFirstArg: true, Validate: validateType("string")},
{Name: "onClick", Type: "string", Required: false, Validate: validateType("string")},
{Name: "style", Type: "object", Required: false, Validate: validateType("object")},
{Name: "intent", Type: "string", Required: false, Validate: validateType("string")},
{Name: "disabled", Type: "boolean", Required: false, Default: false, Validate: validateType("boolean")},
{Name: "loading", Type: "boolean", Required: false, Default: false, Validate: validateType("boolean")},
{Name: "size", Type: "string", Required: false, Validate: validateType("string")},
{Name: "className", Type: "string", Required: false, Validate: validateType("string")},
})
}
// jsAnchor
//
// Example:
// const anchor = tray.anchor("Click here", { href: "https://example.com" })
// // or
// const anchor = tray.anchor({ text: "Click here", href: "https://example.com" })
func (c *ComponentManager) jsAnchor(call goja.FunctionCall) goja.Value {
return defineComponent(c.ctx.vm, call, "anchor", []ComponentProp{
{Name: "text", Type: "string", Required: true, OptionalFirstArg: true, Validate: validateType("string")},
{Name: "href", Type: "string", Required: true, Validate: validateType("string")},
{Name: "target", Type: "string", Required: false, Default: "_blank", Validate: validateType("string")},
{Name: "onClick", Type: "string", Required: false, Validate: validateType("string")},
{Name: "style", Type: "object", Required: false, Validate: validateType("object")},
{Name: "className", Type: "string", Required: false, Validate: validateType("string")},
})
}
////////////////////////////////////////////
// Fields
////////////////////////////////////////////
// jsInput
//
// Example:
// const input = tray.input("Enter your name") // placeholder as shorthand
// // or
// const input = tray.input({
// placeholder: "Enter your name",
// value: "John",
// onChange: "input-changed"
// })
func (c *ComponentManager) jsInput(call goja.FunctionCall) goja.Value {
return defineComponent(c.ctx.vm, call, "input", []ComponentProp{
{Name: "label", Type: "string", Required: false, OptionalFirstArg: true, Validate: validateType("string")},
{Name: "placeholder", Type: "string", Required: false, Validate: validateType("string")},
{Name: "value", Type: "string", Required: false, Default: "", Validate: validateType("string")},
{Name: "onChange", Type: "string", Required: false, Validate: validateType("string")},
{Name: "onSelect", Type: "string", Required: false, Validate: validateType("string")},
{Name: "fieldRef", Type: "object", Required: false, Validate: validateType("object")},
{Name: "style", Type: "object", Required: false, Validate: validateType("object")},
{Name: "disabled", Type: "boolean", Required: false, Default: false, Validate: validateType("boolean")},
{Name: "textarea", Type: "boolean", Required: false, Default: false, Validate: validateType("boolean")},
{Name: "size", Type: "string", Required: false, Validate: validateType("string")},
{Name: "className", Type: "string", Required: false, Validate: validateType("string")},
})
}
func validateOptions(v interface{}) error {
if v == nil {
return errors.New("options must be an array of objects")
}
marshaled, err := json.Marshal(v)
if err != nil {
return err
}
var arr []map[string]interface{}
if err := json.Unmarshal(marshaled, &arr); err != nil {
return err
}
if len(arr) == 0 {
return nil
}
for _, option := range arr {
if _, ok := option["label"]; !ok {
return errors.New("options must be an array of objects with a label property")
}
if _, ok := option["value"]; !ok {
return errors.New("options must be an array of objects with a value property")
}
}
return nil
}
// jsSelect
//
// Example:
// const select = tray.select("Select an item", {
// options: [{ label: "Item 1", value: "item1" }, { label: "Item 2", value: "item2" }],
// onChange: "select-changed"
// })
// // or
// const select = tray.select({
// placeholder: "Select an item",
// options: [{ label: "Item 1", value: "item1" }, { label: "Item 2", value: "item2" }],
// value: "Item 1",
// onChange: "select-changed"
// })
func (c *ComponentManager) jsSelect(call goja.FunctionCall) goja.Value {
return defineComponent(c.ctx.vm, call, "select", []ComponentProp{
{Name: "label", Type: "string", Required: true, OptionalFirstArg: true, Validate: validateType("string")},
{Name: "placeholder", Type: "string", Required: false, Validate: validateType("string")},
{
Name: "options",
Type: "array",
Required: true,
Validate: validateOptions,
},
{Name: "value", Type: "string", Required: false, Default: "", Validate: validateType("string")},
{Name: "onChange", Type: "string", Required: false, Validate: validateType("string")},
{Name: "fieldRef", Type: "object", Required: false, Validate: validateType("object")},
{Name: "style", Type: "object", Required: false, Validate: validateType("object")},
{Name: "disabled", Type: "boolean", Required: false, Default: false, Validate: validateType("boolean")},
{Name: "size", Type: "string", Required: false, Validate: validateType("string")},
{Name: "className", Type: "string", Required: false, Validate: validateType("string")},
})
}
// jsCheckbox
//
// Example:
// const checkbox = tray.checkbox("I agree to the terms and conditions")
// // or
// const checkbox = tray.checkbox({ label: "I agree to the terms and conditions", value: true })
func (c *ComponentManager) jsCheckbox(call goja.FunctionCall) goja.Value {
return defineComponent(c.ctx.vm, call, "checkbox", []ComponentProp{
{Name: "label", Type: "string", Required: true, OptionalFirstArg: true, Validate: validateType("string")},
{Name: "value", Type: "boolean", Required: false, Default: false, Validate: validateType("boolean")},
{Name: "onChange", Type: "string", Required: false, Validate: validateType("string")},
{Name: "fieldRef", Type: "object", Required: false, Validate: validateType("object")},
{Name: "style", Type: "object", Required: false, Validate: validateType("object")},
{Name: "disabled", Type: "boolean", Required: false, Default: false, Validate: validateType("boolean")},
{Name: "size", Type: "string", Required: false, Validate: validateType("string")},
{Name: "className", Type: "string", Required: false, Validate: validateType("string")},
})
}
// jsRadioGroup
//
// Example:
// const radioGroup = tray.radioGroup({
// options: [{ label: "Item 1", value: "item1" }, { label: "Item 2", value: "item2" }],
// onChange: "radio-group-changed"
// })
func (c *ComponentManager) jsRadioGroup(call goja.FunctionCall) goja.Value {
return defineComponent(c.ctx.vm, call, "radio-group", []ComponentProp{
{Name: "label", Type: "string", Required: true, OptionalFirstArg: true, Validate: validateType("string")},
{Name: "value", Type: "string", Required: false, Default: "", Validate: validateType("string")},
{
Name: "options",
Type: "array",
Required: true,
Validate: validateOptions,
},
{Name: "onChange", Type: "string", Required: false, Validate: validateType("string")},
{Name: "fieldRef", Type: "object", Required: false, Validate: validateType("object")},
{Name: "style", Type: "object", Required: false, Validate: validateType("object")},
{Name: "disabled", Type: "boolean", Required: false, Default: false, Validate: validateType("boolean")},
{Name: "size", Type: "string", Required: false, Validate: validateType("string")},
{Name: "className", Type: "string", Required: false, Validate: validateType("string")},
})
}
// jsSwitch
//
// Example:
// const switch = tray.switch({
// label: "Toggle me",
// value: true
// })
func (c *ComponentManager) jsSwitch(call goja.FunctionCall) goja.Value {
return defineComponent(c.ctx.vm, call, "switch", []ComponentProp{
{Name: "label", Type: "string", Required: true, OptionalFirstArg: true, Validate: validateType("string")},
{Name: "value", Type: "boolean", Required: false, Default: false, Validate: validateType("boolean")},
{Name: "onChange", Type: "string", Required: false, Validate: validateType("string")},
{Name: "fieldRef", Type: "object", Required: false, Validate: validateType("object")},
{Name: "style", Type: "object", Required: false, Validate: validateType("object")},
{Name: "disabled", Type: "boolean", Required: false, Default: false, Validate: validateType("boolean")},
{Name: "size", Type: "string", Required: false, Validate: validateType("string")},
{Name: "side", Type: "string", Required: false, Validate: validateType("string")},
{Name: "className", Type: "string", Required: false, Validate: validateType("string")},
})
}