This commit is contained in:
Robert Janetzko 2022-04-16 18:34:19 +00:00
parent 9d4af37c22
commit 71cb14c0cd
34 changed files with 2749 additions and 663 deletions

View file

@ -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"
}

View file

@ -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
}

View 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 ""
}

View 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)
}

View file

@ -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
}

View file

@ -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

View file

@ -0,0 +1,7 @@
{{template "layout.html" .}}
{{define "title"}}<h1>{{.Title}}</h1>{{end}}
{{define "content"}}
<h1>HF</h1>
{{end}}

View 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>

View file

@ -0,0 +1 @@
<a href="/entity/{{ .Id }}">{{ title .Name }}</a>

30
backend/templates/hf.html Normal file
View 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 }}

View file

@ -0,0 +1,5 @@
the
{{ .Race }}
{{ check .Deity "deity"}}
{{ check .Force "force"}}
<a href="/hf/{{ .Id }}">{{ title .Name }}</a>

View 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>

View 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)
}

View file

@ -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({

View file

@ -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,

View file

@ -0,0 +1 @@
<p>event-list works!</p>

View file

@ -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();
});
});

View file

@ -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 {
}
}

View file

@ -0,0 +1 @@
<a [routerLink]="['/entity', data?.id]">{{data?.name}}</a>

View file

@ -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();
});
});

View file

@ -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 {
}
}

View file

@ -0,0 +1 @@
<a [routerLink]="['/hf', data?.id]">{{data?.name}}</a>

View file

@ -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();
});
});

View 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 {
}
}

View file

@ -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')!);
}
}

View 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>

View 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();
});
});

View 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