frontend
This commit is contained in:
parent
9d4af37c22
commit
71cb14c0cd
34 changed files with 2749 additions and 663 deletions
|
@ -20,7 +20,15 @@ func Generate() error {
|
|||
return err
|
||||
}
|
||||
|
||||
return generateCode(m)
|
||||
if err := generateBackendCode(m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := generateFrontendCode(m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var allowedTyped = map[string]bool{
|
||||
|
@ -130,6 +138,8 @@ func createMetadata(a *AnalyzeData) (*Metadata, error) {
|
|||
field.Type = "object"
|
||||
el := typeNames[f]
|
||||
field.ElementType = &el
|
||||
} else if !a.Fields[f].NoBool {
|
||||
field.Type = "bool"
|
||||
} else if a.Fields[f].IsString {
|
||||
field.Type = "string"
|
||||
}
|
||||
|
|
|
@ -1,16 +1,8 @@
|
|||
package df
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"text/template"
|
||||
|
||||
"github.com/iancoleman/strcase"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/util"
|
||||
)
|
||||
|
||||
|
@ -40,103 +32,6 @@ type Field struct {
|
|||
Plus bool
|
||||
}
|
||||
|
||||
func (f Field) TypeLine() string {
|
||||
n := f.Name
|
||||
|
||||
if n == "Id" || n == "Name" {
|
||||
n = n + "_"
|
||||
}
|
||||
|
||||
m := ""
|
||||
if f.Multiple {
|
||||
m = "[]"
|
||||
}
|
||||
t := f.Type
|
||||
if f.Type == "array" {
|
||||
t = "[]*" + *f.ElementType
|
||||
}
|
||||
if f.Type == "map" {
|
||||
t = "map[int]*" + *f.ElementType
|
||||
}
|
||||
if f.Type == "object" {
|
||||
t = "*" + *f.ElementType
|
||||
}
|
||||
j := fmt.Sprintf("`json:\"%s\" legend:\"%s\"`", strcase.ToLowerCamel(f.Name), f.Legend)
|
||||
return fmt.Sprintf("%s %s%s %s", n, m, t, j)
|
||||
}
|
||||
|
||||
func (f Field) Init(plus bool) string {
|
||||
if !plus && f.Type == "map" {
|
||||
return fmt.Sprintf("obj.%s = make(map[int]*%s)", f.Name, *f.ElementType)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f Field) StartAction(plus bool) string {
|
||||
n := f.Name
|
||||
|
||||
if n == "Id" || n == "Name" {
|
||||
n = n + "_"
|
||||
}
|
||||
|
||||
if f.Type == "object" {
|
||||
p := fmt.Sprintf("v, _ := parse%s(d, &t)", *f.ElementType)
|
||||
if !f.Multiple {
|
||||
return fmt.Sprintf("%s\nobj.%s = v", p, n)
|
||||
} else {
|
||||
return fmt.Sprintf("%s\nobj.%s = append(obj.%s, v)", p, n, n)
|
||||
}
|
||||
}
|
||||
|
||||
if f.Type == "array" || f.Type == "map" {
|
||||
gen := fmt.Sprintf("parse%s", *f.ElementType)
|
||||
|
||||
if f.Type == "array" {
|
||||
return fmt.Sprintf("parseArray(d, &obj.%s, %s)", f.Name, gen)
|
||||
}
|
||||
|
||||
if f.Type == "map" {
|
||||
if !plus {
|
||||
return fmt.Sprintf("parseMap(d, &obj.%s, %s)", f.Name, gen)
|
||||
} else {
|
||||
gen = fmt.Sprintf("parse%sPlus", *f.ElementType)
|
||||
return fmt.Sprintf("parseMapPlus(d, &obj.%s, %s)", f.Name, gen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if f.Type == "int" || f.Type == "string" {
|
||||
return "data = nil"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f Field) EndAction() string {
|
||||
n := f.Name
|
||||
|
||||
if n == "Id" || n == "Name" {
|
||||
n = n + "_"
|
||||
}
|
||||
|
||||
if !f.Multiple {
|
||||
if f.Type == "int" {
|
||||
return fmt.Sprintf("obj.%s = n(data)", n)
|
||||
} else if f.Type == "string" {
|
||||
return fmt.Sprintf("obj.%s = string(data)", n)
|
||||
}
|
||||
} else {
|
||||
if f.Type == "int" {
|
||||
return fmt.Sprintf("obj.%s = append(obj.%s, n(data))", n, n)
|
||||
} else if f.Type == "string" {
|
||||
return fmt.Sprintf("obj.%s = append(obj.%s, string(data))", n, n)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f Field) Active(plus bool) bool {
|
||||
if plus && f.Plus {
|
||||
return true
|
||||
|
@ -187,164 +82,3 @@ func (f Object) ActiveSubTypes(plus bool) []*ActiveSubType {
|
|||
|
||||
return list
|
||||
}
|
||||
|
||||
var packageTemplate = template.Must(template.New("").Parse(`// Code generated by legendsbrowser; DO NOT EDIT.
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"strconv"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
{{- range $name, $obj := $.Objects }}
|
||||
type {{ $obj.Name }} struct {
|
||||
{{- range $fname, $field := $obj.Fields }}
|
||||
{{- if not (and (eq $fname "type") (not (not $obj.SubTypes))) }}
|
||||
{{ $field.TypeLine }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if not (not $obj.SubTypes) }}
|
||||
Details any
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
{{- if $obj.Id }}
|
||||
func (x *{{ $obj.Name }}) Id() int { return x.Id_ }
|
||||
{{- end }}
|
||||
{{- if $obj.Named }}
|
||||
func (x *{{ $obj.Name }}) Name() string { return x.Name_ }
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
// Parser
|
||||
|
||||
func n(d []byte) int {
|
||||
v, _ := strconv.Atoi(string(d))
|
||||
return v
|
||||
}
|
||||
|
||||
{{- range $name, $obj := $.Objects }}
|
||||
{{- range $plus := $.Modes }}
|
||||
func parse{{ $obj.Name }}{{ if $plus }}Plus{{ end }}(d *xml.Decoder, start *xml.StartElement{{ if $plus }}, obj *{{ $obj.Name }}{{ end }}) (*{{ $obj.Name }}, error) {
|
||||
var (
|
||||
{{- if not $plus }}
|
||||
obj = &{{ $obj.Name }}{}
|
||||
{{- end }}
|
||||
data []byte
|
||||
)
|
||||
{{- if $plus }}
|
||||
if obj == nil {
|
||||
obj = &{{ $obj.Name }}{}
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
{{- range $fname, $field := $obj.Fields }}
|
||||
{{ $field.Init $plus }}
|
||||
{{- end }}
|
||||
|
||||
for {
|
||||
tok, err := d.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch t := tok.(type) {
|
||||
case xml.StartElement:
|
||||
switch t.Name.Local {
|
||||
{{- range $fname, $field := $obj.Fields }}
|
||||
{{- if $field.Active $plus }}
|
||||
case "{{ $fname }}":
|
||||
{{ $field.StartAction $plus }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
default:
|
||||
// fmt.Println("unknown field", t.Name.Local)
|
||||
d.Skip()
|
||||
}
|
||||
|
||||
case xml.CharData:
|
||||
data = append(data, t...)
|
||||
|
||||
case xml.EndElement:
|
||||
if t.Name.Local == start.Name.Local {
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
switch t.Name.Local {
|
||||
{{- range $fname, $field := $obj.Fields }}{{- if $field.Active $plus }}
|
||||
case "{{ $fname }}":
|
||||
{{- if and (eq $fname "type") (not (not $obj.SubTypes)) }}
|
||||
|
||||
var err error
|
||||
switch string(data) {
|
||||
{{- range $sub := ($obj.ActiveSubTypes $plus) }}
|
||||
case "{{ $sub.Case }}":
|
||||
{{- if eq 1 (len $sub.Options) }}
|
||||
{{- if not $plus }}
|
||||
obj.Details, err = parse{{ $sub.Name }}(d, start)
|
||||
{{- else }}
|
||||
obj.Details, err = parse{{ $sub.Name }}Plus(d, start, obj.Details.(*{{ $sub.Name }}))
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
switch details := obj.Details.(type) {
|
||||
{{- range $opt := $sub.Options }}
|
||||
case *{{ $opt}}:
|
||||
obj.Details, err = parse{{ $opt }}Plus(d, start, details)
|
||||
{{- end }}
|
||||
default:
|
||||
fmt.Println("unknown subtype option", obj.Details)
|
||||
d.Skip()
|
||||
}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
default:
|
||||
d.Skip()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj, nil
|
||||
|
||||
{{- else }}
|
||||
{{ $field.EndAction }}
|
||||
{{- end }}
|
||||
{{- end }}{{- end }}
|
||||
default:
|
||||
// fmt.Println("unknown field", t.Name.Local)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
`))
|
||||
|
||||
func generateCode(objects *Metadata) error {
|
||||
file, _ := json.MarshalIndent(objects, "", " ")
|
||||
_ = ioutil.WriteFile("model.json", file, 0644)
|
||||
|
||||
f, err := os.Create("../backend/model/model.go")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = packageTemplate.Execute(&buf, struct {
|
||||
Objects *Metadata
|
||||
Modes []bool
|
||||
}{
|
||||
Objects: objects,
|
||||
Modes: []bool{false, true},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
fmt.Println("WARN: could not format source", err)
|
||||
p = buf.Bytes()
|
||||
}
|
||||
_, err = f.Write(p)
|
||||
return err
|
||||
}
|
||||
|
|
273
analyze/df/generate_backend.go
Normal file
273
analyze/df/generate_backend.go
Normal file
|
@ -0,0 +1,273 @@
|
|||
package df
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/iancoleman/strcase"
|
||||
)
|
||||
|
||||
var backendTemplate = template.Must(template.New("").Parse(`// Code generated by legendsbrowser; DO NOT EDIT.
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"strconv"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
{{- range $name, $obj := $.Objects }}
|
||||
type {{ $obj.Name }} struct {
|
||||
{{- range $fname, $field := $obj.Fields }}
|
||||
{{- if not (and (eq $fname "type") (not (not $obj.SubTypes))) }}
|
||||
{{ $field.TypeLine }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if not (not $obj.SubTypes) }}
|
||||
Details any
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
{{- if $obj.Id }}
|
||||
func (x *{{ $obj.Name }}) Id() int { return x.Id_ }
|
||||
{{- end }}
|
||||
{{- if $obj.Named }}
|
||||
func (x *{{ $obj.Name }}) Name() string { return x.Name_ }
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
// Parser
|
||||
|
||||
func n(d []byte) int {
|
||||
v, _ := strconv.Atoi(string(d))
|
||||
return v
|
||||
}
|
||||
|
||||
{{- range $name, $obj := $.Objects }}
|
||||
{{- range $plus := $.Modes }}
|
||||
func parse{{ $obj.Name }}{{ if $plus }}Plus{{ end }}(d *xml.Decoder, start *xml.StartElement{{ if $plus }}, obj *{{ $obj.Name }}{{ end }}) (*{{ $obj.Name }}, error) {
|
||||
var (
|
||||
{{- if not $plus }}
|
||||
obj = &{{ $obj.Name }}{}
|
||||
{{- end }}
|
||||
data []byte
|
||||
)
|
||||
{{- if $plus }}
|
||||
if obj == nil {
|
||||
obj = &{{ $obj.Name }}{}
|
||||
}
|
||||
{{- end }}
|
||||
|
||||
{{- range $fname, $field := $obj.Fields }}
|
||||
{{ $field.Init $plus }}
|
||||
{{- end }}
|
||||
|
||||
for {
|
||||
tok, err := d.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch t := tok.(type) {
|
||||
case xml.StartElement:
|
||||
switch t.Name.Local {
|
||||
{{- range $fname, $field := $obj.Fields }}
|
||||
{{- if $field.Active $plus }}
|
||||
case "{{ $fname }}":
|
||||
{{ $field.StartAction $plus }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
default:
|
||||
// fmt.Println("unknown field", t.Name.Local)
|
||||
d.Skip()
|
||||
}
|
||||
|
||||
case xml.CharData:
|
||||
data = append(data, t...)
|
||||
|
||||
case xml.EndElement:
|
||||
if t.Name.Local == start.Name.Local {
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
switch t.Name.Local {
|
||||
{{- range $fname, $field := $obj.Fields }}{{- if $field.Active $plus }}
|
||||
case "{{ $fname }}":
|
||||
{{- if and (eq $fname "type") (not (not $obj.SubTypes)) }}
|
||||
|
||||
var err error
|
||||
switch string(data) {
|
||||
{{- range $sub := ($obj.ActiveSubTypes $plus) }}
|
||||
case "{{ $sub.Case }}":
|
||||
{{- if eq 1 (len $sub.Options) }}
|
||||
{{- if not $plus }}
|
||||
obj.Details, err = parse{{ $sub.Name }}(d, start)
|
||||
{{- else }}
|
||||
obj.Details, err = parse{{ $sub.Name }}Plus(d, start, obj.Details.(*{{ $sub.Name }}))
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
switch details := obj.Details.(type) {
|
||||
{{- range $opt := $sub.Options }}
|
||||
case *{{ $opt}}:
|
||||
obj.Details, err = parse{{ $opt }}Plus(d, start, details)
|
||||
{{- end }}
|
||||
default:
|
||||
fmt.Println("unknown subtype option", obj.Details)
|
||||
d.Skip()
|
||||
}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
default:
|
||||
d.Skip()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj, nil
|
||||
|
||||
{{- else }}
|
||||
{{ $field.EndAction }}
|
||||
{{- end }}
|
||||
{{- end }}{{- end }}
|
||||
default:
|
||||
// fmt.Println("unknown field", t.Name.Local)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
`))
|
||||
|
||||
func generateBackendCode(objects *Metadata) error {
|
||||
file, _ := json.MarshalIndent(objects, "", " ")
|
||||
_ = ioutil.WriteFile("model.json", file, 0644)
|
||||
|
||||
f, err := os.Create("../backend/model/model.go")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = backendTemplate.Execute(&buf, struct {
|
||||
Objects *Metadata
|
||||
Modes []bool
|
||||
}{
|
||||
Objects: objects,
|
||||
Modes: []bool{false, true},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
fmt.Println("WARN: could not format source", err)
|
||||
p = buf.Bytes()
|
||||
}
|
||||
_, err = f.Write(p)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f Field) TypeLine() string {
|
||||
n := f.Name
|
||||
|
||||
if n == "Id" || n == "Name" {
|
||||
n = n + "_"
|
||||
}
|
||||
|
||||
m := ""
|
||||
if f.Multiple {
|
||||
m = "[]"
|
||||
}
|
||||
t := f.Type
|
||||
if f.Type == "array" {
|
||||
t = "[]*" + *f.ElementType
|
||||
}
|
||||
if f.Type == "map" {
|
||||
t = "map[int]*" + *f.ElementType
|
||||
}
|
||||
if f.Type == "object" {
|
||||
t = "*" + *f.ElementType
|
||||
}
|
||||
j := fmt.Sprintf("`json:\"%s\" legend:\"%s\"`", strcase.ToLowerCamel(f.Name), f.Legend)
|
||||
return fmt.Sprintf("%s %s%s %s", n, m, t, j)
|
||||
}
|
||||
|
||||
func (f Field) Init(plus bool) string {
|
||||
if !plus && f.Type == "map" {
|
||||
return fmt.Sprintf("obj.%s = make(map[int]*%s)", f.Name, *f.ElementType)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f Field) StartAction(plus bool) string {
|
||||
n := f.Name
|
||||
|
||||
if n == "Id" || n == "Name" {
|
||||
n = n + "_"
|
||||
}
|
||||
|
||||
if f.Type == "object" {
|
||||
p := fmt.Sprintf("v, _ := parse%s(d, &t)", *f.ElementType)
|
||||
if !f.Multiple {
|
||||
return fmt.Sprintf("%s\nobj.%s = v", p, n)
|
||||
} else {
|
||||
return fmt.Sprintf("%s\nobj.%s = append(obj.%s, v)", p, n, n)
|
||||
}
|
||||
}
|
||||
|
||||
if f.Type == "array" || f.Type == "map" {
|
||||
gen := fmt.Sprintf("parse%s", *f.ElementType)
|
||||
|
||||
if f.Type == "array" {
|
||||
return fmt.Sprintf("parseArray(d, &obj.%s, %s)", f.Name, gen)
|
||||
}
|
||||
|
||||
if f.Type == "map" {
|
||||
if !plus {
|
||||
return fmt.Sprintf("parseMap(d, &obj.%s, %s)", f.Name, gen)
|
||||
} else {
|
||||
gen = fmt.Sprintf("parse%sPlus", *f.ElementType)
|
||||
return fmt.Sprintf("parseMapPlus(d, &obj.%s, %s)", f.Name, gen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if f.Type == "int" || f.Type == "string" || f.Type == "bool" {
|
||||
return "data = nil"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f Field) EndAction() string {
|
||||
n := f.Name
|
||||
|
||||
if n == "Id" || n == "Name" {
|
||||
n = n + "_"
|
||||
}
|
||||
|
||||
if !f.Multiple {
|
||||
if f.Type == "int" {
|
||||
return fmt.Sprintf("obj.%s = n(data)", n)
|
||||
} else if f.Type == "string" {
|
||||
return fmt.Sprintf("obj.%s = string(data)", n)
|
||||
} else if f.Type == "bool" {
|
||||
return fmt.Sprintf("obj.%s = true", n)
|
||||
}
|
||||
} else {
|
||||
if f.Type == "int" {
|
||||
return fmt.Sprintf("obj.%s = append(obj.%s, n(data))", n, n)
|
||||
} else if f.Type == "string" {
|
||||
return fmt.Sprintf("obj.%s = append(obj.%s, string(data))", n, n)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
74
analyze/df/generate_frontend.go
Normal file
74
analyze/df/generate_frontend.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package df
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/iancoleman/strcase"
|
||||
)
|
||||
|
||||
var frontendTemplate = template.Must(template.New("").Parse(`// Code generated by legendsbrowser; DO NOT EDIT.
|
||||
{{- range $name, $obj := $.Objects }}
|
||||
export interface {{ $obj.Name }} {
|
||||
{{- range $fname, $field := $obj.Fields }}
|
||||
{{- if not (and (eq $fname "type") (not (not $obj.SubTypes))) }}
|
||||
{{ $field.JsonTypeLine }};
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if not (not $obj.SubTypes) }}
|
||||
Details: any;
|
||||
{{- end }}
|
||||
}
|
||||
{{- end }}
|
||||
`))
|
||||
|
||||
func generateFrontendCode(objects *Metadata) error {
|
||||
file, _ := json.MarshalIndent(objects, "", " ")
|
||||
_ = ioutil.WriteFile("model.json", file, 0644)
|
||||
|
||||
f, err := os.Create("../frontend/src/app/types.ts")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = frontendTemplate.Execute(&buf, struct {
|
||||
Objects *Metadata
|
||||
Modes []bool
|
||||
}{
|
||||
Objects: objects,
|
||||
Modes: []bool{false, true},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = f.Write(buf.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
func (f Field) JsonTypeLine() string {
|
||||
m := ""
|
||||
if f.Multiple {
|
||||
m = "[]"
|
||||
}
|
||||
t := f.Type
|
||||
if f.Type == "int" {
|
||||
t = "number"
|
||||
}
|
||||
if f.Type == "array" {
|
||||
t = *f.ElementType + "[]"
|
||||
}
|
||||
if f.Type == "map" {
|
||||
t = "{ [key:number]:" + *f.ElementType + "; }"
|
||||
}
|
||||
if f.Type == "object" {
|
||||
t = *f.ElementType
|
||||
}
|
||||
return fmt.Sprintf("%s: %s%s", strcase.ToLowerCamel(f.Name), t, m)
|
||||
}
|
|
@ -34,6 +34,7 @@ func AnalyzeStructure(filex string) error {
|
|||
|
||||
type FieldData struct {
|
||||
IsString bool
|
||||
NoBool bool
|
||||
Multiple bool
|
||||
Base bool
|
||||
Plus bool
|
||||
|
@ -253,6 +254,9 @@ Loop:
|
|||
if _, err := strconv.Atoi(s); err != nil {
|
||||
a.GetField(strings.Join(path, PATH_SEPARATOR)).IsString = true
|
||||
}
|
||||
if len(s) > 0 {
|
||||
a.GetField(strings.Join(path, PATH_SEPARATOR)).NoBool = true
|
||||
}
|
||||
return &Value{Name: t.Name.Local, Value: s}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -2,18 +2,23 @@ package main
|
|||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/profile"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/model"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/server"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/templates"
|
||||
)
|
||||
|
||||
var world *model.DfWorld
|
||||
|
@ -27,6 +32,39 @@ func main() {
|
|||
|
||||
router := mux.NewRouter().StrictSlash(true)
|
||||
|
||||
functions := template.FuncMap{
|
||||
"json": func(obj any) string {
|
||||
b, err := json.MarshalIndent(obj, "", " ")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
},
|
||||
"check": func(condition bool, v any) any {
|
||||
if condition {
|
||||
return v
|
||||
}
|
||||
return nil
|
||||
},
|
||||
"title": func(input string) string {
|
||||
words := strings.Split(input, " ")
|
||||
smallwords := " a an on the to of "
|
||||
|
||||
for index, word := range words {
|
||||
if strings.Contains(smallwords, " "+word+" ") && index > 0 {
|
||||
words[index] = word
|
||||
} else {
|
||||
words[index] = strings.Title(word)
|
||||
}
|
||||
}
|
||||
return strings.Join(words, " ")
|
||||
},
|
||||
"getHf": func(id int) *model.HistoricalFigure { return world.HistoricalFigures[id] },
|
||||
"getEntity": func(id int) *model.Entity { return world.Entities[id] },
|
||||
}
|
||||
t := templates.New(functions)
|
||||
|
||||
if len(*f) > 0 {
|
||||
defer profile.Start(profile.MemProfile).Stop()
|
||||
go func() {
|
||||
|
@ -77,6 +115,9 @@ func main() {
|
|||
server.RegisterResource(router, "musicalForm", world.MusicalForms)
|
||||
server.RegisterResource(router, "poeticForm", world.PoeticForms)
|
||||
// server.RegisterResource(router, "written", world.WrittenContents)
|
||||
|
||||
RegisterPage(router, "/hf/{id}", t, "hf.html", func(id int) any { return world.HistoricalFigures[id] })
|
||||
|
||||
}
|
||||
|
||||
spa := spaHandler{staticFS: frontend, staticPath: "resources/frontend", indexPath: "index.html"}
|
||||
|
@ -87,6 +128,23 @@ func main() {
|
|||
|
||||
}
|
||||
|
||||
func RegisterPage(router *mux.Router, path string, templates *templates.Template, template string, accessor func(int) any) {
|
||||
get := func(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.Atoi(mux.Vars(r)["id"])
|
||||
if err != nil {
|
||||
fmt.Fprintln(w, err)
|
||||
fmt.Println(err)
|
||||
}
|
||||
err = templates.Render(w, template, accessor(id))
|
||||
if err != nil {
|
||||
fmt.Fprintln(w, err)
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
router.HandleFunc(path, get).Methods("GET")
|
||||
}
|
||||
|
||||
type spaHandler struct {
|
||||
staticFS embed.FS
|
||||
staticPath string
|
||||
|
|
File diff suppressed because it is too large
Load diff
7
backend/resources/templates/hf.html
Normal file
7
backend/resources/templates/hf.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
{{template "layout.html" .}}
|
||||
|
||||
{{define "title"}}<h1>{{.Title}}</h1>{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<h1>HF</h1>
|
||||
{{end}}
|
15
backend/resources/templates/layout.html
Normal file
15
backend/resources/templates/layout.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{block "title" .}}{{end}}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="main">{{block "content" .}}{{end}}</div>
|
||||
</body>
|
||||
|
||||
</html>
|
1
backend/templates/entityLink.html
Normal file
1
backend/templates/entityLink.html
Normal file
|
@ -0,0 +1 @@
|
|||
<a href="/entity/{{ .Id }}">{{ title .Name }}</a>
|
30
backend/templates/hf.html
Normal file
30
backend/templates/hf.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
{{template "layout.html" .}}
|
||||
|
||||
{{define "title"}}{{ title .Name }}{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<h1>{{ title .Name }}</h1>
|
||||
{{ .Race }} (*{{ .BirthYear }}{{ if ge .DeathYear 0 }} †{{ .DeathYear }}{{ end }})
|
||||
{{end}}
|
||||
|
||||
{{- if ne 0 (len .HfLink) }}
|
||||
<h3>Related Figures</h3>
|
||||
<ul>
|
||||
{{- range $i := .HfLink }}
|
||||
<li>
|
||||
{{ template "hfLink.html" (getHf $i.Hfid) }} ({{ $i.LinkType }})
|
||||
</li>
|
||||
{{- end }}
|
||||
</ul>
|
||||
{{- end }}
|
||||
|
||||
{{- if ne 0 (len .EntityLink) }}
|
||||
<h3>Related Entities</h3>
|
||||
<ul>
|
||||
{{- range $i := .EntityLink }}
|
||||
<li>
|
||||
{{ template "entityLink.html" (getEntity $i.EntityId) }} ({{ $i.LinkType }})
|
||||
</li>
|
||||
{{- end }}
|
||||
</ul>
|
||||
{{- end }}
|
5
backend/templates/hfLink.html
Normal file
5
backend/templates/hfLink.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
the
|
||||
{{ .Race }}
|
||||
{{ check .Deity "deity"}}
|
||||
{{ check .Force "force"}}
|
||||
<a href="/hf/{{ .Id }}">{{ title .Name }}</a>
|
15
backend/templates/layout.html
Normal file
15
backend/templates/layout.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{block "title" .}}{{end}}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="main">{{block "content" .}}{{end}}</div>
|
||||
</body>
|
||||
|
||||
</html>
|
40
backend/templates/templates.go
Normal file
40
backend/templates/templates.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"html/template"
|
||||
"io"
|
||||
)
|
||||
|
||||
//go:embed *.html
|
||||
var templateFS embed.FS
|
||||
|
||||
type Template struct {
|
||||
funcMap template.FuncMap
|
||||
templates *template.Template
|
||||
}
|
||||
|
||||
func New(funcMap template.FuncMap) *Template {
|
||||
templates := template.Must(template.New("").Funcs(funcMap).ParseFS(templateFS, "*.html"))
|
||||
return &Template{
|
||||
funcMap: funcMap,
|
||||
templates: templates,
|
||||
}
|
||||
}
|
||||
|
||||
func NewDebug(funcMap template.FuncMap) *Template {
|
||||
templates := template.Must(template.New("").Funcs(funcMap).ParseGlob("templates/*.html"))
|
||||
return &Template{
|
||||
funcMap: funcMap,
|
||||
templates: templates,
|
||||
}
|
||||
}
|
||||
|
||||
var DebugTemplates = true
|
||||
|
||||
func (t *Template) Render(w io.Writer, name string, data interface{}) error {
|
||||
if DebugTemplates {
|
||||
return NewDebug(t.funcMap).templates.ExecuteTemplate(w, name, data)
|
||||
}
|
||||
return t.templates.ExecuteTemplate(w, name, data)
|
||||
}
|
|
@ -3,10 +3,12 @@ import { RouterModule, Routes } from '@angular/router';
|
|||
import { EntitiesResolver, EntityResolver } from './entity.service';
|
||||
import { CivilizationsComponent } from './pages/civilizations/civilizations.component';
|
||||
import { EntityComponent } from './pages/entity/entity.component';
|
||||
import { HfComponent } from './pages/hf/hf.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: CivilizationsComponent, resolve: { civilizations: EntitiesResolver } },
|
||||
{ path: 'entity/:id', component: EntityComponent, resolve: { entity: EntityResolver } },
|
||||
{ path: 'entity/:id', component: EntityComponent, resolve: { entity: EntityResolver }, data: { resource: "entity" } },
|
||||
{ path: 'hf/:id', component: HfComponent, resolve: { data: EntityResolver }, data: { resource: "hf" } },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -5,12 +5,20 @@ import { AppRoutingModule } from './app-routing.module';
|
|||
import { AppComponent } from './app.component';
|
||||
import { CivilizationsComponent } from './pages/civilizations/civilizations.component';
|
||||
import { EntityComponent } from './pages/entity/entity.component';
|
||||
import { HfComponent } from './pages/hf/hf.component';
|
||||
import { EventListComponent } from './components/event-list/event-list.component';
|
||||
import { InlineEntityComponent } from './components/inline-entity/inline-entity.component';
|
||||
import { InlineHfComponent } from './components/inline-hf/inline-hf.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
CivilizationsComponent,
|
||||
EntityComponent
|
||||
EntityComponent,
|
||||
HfComponent,
|
||||
EventListComponent,
|
||||
InlineEntityComponent,
|
||||
InlineHfComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<p>event-list works!</p>
|
|
@ -0,0 +1,25 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EventListComponent } from './event-list.component';
|
||||
|
||||
describe('EventListComponent', () => {
|
||||
let component: EventListComponent;
|
||||
let fixture: ComponentFixture<EventListComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ EventListComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EventListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-event-list',
|
||||
templateUrl: './event-list.component.html',
|
||||
styleUrls: ['./event-list.component.scss']
|
||||
})
|
||||
export class EventListComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<a [routerLink]="['/entity', data?.id]">{{data?.name}}</a>
|
|
@ -0,0 +1,25 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InlineEntityComponent } from './inline-entity.component';
|
||||
|
||||
describe('InlineEntityComponent', () => {
|
||||
let component: InlineEntityComponent;
|
||||
let fixture: ComponentFixture<InlineEntityComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ InlineEntityComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InlineEntityComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { EntityService } from 'src/app/entity.service';
|
||||
import { Entity } from 'src/app/types';
|
||||
|
||||
@Component({
|
||||
selector: 'app-inline-entity',
|
||||
templateUrl: './inline-entity.component.html',
|
||||
styleUrls: ['./inline-entity.component.scss']
|
||||
})
|
||||
export class InlineEntityComponent implements OnInit {
|
||||
|
||||
_id?: number
|
||||
data?: Entity
|
||||
|
||||
@Input()
|
||||
get id(): number | undefined {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
set id(val: number | undefined) {
|
||||
this._id = val
|
||||
if (val) {
|
||||
this.service.getOne<Entity>("entity", val).then(data => this.data = data);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
private service: EntityService
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<a [routerLink]="['/hf', data?.id]">{{data?.name}}</a>
|
|
@ -0,0 +1,25 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InlineHfComponent } from './inline-hf.component';
|
||||
|
||||
describe('InlineHfComponent', () => {
|
||||
let component: InlineHfComponent;
|
||||
let fixture: ComponentFixture<InlineHfComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ InlineHfComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InlineHfComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
34
frontend/src/app/components/inline-hf/inline-hf.component.ts
Normal file
34
frontend/src/app/components/inline-hf/inline-hf.component.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { EntityService } from 'src/app/entity.service';
|
||||
import { HistoricalFigure } from 'src/app/types';
|
||||
|
||||
@Component({
|
||||
selector: 'app-inline-hf',
|
||||
templateUrl: './inline-hf.component.html',
|
||||
styleUrls: ['./inline-hf.component.scss']
|
||||
})
|
||||
export class InlineHfComponent implements OnInit {
|
||||
|
||||
_id?: number
|
||||
data?: HistoricalFigure
|
||||
|
||||
@Input()
|
||||
get id(): number | undefined {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
set id(val: number | undefined) {
|
||||
this._id = val
|
||||
if (val) {
|
||||
this.service.getOne<HistoricalFigure>("hf", val).then(data => this.data = data);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
private service: EntityService
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
|
@ -15,8 +15,8 @@ export class EntityService {
|
|||
return firstValueFrom(this.http.get<Entity[]>("./api/entity"));
|
||||
}
|
||||
|
||||
getOne(id: string | number): Promise<Entity> {
|
||||
return firstValueFrom(this.http.get<Entity>("./api/entity/" + id));
|
||||
getOne<T>(resource: string, id: string | number): Promise<T> {
|
||||
return firstValueFrom(this.http.get<T>(`./api/${resource}/${id}`));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ export class EntityResolver implements Resolve<Entity> {
|
|||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
): Observable<Entity> | Promise<Entity> | Entity {
|
||||
return this.service.getOne(route.paramMap.get('id')!);
|
||||
console.log("R", route.data)
|
||||
return this.service.getOne(route.data['resource'], route.paramMap.get('id')!);
|
||||
}
|
||||
}
|
16
frontend/src/app/pages/hf/hf.component.html
Normal file
16
frontend/src/app/pages/hf/hf.component.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
<h1>{{data.name | titlecase}}</h1>
|
||||
<span>{{data.race}}</span> (*{{data.birthYear}}<span *ngIf="data.deathYear >= 0"> †{{data.deathYear}}</span>)
|
||||
|
||||
<h3>Related Figures</h3>
|
||||
<ul>
|
||||
<li *ngFor="let i of data.hfLink">
|
||||
<app-inline-hf [id]="i.hfid"></app-inline-hf> ({{i.linkType}})
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>Related Entities</h3>
|
||||
<ul>
|
||||
<li *ngFor="let i of data.entityLink">
|
||||
<app-inline-entity [id]="i.entityId"></app-inline-entity> ({{i.linkType}})
|
||||
</li>
|
||||
</ul>
|
0
frontend/src/app/pages/hf/hf.component.scss
Normal file
0
frontend/src/app/pages/hf/hf.component.scss
Normal file
25
frontend/src/app/pages/hf/hf.component.spec.ts
Normal file
25
frontend/src/app/pages/hf/hf.component.spec.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HfComponent } from './hf.component';
|
||||
|
||||
describe('HfComponent', () => {
|
||||
let component: HfComponent;
|
||||
let fixture: ComponentFixture<HfComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ HfComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HfComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
22
frontend/src/app/pages/hf/hf.component.ts
Normal file
22
frontend/src/app/pages/hf/hf.component.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { EntityService } from 'src/app/entity.service';
|
||||
import { HistoricalFigure } from 'src/app/types';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hf',
|
||||
templateUrl: './hf.component.html',
|
||||
styleUrls: ['./hf.component.scss']
|
||||
})
|
||||
export class HfComponent implements OnInit {
|
||||
|
||||
data!: HistoricalFigure
|
||||
|
||||
constructor(private route: ActivatedRoute, private service: EntityService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.data = this.route.snapshot.data['data'];
|
||||
this.route.params.subscribe(p => this.service.getOne<HistoricalFigure>("hf", p['id']).then(data => this.data = data));
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue