281 lines
12 KiB
Go
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")},
|
|
})
|
|
}
|