github.com/df-mc/dragonfly@v0.9.13/server/player/form/modal.go (about)

     1  package form
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"reflect"
     7  )
     8  
     9  // Modal represents a modal form. These forms have a body with text and two buttons at the end, typically one
    10  // for Yes and one for No. These buttons may have custom text, but can, unlike with a Menu form, not have
    11  // images next to them.
    12  type Modal struct {
    13  	title, body string
    14  	submittable ModalSubmittable
    15  }
    16  
    17  // NewModal creates a new Modal form using the ModalSubmittable passed to handle the output of the form. The
    18  // title passed is formatted following the fmt.Sprintln rules.
    19  // Default 'yes' and 'no' buttons may be passed by setting the two exported struct fields of the submittable
    20  // to YesButton() and NoButton() respectively.
    21  func NewModal(submittable ModalSubmittable, title ...any) Modal {
    22  	t := reflect.TypeOf(submittable)
    23  	if t.Kind() != reflect.Struct {
    24  		panic("submittable must be struct")
    25  	}
    26  	m := Modal{title: format(title), submittable: submittable}
    27  	m.verify()
    28  	return m
    29  }
    30  
    31  // YesButton returns a Button which may be used as a default 'yes' button for a modal form.
    32  func YesButton() Button {
    33  	return Button{Text: "gui.yes"}
    34  }
    35  
    36  // NoButton returns a Button which may be used as a default 'no' button for a modal form.
    37  func NoButton() Button {
    38  	return Button{Text: "gui.no"}
    39  }
    40  
    41  // MarshalJSON ...
    42  func (m Modal) MarshalJSON() ([]byte, error) {
    43  	return json.Marshal(map[string]any{
    44  		"type":    "modal",
    45  		"title":   m.title,
    46  		"content": m.body,
    47  		"button1": m.Buttons()[0].Text,
    48  		"button2": m.Buttons()[1].Text,
    49  	})
    50  }
    51  
    52  // WithBody creates a copy of the Modal form and changes its body to the body passed, after which the new Modal
    53  // form is returned. The text is formatted following the rules of fmt.Sprintln.
    54  func (m Modal) WithBody(body ...any) Modal {
    55  	m.body = format(body)
    56  	return m
    57  }
    58  
    59  // Title returns the formatted title passed to the menu upon construction using NewModal().
    60  func (m Modal) Title() string {
    61  	return m.title
    62  }
    63  
    64  // Body returns the formatted text in the body passed to the menu using WithBody().
    65  func (m Modal) Body() string {
    66  	return m.body
    67  }
    68  
    69  // SubmitJSON submits a JSON byte slice to the modal form. This byte slice contains a JSON encoded bool in it,
    70  // which is used to determine which button was clicked.
    71  func (m Modal) SubmitJSON(b []byte, submitter Submitter) error {
    72  	if b == nil {
    73  		if closer, ok := m.submittable.(Closer); ok {
    74  			closer.Close(submitter)
    75  		}
    76  		return nil
    77  	}
    78  
    79  	var value bool
    80  	if err := json.Unmarshal(b, &value); err != nil {
    81  		return fmt.Errorf("error parsing JSON as bool: %w", err)
    82  	}
    83  	if value {
    84  		m.submittable.Submit(submitter, m.Buttons()[0])
    85  		return nil
    86  	}
    87  	m.submittable.Submit(submitter, m.Buttons()[1])
    88  	return nil
    89  }
    90  
    91  // Buttons returns a list of all buttons of the Modal form, which will always be a total of two buttons.
    92  func (m Modal) Buttons() []Button {
    93  	v := reflect.New(reflect.TypeOf(m.submittable)).Elem()
    94  	v.Set(reflect.ValueOf(m.submittable))
    95  
    96  	buttons := make([]Button, 0, v.NumField())
    97  	for i := 0; i < v.NumField(); i++ {
    98  		field := v.Field(i)
    99  		if !field.CanSet() {
   100  			continue
   101  		}
   102  		// Each exported field is guaranteed to be of type Button.
   103  		buttons = append(buttons, field.Interface().(Button))
   104  	}
   105  	return buttons
   106  }
   107  
   108  // verify verifies that the Modal form is valid. It checks if exactly two exported fields are present and
   109  // ensures that both have the Button type.
   110  func (m Modal) verify() {
   111  	var count int
   112  
   113  	v := reflect.New(reflect.TypeOf(m.submittable)).Elem()
   114  	v.Set(reflect.ValueOf(m.submittable))
   115  
   116  	for i := 0; i < v.NumField(); i++ {
   117  		if !v.Field(i).CanSet() {
   118  			continue
   119  		}
   120  		if _, ok := v.Field(i).Interface().(Button); !ok {
   121  			panic("both exported fields must be of the type form.Button")
   122  		}
   123  		count++
   124  	}
   125  	if count != 2 {
   126  		panic("modal form must have exactly two exported fields of the type form.Button")
   127  	}
   128  }