130 lines
3.8 KiB
Go
130 lines
3.8 KiB
Go
|
|
package form
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"github.com/df-mc/dragonfly/server/world"
|
||
|
|
"reflect"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Modal represents a modal form. These forms have a body with text and two buttons at the end, typically one
|
||
|
|
// for Yes and one for No. These buttons may have custom text, but can, unlike with a Menu form, not have
|
||
|
|
// images next to them.
|
||
|
|
type Modal struct {
|
||
|
|
title, body string
|
||
|
|
submittable ModalSubmittable
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewModal creates a new Modal form using the ModalSubmittable passed to handle the output of the form. The
|
||
|
|
// title passed is formatted following the fmt.Sprintln rules.
|
||
|
|
// Default 'yes' and 'no' buttons may be passed by setting the two exported struct fields of the submittable
|
||
|
|
// to YesButton() and NoButton() respectively.
|
||
|
|
func NewModal(submittable ModalSubmittable, title ...any) Modal {
|
||
|
|
t := reflect.TypeOf(submittable)
|
||
|
|
if t.Kind() != reflect.Struct {
|
||
|
|
panic("submittable must be struct")
|
||
|
|
}
|
||
|
|
m := Modal{title: format(title), submittable: submittable}
|
||
|
|
m.verify()
|
||
|
|
return m
|
||
|
|
}
|
||
|
|
|
||
|
|
// YesButton returns a Button which may be used as a default 'yes' button for a modal form.
|
||
|
|
func YesButton() Button {
|
||
|
|
return Button{Text: "gui.yes"}
|
||
|
|
}
|
||
|
|
|
||
|
|
// NoButton returns a Button which may be used as a default 'no' button for a modal form.
|
||
|
|
func NoButton() Button {
|
||
|
|
return Button{Text: "gui.no"}
|
||
|
|
}
|
||
|
|
|
||
|
|
// MarshalJSON ...
|
||
|
|
func (m Modal) MarshalJSON() ([]byte, error) {
|
||
|
|
return json.Marshal(map[string]any{
|
||
|
|
"type": "modal",
|
||
|
|
"title": m.title,
|
||
|
|
"content": m.body,
|
||
|
|
"button1": m.Buttons()[0].Text,
|
||
|
|
"button2": m.Buttons()[1].Text,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// WithBody creates a copy of the Modal form and changes its body to the body passed, after which the new Modal
|
||
|
|
// form is returned. The text is formatted following the rules of fmt.Sprintln.
|
||
|
|
func (m Modal) WithBody(body ...any) Modal {
|
||
|
|
m.body = format(body)
|
||
|
|
return m
|
||
|
|
}
|
||
|
|
|
||
|
|
// Title returns the formatted title passed to the menu upon construction using NewModal().
|
||
|
|
func (m Modal) Title() string {
|
||
|
|
return m.title
|
||
|
|
}
|
||
|
|
|
||
|
|
// Body returns the formatted text in the body passed to the menu using WithBody().
|
||
|
|
func (m Modal) Body() string {
|
||
|
|
return m.body
|
||
|
|
}
|
||
|
|
|
||
|
|
// SubmitJSON submits a JSON byte slice to the modal form. This byte slice contains a JSON encoded bool in it,
|
||
|
|
// which is used to determine which button was clicked.
|
||
|
|
func (m Modal) 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 value bool
|
||
|
|
if err := json.Unmarshal(b, &value); err != nil {
|
||
|
|
return fmt.Errorf("error parsing JSON as bool: %w", err)
|
||
|
|
}
|
||
|
|
if value {
|
||
|
|
m.submittable.Submit(submitter, m.Buttons()[0], tx)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
m.submittable.Submit(submitter, m.Buttons()[1], tx)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Buttons returns a list of all buttons of the Modal form, which will always be a total of two buttons.
|
||
|
|
func (m Modal) Buttons() []Button {
|
||
|
|
v := reflect.New(reflect.TypeOf(m.submittable)).Elem()
|
||
|
|
v.Set(reflect.ValueOf(m.submittable))
|
||
|
|
|
||
|
|
buttons := make([]Button, 0, v.NumField())
|
||
|
|
for i := 0; i < v.NumField(); i++ {
|
||
|
|
field := v.Field(i)
|
||
|
|
if !field.CanSet() {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
// Each exported field is guaranteed to be of type Button.
|
||
|
|
buttons = append(buttons, field.Interface().(Button))
|
||
|
|
}
|
||
|
|
return buttons
|
||
|
|
}
|
||
|
|
|
||
|
|
// verify verifies that the Modal form is valid. It checks if exactly two exported fields are present and
|
||
|
|
// ensures that both have the Button type.
|
||
|
|
func (m Modal) verify() {
|
||
|
|
var count int
|
||
|
|
|
||
|
|
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().(Button); !ok {
|
||
|
|
panic("both exported fields must be of the type form.Button")
|
||
|
|
}
|
||
|
|
count++
|
||
|
|
}
|
||
|
|
if count != 2 {
|
||
|
|
panic("modal form must have exactly two exported fields of the type form.Button")
|
||
|
|
}
|
||
|
|
}
|