up3
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
package form
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/df-mc/dragonfly/server/world"
|
||||
)
|
||||
|
||||
// Menu represents a menu form. These menus are made up of a title and a body, with a number of elements which
|
||||
// come below the body. These elements can include buttons, dividers, headers, and labels.
|
||||
type Menu struct {
|
||||
title, body string
|
||||
submittable MenuSubmittable
|
||||
elements []MenuElement
|
||||
}
|
||||
|
||||
// NewMenu creates a new Menu form using the MenuSubmittable passed to handle the output of the form. The
|
||||
// title passed is formatted following the rules of fmt.Sprintln.
|
||||
func NewMenu(submittable MenuSubmittable, title ...any) Menu {
|
||||
t := reflect.TypeOf(submittable)
|
||||
if t.Kind() != reflect.Struct {
|
||||
panic("submittable must be struct")
|
||||
}
|
||||
m := Menu{title: format(title), submittable: submittable}
|
||||
m.verify()
|
||||
return m
|
||||
}
|
||||
|
||||
// MarshalJSON ...
|
||||
func (m Menu) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"type": "form",
|
||||
"title": m.title,
|
||||
"content": m.body,
|
||||
"elements": m.Elements(),
|
||||
})
|
||||
}
|
||||
|
||||
// WithBody creates a copy of the Menu form and changes its body to the body passed, after which the new Menu
|
||||
// form is returned. The text is formatted following the rules of fmt.Sprintln.
|
||||
func (m Menu) WithBody(body ...any) Menu {
|
||||
m.body = format(body)
|
||||
return m
|
||||
}
|
||||
|
||||
// AddButton appends a button to the menu's element list and returns the updated Menu.
|
||||
func (m Menu) AddButton(button Button) Menu {
|
||||
m.elements = append(m.elements, button)
|
||||
return m
|
||||
}
|
||||
|
||||
// AddDivider appends a divider to the menu's element list and returns the updated Menu.
|
||||
func (m Menu) AddDivider(divider Divider) Menu {
|
||||
m.elements = append(m.elements, divider)
|
||||
return m
|
||||
}
|
||||
|
||||
// AddHeader appends a header to the menu's element list and returns the updated Menu.
|
||||
func (m Menu) AddHeader(header Header) Menu {
|
||||
m.elements = append(m.elements, header)
|
||||
return m
|
||||
}
|
||||
|
||||
// AddLabel appends a label to the menu's element list and returns the updated Menu.
|
||||
func (m Menu) AddLabel(label Label) Menu {
|
||||
m.elements = append(m.elements, label)
|
||||
return m
|
||||
}
|
||||
|
||||
// WithButtons creates a copy of the Menu form and appends the buttons passed to the existing elements, after
|
||||
// which the new Menu form is returned.
|
||||
func (m Menu) WithButtons(buttons ...Button) Menu {
|
||||
for _, b := range buttons {
|
||||
m.elements = append(m.elements, b)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// WithElements creates a copy of the Menu form and appends the elements passed to the existing elements, after
|
||||
// which the new Menu form is returned. This allows adding any MenuElement type.
|
||||
func (m Menu) WithElements(elements ...MenuElement) Menu {
|
||||
m.elements = append(m.elements, elements...)
|
||||
return m
|
||||
}
|
||||
|
||||
// Title returns the formatted title passed to the menu upon construction using NewMenu().
|
||||
func (m Menu) Title() string {
|
||||
return m.title
|
||||
}
|
||||
|
||||
// Body returns the formatted text in the body passed to the menu using WithBody().
|
||||
func (m Menu) Body() string {
|
||||
return m.body
|
||||
}
|
||||
|
||||
// Buttons returns a list of all buttons of the MenuSubmittable. It collects buttons from the MenuSubmittable
|
||||
// fields and any buttons added via WithButtons(), AddButton().
|
||||
func (m Menu) Buttons() []Button {
|
||||
v := reflect.New(reflect.TypeOf(m.submittable)).Elem()
|
||||
v.Set(reflect.ValueOf(m.submittable))
|
||||
|
||||
buttons := make([]Button, 0)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
if !field.CanSet() {
|
||||
continue
|
||||
}
|
||||
if b, ok := field.Interface().(Button); ok {
|
||||
buttons = append(buttons, b)
|
||||
}
|
||||
}
|
||||
for _, elem := range m.elements {
|
||||
if b, ok := elem.(Button); ok {
|
||||
buttons = append(buttons, b)
|
||||
}
|
||||
}
|
||||
return buttons
|
||||
}
|
||||
|
||||
// Elements returns all elements of this menu form. It collects elements from the MenuSubmittable
|
||||
// fields and any elements added via WithElements().
|
||||
func (m Menu) Elements() []MenuElement {
|
||||
v := reflect.New(reflect.TypeOf(m.submittable)).Elem()
|
||||
v.Set(reflect.ValueOf(m.submittable))
|
||||
|
||||
elements := make([]MenuElement, 0, v.NumField()+len(m.elements))
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
if !field.CanSet() {
|
||||
continue
|
||||
}
|
||||
elements = append(elements, field.Interface().(MenuElement))
|
||||
}
|
||||
elements = append(elements, m.elements...)
|
||||
return elements
|
||||
}
|
||||
|
||||
// SubmitJSON submits a JSON value to the menu, containing the index of the button clicked.
|
||||
func (m Menu) SubmitJSON(b []byte, submitter Submitter, tx *world.Tx) error {
|
||||
if b == nil {
|
||||
if closer, ok := m.submittable.(Closer); ok {
|
||||
closer.Close(submitter, tx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var index uint
|
||||
err := json.Unmarshal(b, &index)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse button index as int: %w", err)
|
||||
}
|
||||
buttons := m.Buttons()
|
||||
if index >= uint(len(buttons)) {
|
||||
return fmt.Errorf("button index points to inexistent button: %v (only %v buttons present)", index, len(buttons))
|
||||
}
|
||||
m.submittable.Submit(submitter, buttons[index], tx)
|
||||
return nil
|
||||
}
|
||||
|
||||
// verify verifies if the form is valid, checking all exported fields implement MenuElement.
|
||||
// It panics if the form is not valid.
|
||||
func (m Menu) verify() {
|
||||
v := reflect.New(reflect.TypeOf(m.submittable)).Elem()
|
||||
v.Set(reflect.ValueOf(m.submittable))
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if !v.Field(i).CanSet() {
|
||||
continue
|
||||
}
|
||||
if _, ok := v.Field(i).Interface().(MenuElement); !ok {
|
||||
panic("all exported fields must implement form.MenuElement")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user