package editform
import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/aerogo/api"
"github.com/animenotifier/arn"
"github.com/animenotifier/notify.moe/components"
"github.com/animenotifier/notify.moe/utils"
)
// Render renders a generic editing UI for any kind of datatype that has an ID.
func Render(obj interface{}, title string, user *arn.User) string {
t := reflect.TypeOf(obj).Elem()
v := reflect.ValueOf(obj).Elem()
id := findMainID(t, v)
lowerCaseTypeName := strings.ToLower(t.Name())
endpoint := `/api/` + lowerCaseTypeName + `/` + id.String()
var b strings.Builder
b.WriteString(`
")
return b.String()
}
// RenderObject renders the UI for the object into the bytes buffer and appends an ID prefix for all API requests.
// The ID prefix should either be empty or end with a dot character.
func RenderObject(b *strings.Builder, obj interface{}, idPrefix string) {
t := reflect.TypeOf(obj)
v := reflect.ValueOf(obj)
if t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
// Fields
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
RenderField(b, &v, field, idPrefix)
}
}
// RenderField ...
func RenderField(b *strings.Builder, v *reflect.Value, field reflect.StructField, idPrefix string) {
fieldValue := reflect.Indirect(v.FieldByName(field.Name))
// Embedded fields
if field.Anonymous {
// If it's exported, render it normally.
if field.PkgPath == "" {
RenderObject(b, fieldValue.Interface(), idPrefix)
}
// If it's an unexported struct, we try to access the field names
// given to us from the anonymous struct on the actual object.
if field.Type.Kind() == reflect.Struct {
for i := 0; i < field.Type.NumField(); i++ {
f := field.Type.Field(i)
RenderField(b, v, f, idPrefix)
}
}
return
}
if field.Tag.Get("editable") != "true" {
return
}
b.WriteString("")
defer b.WriteString("
")
fieldType := field.Type.String()
// String
if fieldType == "string" {
renderStringField(b, v, field, idPrefix, fieldValue)
return
}
// Int
if fieldType == "int" {
b.WriteString(components.InputNumber(idPrefix+field.Name, float64(fieldValue.Int()), field.Name, field.Tag.Get("tooltip"), "", "", "1"))
return
}
// Float
if fieldType == "float64" {
b.WriteString(components.InputNumber(idPrefix+field.Name, fieldValue.Float(), field.Name, field.Tag.Get("tooltip"), "", "", ""))
return
}
// Bool
if fieldType == "bool" {
if field.Name == "IsDraft" {
return
}
b.WriteString(components.InputBool(idPrefix+field.Name, fieldValue.Bool(), field.Name, field.Tag.Get("tooltip")))
return
}
// Array of strings
if fieldType == "[]string" {
b.WriteString(components.InputTags(idPrefix+field.Name, fieldValue.Interface().([]string), field.Name, field.Tag.Get("tooltip")))
return
}
// Any kind of array
if strings.HasPrefix(fieldType, "[]") {
renderSliceField(b, v, field, idPrefix, fieldType, fieldValue)
return
}
// Any custom field type will be recursively rendered via another RenderObject call
b.WriteString(``)
}
// String field
func renderStringField(b *strings.Builder, v *reflect.Value, field reflect.StructField, idPrefix string, fieldValue reflect.Value) {
idType := field.Tag.Get("idType")
// Try to infer the ID type by the field name
if idType == "" {
switch field.Name {
case "AnimeID":
idType = "Anime"
case "CharacterID":
idType = "Character"
}
}
showPreview := idType != "" && fieldValue.String() != ""
if showPreview {
b.WriteString("")
}
}
// Slice field
func renderSliceField(b *strings.Builder, v *reflect.Value, field reflect.StructField, idPrefix string, fieldType string, fieldValue reflect.Value) {
b.WriteString(``)
}
// findMainID finds the main ID of the object.
func findMainID(t reflect.Type, v reflect.Value) reflect.Value {
idField := v.FieldByName("ID")
if idField.IsValid() {
return reflect.Indirect(idField)
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Tag.Get("mainID") == "true" {
return reflect.Indirect(v.Field(i))
}
}
panic("Type " + t.Name() + " doesn't have a main ID!")
}