refactor server context
This commit is contained in:
parent
5aa0bc5a8d
commit
93e5ebb788
13 changed files with 900 additions and 930 deletions
3
backend/.gitignore
vendored
3
backend/.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
resources/frontend/*
|
||||
resources/frontend/*
|
||||
same.json
|
169
backend/main.go
169
backend/main.go
|
@ -4,25 +4,16 @@ import (
|
|||
"embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/profile"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/model"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/templates"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/util"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/server"
|
||||
)
|
||||
|
||||
var world *model.DfWorld
|
||||
|
||||
//go:embed static
|
||||
var static embed.FS
|
||||
|
||||
|
@ -31,34 +22,6 @@ func main() {
|
|||
p := flag.Bool("p", false, "start profiling")
|
||||
flag.Parse()
|
||||
|
||||
router := mux.NewRouter().StrictSlash(true)
|
||||
|
||||
functions := template.FuncMap{
|
||||
"json": util.Json,
|
||||
"check": func(condition bool, v any) any {
|
||||
if condition {
|
||||
return v
|
||||
}
|
||||
return nil
|
||||
},
|
||||
"title": util.Title,
|
||||
"hf": model.LinkHf,
|
||||
"getHf": func(id int) *model.HistoricalFigure { return world.HistoricalFigures[id] },
|
||||
"entity": model.LinkEntity,
|
||||
"getEntity": func(id int) *model.Entity { return world.Entities[id] },
|
||||
"site": model.LinkSite,
|
||||
"getSite": func(id int) *model.Site { return world.Sites[id] },
|
||||
"region": model.LinkRegion,
|
||||
"getRegion": func(id int) *model.Region { return world.Regions[id] },
|
||||
"events": model.NewEventList,
|
||||
"season": model.Season,
|
||||
"time": model.Time,
|
||||
"html": func(value any) template.HTML {
|
||||
return template.HTML(fmt.Sprint(value))
|
||||
},
|
||||
}
|
||||
t := templates.New(functions)
|
||||
|
||||
if len(*f) > 0 {
|
||||
if *p {
|
||||
defer profile.Start(profile.ProfilePath(".")).Stop()
|
||||
|
@ -73,137 +36,9 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
world = w
|
||||
runtime.GC()
|
||||
|
||||
RegisterResourcePage(router, "/entity/{id}", t, "entity.html", func(id int) any { return world.Entities[id] })
|
||||
RegisterResourcePage(router, "/hf/{id}", t, "hf.html", func(id int) any { return world.HistoricalFigures[id] })
|
||||
RegisterResourcePage(router, "/region/{id}", t, "region.html", func(id int) any { return world.Regions[id] })
|
||||
RegisterResourcePage(router, "/site/{id}", t, "site.html", func(id int) any { return world.Sites[id] })
|
||||
RegisterResourcePage(router, "/artifact/{id}", t, "artifact.html", func(id int) any { return world.Artifacts[id] })
|
||||
RegisterPage(router, "/events", t, "eventTypes.html", func(p Parms) any { return allEventTypes() })
|
||||
RegisterPage(router, "/events/{type}", t, "eventType.html", func(p Parms) any { return eventsOfType(p["type"]) })
|
||||
}
|
||||
|
||||
spa := spaHandler{staticFS: static, staticPath: "static", indexPath: "index.html"}
|
||||
router.PathPrefix("/").Handler(spa)
|
||||
|
||||
openbrowser("http://localhost:8080")
|
||||
|
||||
fmt.Println("Serving at :8080")
|
||||
http.ListenAndServe(":8080", router)
|
||||
}
|
||||
|
||||
func openbrowser(url string) {
|
||||
var err error
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
err = exec.Command("xdg-open", url).Start()
|
||||
case "windows":
|
||||
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||
case "darwin":
|
||||
err = exec.Command("open", url).Start()
|
||||
default:
|
||||
err = fmt.Errorf("unsupported platform")
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("navigate to http://localhost:8080 in your browser")
|
||||
server.StartServer(w, static)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func allEventTypes() []string {
|
||||
types := make(map[string]bool)
|
||||
for _, e := range world.HistoricalEvents {
|
||||
types[e.Details.Type()] = true
|
||||
}
|
||||
var list = util.Keys(types)
|
||||
sort.Strings(list)
|
||||
return list
|
||||
}
|
||||
|
||||
func eventsOfType(t string) any {
|
||||
var list []*model.HistoricalEvent
|
||||
for _, e := range world.HistoricalEvents {
|
||||
if e.Details.Type() == t {
|
||||
list = append(list, e)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(list, func(i, j int) bool { return list[i].Id_ < list[j].Id_ })
|
||||
|
||||
return struct {
|
||||
Type string
|
||||
Events []*model.HistoricalEvent
|
||||
}{
|
||||
Type: t,
|
||||
Events: list,
|
||||
}
|
||||
}
|
||||
|
||||
type Parms map[string]string
|
||||
|
||||
func RegisterPage(router *mux.Router, path string, templates *templates.Template, template string, accessor func(Parms) any) {
|
||||
get := func(w http.ResponseWriter, r *http.Request) {
|
||||
err := templates.Render(w, template, accessor(mux.Vars(r)))
|
||||
if err != nil {
|
||||
fmt.Fprintln(w, err)
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
router.HandleFunc(path, get).Methods("GET")
|
||||
}
|
||||
|
||||
func RegisterResourcePage(router *mux.Router, path string, templates *templates.Template, template string, accessor func(int) any) {
|
||||
RegisterPage(router, path, templates, template, func(params Parms) any {
|
||||
id, _ := strconv.Atoi(params["id"])
|
||||
return accessor(id)
|
||||
})
|
||||
}
|
||||
|
||||
type spaHandler struct {
|
||||
staticFS embed.FS
|
||||
staticPath string
|
||||
indexPath string
|
||||
}
|
||||
|
||||
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// get the absolute path to prevent directory traversal
|
||||
path := r.URL.Path
|
||||
// if err != nil {
|
||||
// // if we failed to get the absolute path respond with a 400 bad request and stop
|
||||
// http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
// return
|
||||
// }
|
||||
// prepend the path with the path to the static directory
|
||||
path = h.staticPath + path
|
||||
fmt.Println(r.URL, "->", path)
|
||||
|
||||
_, err := h.staticFS.Open(path)
|
||||
if os.IsNotExist(err) {
|
||||
// file does not exist, serve index.html
|
||||
fmt.Println(path)
|
||||
index, err := h.staticFS.ReadFile(h.staticPath + "/" + h.indexPath)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
w.Write(index)
|
||||
return
|
||||
} else if err != nil {
|
||||
// if we got an error (that wasn't that the file doesn't exist) stating the
|
||||
// file, return a 500 internal server error and stop
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// get the subdirectory of the static dir
|
||||
statics, err := fs.Sub(h.staticFS, h.staticPath)
|
||||
// otherwise, use http.FileServer to serve the static dir
|
||||
http.FileServer(http.FS(statics)).ServeHTTP(w, r)
|
||||
}
|
||||
|
|
273
backend/model/context.go
Normal file
273
backend/model/context.go
Normal file
|
@ -0,0 +1,273 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/iancoleman/strcase"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/util"
|
||||
)
|
||||
|
||||
type context struct {
|
||||
world *DfWorld
|
||||
hfId int
|
||||
story bool
|
||||
}
|
||||
|
||||
func (c *context) hf(id int) string {
|
||||
if c.hfId != -1 {
|
||||
if c.hfId == id {
|
||||
return c.hfShort(id)
|
||||
} else {
|
||||
return c.hfRelated(id, c.hfId)
|
||||
}
|
||||
}
|
||||
if x, ok := c.world.HistoricalFigures[id]; ok {
|
||||
return fmt.Sprintf(`the %s <a class="hf" href="/hf/%d">%s</a>`, x.Race+util.If(x.Deity, " deity", "")+util.If(x.Force, " force", ""), x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN HISTORICAL FIGURE"
|
||||
}
|
||||
|
||||
func (c *context) hfShort(id int) string {
|
||||
if x, ok := c.world.HistoricalFigures[id]; ok {
|
||||
return fmt.Sprintf(`<a class="hf" href="/hf/%d">%s</a>`, x.Id(), util.Title(x.FirstName()))
|
||||
}
|
||||
return "UNKNOWN HISTORICAL FIGURE"
|
||||
}
|
||||
|
||||
func (c *context) hfRelated(id, to int) string {
|
||||
if c.hfId != -1 {
|
||||
if c.hfId == id {
|
||||
return c.hfShort(id)
|
||||
} else {
|
||||
return c.hfRelated(id, c.hfId)
|
||||
}
|
||||
}
|
||||
if x, ok := c.world.HistoricalFigures[id]; ok {
|
||||
if t, ok := c.world.HistoricalFigures[to]; ok {
|
||||
if y, ok := util.Find(t.HfLink, func(l *HfLink) bool { return l.Hfid == id }); ok {
|
||||
return fmt.Sprintf(`%s %s <a class="hf" href="/hf/%d">%s</a>`, t.PossesivePronoun(), y.LinkType, x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf(`the %s <a class="hf" href="/hf/%d">%s</a>`, x.Race+util.If(x.Deity, " deity", "")+util.If(x.Force, " force", ""), x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN HISTORICAL FIGURE"
|
||||
}
|
||||
|
||||
func (c *context) hfList(ids []int) string {
|
||||
return andList(util.Map(ids, func(id int) string { return c.hf(id) }))
|
||||
}
|
||||
|
||||
func (c *context) hfListRelated(ids []int, to int) string {
|
||||
return andList(util.Map(ids, func(id int) string { return c.hfRelated(id, to) }))
|
||||
}
|
||||
|
||||
func (c *context) artifact(id int) string {
|
||||
if x, ok := c.world.Artifacts[id]; ok {
|
||||
return fmt.Sprintf(`<a class="artifact" href="/artifact/%d">%s</a>`, x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN ARTIFACT"
|
||||
}
|
||||
|
||||
func (c *context) entity(id int) string {
|
||||
if x, ok := c.world.Entities[id]; ok {
|
||||
return fmt.Sprintf(`<a class="entity" href="/entity/%d">%s</a>`, x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN ENTITY"
|
||||
}
|
||||
|
||||
func (c *context) entityList(ids []int) string {
|
||||
return andList(util.Map(ids, func(id int) string { return c.entity(id) }))
|
||||
}
|
||||
|
||||
func (c *context) position(entityId, positionId, hfId int) string {
|
||||
if e, ok := c.world.Entities[entityId]; ok {
|
||||
if h, ok := c.world.HistoricalFigures[hfId]; ok {
|
||||
return e.Position(positionId).GenderName(h)
|
||||
}
|
||||
}
|
||||
return "UNKNOWN POSITION"
|
||||
}
|
||||
|
||||
func (c *context) siteCiv(siteCivId, civId int) string {
|
||||
if siteCivId == civId {
|
||||
return c.entity(civId)
|
||||
}
|
||||
return util.If(siteCivId != -1, c.entity(siteCivId), "") + util.If(civId != -1 && siteCivId != -1, " of ", "") + util.If(civId != -1, c.entity(civId), "")
|
||||
}
|
||||
|
||||
func (c *context) siteStructure(siteId, structureId int, prefix string) string {
|
||||
if siteId == -1 {
|
||||
return ""
|
||||
}
|
||||
return " " + prefix + " " + util.If(structureId != -1, c.structure(siteId, structureId)+" in ", "") + c.site(siteId, "")
|
||||
}
|
||||
|
||||
func (c *context) site(id int, prefix string) string {
|
||||
if x, ok := c.world.Sites[id]; ok {
|
||||
return fmt.Sprintf(`%s <a class="site" href="/site/%d">%s</a>`, prefix, x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN SITE"
|
||||
}
|
||||
|
||||
func (c *context) structure(siteId, structureId int) string {
|
||||
if x, ok := c.world.Sites[siteId]; ok {
|
||||
if y, ok := x.Structures[structureId]; ok {
|
||||
return fmt.Sprintf(`<a class="structure" href="/site/%d/structure/%d">%s</a>`, siteId, structureId, util.Title(y.Name()))
|
||||
}
|
||||
}
|
||||
return "UNKNOWN STRUCTURE"
|
||||
}
|
||||
|
||||
func (c *context) property(siteId, propertyId int) string {
|
||||
if x, ok := c.world.Sites[siteId]; ok {
|
||||
if y, ok := x.SiteProperties[propertyId]; ok {
|
||||
if y.StructureId != -1 {
|
||||
return c.structure(siteId, y.StructureId)
|
||||
}
|
||||
return articled(y.Type.String())
|
||||
}
|
||||
}
|
||||
return "UNKNOWN PROPERTY"
|
||||
}
|
||||
|
||||
func (c *context) region(id int) string {
|
||||
if x, ok := c.world.Regions[id]; ok {
|
||||
return fmt.Sprintf(`<a class="region" href="/region/%d">%s</a>`, x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN REGION"
|
||||
}
|
||||
|
||||
func (c *context) location(siteId int, sitePrefix string, regionId int, regionPrefix string) string {
|
||||
if siteId != -1 {
|
||||
return c.site(siteId, sitePrefix)
|
||||
}
|
||||
if regionId != -1 {
|
||||
return regionPrefix + " " + c.region(regionId)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *context) place(structureId, siteId int, sitePrefix string, regionId int, regionPrefix string) string {
|
||||
if siteId != -1 {
|
||||
return c.siteStructure(siteId, structureId, sitePrefix)
|
||||
}
|
||||
if regionId != -1 {
|
||||
return regionPrefix + " " + c.region(regionId)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *context) mountain(id int) string {
|
||||
if x, ok := c.world.MountainPeaks[id]; ok {
|
||||
return fmt.Sprintf(`<a class="mountain" href="/site/%d">%s</a>`, x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN MOUNTAIN"
|
||||
}
|
||||
|
||||
func (c *context) identity(id int) string {
|
||||
if x, ok := c.world.Identities[id]; ok {
|
||||
return fmt.Sprintf(`<a class="identity" href="/region/%d">%s</a>`, x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN IDENTITY"
|
||||
}
|
||||
|
||||
func (c *context) fullIdentity(id int) string {
|
||||
if x, ok := c.world.Identities[id]; ok {
|
||||
return fmt.Sprintf(`"the %s <a class="identity" href="/region/%d">%s</a> of %s"`, x.Profession.String(), x.Id(), util.Title(x.Name()), c.entity(x.EntityId))
|
||||
}
|
||||
return "UNKNOWN IDENTITY"
|
||||
}
|
||||
|
||||
func (c *context) danceForm(id int) string {
|
||||
if x, ok := c.world.DanceForms[id]; ok {
|
||||
return fmt.Sprintf(`<a class="artform" href="/danceForm/%d">%s</a>`, id, util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN DANCE FORM"
|
||||
}
|
||||
|
||||
func (c *context) musicalForm(id int) string {
|
||||
if x, ok := c.world.MusicalForms[id]; ok {
|
||||
return fmt.Sprintf(`<a class="artform" href="/musicalForm/%d">%s</a>`, id, util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN MUSICAL FORM"
|
||||
}
|
||||
|
||||
func (c *context) poeticForm(id int) string {
|
||||
if x, ok := c.world.PoeticForms[id]; ok {
|
||||
return fmt.Sprintf(`<a class="artform" href="/poeticForm/%d">%s</a>`, id, util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN POETIC FORM"
|
||||
}
|
||||
|
||||
func (c *context) worldConstruction(id int) string {
|
||||
if x, ok := c.world.WorldConstructions[id]; ok {
|
||||
return fmt.Sprintf(`<a class="artform" href="/wc/%d">%s</a>`, id, util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN WORLD CONSTRUCTION"
|
||||
}
|
||||
|
||||
func (c *context) writtenContent(id int) string {
|
||||
if x, ok := c.world.WrittenContents[id]; ok {
|
||||
return fmt.Sprintf(`<a class="artform" href="/writtenContent/%d">%s</a>`, id, util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN WORLD CONSTRUCTION"
|
||||
}
|
||||
|
||||
func (c *context) feature(x *Feature) string {
|
||||
switch x.Type {
|
||||
case FeatureType_DancePerformance:
|
||||
return "a perfomance of " + c.danceForm(x.Reference)
|
||||
case FeatureType_Images:
|
||||
if x.Reference != -1 {
|
||||
return "images of " + c.hf(x.Reference)
|
||||
}
|
||||
return "images"
|
||||
case FeatureType_MusicalPerformance:
|
||||
return "a perfomance of " + c.musicalForm(x.Reference)
|
||||
case FeatureType_PoetryRecital:
|
||||
return "a recital of " + c.poeticForm(x.Reference)
|
||||
case FeatureType_Storytelling:
|
||||
if x.Reference != -1 {
|
||||
if e, ok := c.world.HistoricalEvents[x.Reference]; ok {
|
||||
return "a telling of the story of " + e.Details.Html(&context{story: true}) + " in " + Time(e.Year, e.Seconds72)
|
||||
}
|
||||
}
|
||||
return "a story recital"
|
||||
default:
|
||||
return strcase.ToDelimited(x.Type.String(), ' ')
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) schedule(x *Schedule) string {
|
||||
switch x.Type {
|
||||
case ScheduleType_DancePerformance:
|
||||
return "a perfomance of " + c.danceForm(x.Reference)
|
||||
case ScheduleType_MusicalPerformance:
|
||||
return "a perfomance of " + c.musicalForm(x.Reference)
|
||||
case ScheduleType_PoetryRecital:
|
||||
return "a recital of " + c.poeticForm(x.Reference)
|
||||
case ScheduleType_Storytelling:
|
||||
if x.Reference != -1 {
|
||||
if e, ok := c.world.HistoricalEvents[x.Reference]; ok {
|
||||
return "the story of " + e.Details.Html(&context{story: true}) + " in " + Time(e.Year, e.Seconds72)
|
||||
}
|
||||
}
|
||||
return "a story recital"
|
||||
default:
|
||||
return strcase.ToDelimited(x.Type.String(), ' ')
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) pronoun(id int) string {
|
||||
if x, ok := c.world.HistoricalFigures[id]; ok {
|
||||
return x.Pronoun()
|
||||
}
|
||||
return "he"
|
||||
}
|
||||
|
||||
func (c *context) posessivePronoun(id int) string {
|
||||
if x, ok := c.world.HistoricalFigures[id]; ok {
|
||||
return x.PossesivePronoun()
|
||||
}
|
||||
return "his"
|
||||
}
|
49
backend/model/eventList.go
Normal file
49
backend/model/eventList.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type HistoricalEventDetails interface {
|
||||
RelatedToEntity(int) bool
|
||||
RelatedToHf(int) bool
|
||||
RelatedToArtifact(int) bool
|
||||
RelatedToSite(int) bool
|
||||
RelatedToRegion(int) bool
|
||||
Html(*context) string
|
||||
Type() string
|
||||
}
|
||||
|
||||
type HistoricalEventCollectionDetails interface {
|
||||
}
|
||||
|
||||
type EventList struct {
|
||||
Events []*HistoricalEvent
|
||||
Context *context
|
||||
}
|
||||
|
||||
func NewEventList(world *DfWorld, obj any) *EventList {
|
||||
el := EventList{
|
||||
Context: &context{hfId: -1},
|
||||
}
|
||||
|
||||
switch x := obj.(type) {
|
||||
case *Entity:
|
||||
el.Events = world.EventsMatching(func(d HistoricalEventDetails) bool { return d.RelatedToEntity(x.Id()) })
|
||||
case *HistoricalFigure:
|
||||
el.Context.hfId = x.Id()
|
||||
el.Events = world.EventsMatching(func(d HistoricalEventDetails) bool { return d.RelatedToHf(x.Id()) })
|
||||
case *Artifact:
|
||||
el.Events = world.EventsMatching(func(d HistoricalEventDetails) bool { return d.RelatedToArtifact(x.Id()) })
|
||||
case *Site:
|
||||
el.Events = world.EventsMatching(func(d HistoricalEventDetails) bool { return d.RelatedToSite(x.Id()) })
|
||||
case *Region:
|
||||
el.Events = world.EventsMatching(func(d HistoricalEventDetails) bool { return d.RelatedToRegion(x.Id()) })
|
||||
case []*HistoricalEvent:
|
||||
el.Events = x
|
||||
default:
|
||||
fmt.Printf("unknown type %T\n", obj)
|
||||
}
|
||||
|
||||
return &el
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -2,15 +2,52 @@ package model
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/iancoleman/strcase"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/util"
|
||||
)
|
||||
|
||||
func (w *DfWorld) AllEventTypes() []string {
|
||||
types := make(map[string]bool)
|
||||
for _, e := range w.HistoricalEvents {
|
||||
types[e.Details.Type()] = true
|
||||
}
|
||||
var list = util.Keys(types)
|
||||
sort.Strings(list)
|
||||
return list
|
||||
}
|
||||
|
||||
func (w *DfWorld) EventsOfType(t string) any {
|
||||
var list []*HistoricalEvent
|
||||
for _, e := range w.HistoricalEvents {
|
||||
if e.Details.Type() == t {
|
||||
list = append(list, e)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(list, func(i, j int) bool { return list[i].Id_ < list[j].Id_ })
|
||||
|
||||
return struct {
|
||||
Type string
|
||||
Events []*HistoricalEvent
|
||||
}{
|
||||
Type: t,
|
||||
Events: list,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *DfWorld) EventsMatching(f func(HistoricalEventDetails) bool) []*HistoricalEvent {
|
||||
var list []*HistoricalEvent
|
||||
for _, e := range w.HistoricalEvents {
|
||||
if f(e.Details) {
|
||||
list = append(list, e)
|
||||
}
|
||||
}
|
||||
sort.Slice(list, func(a, b int) bool { return list[a].Id_ < list[b].Id_ })
|
||||
return list
|
||||
}
|
||||
|
||||
func (e *Entity) Position(id int) *EntityPosition {
|
||||
for _, p := range e.EntityPosition {
|
||||
if p.Id_ == id {
|
||||
|
@ -56,434 +93,30 @@ func (hf *HistoricalFigure) FirstName() string {
|
|||
return strings.Split(hf.Name_, " ")[0]
|
||||
}
|
||||
|
||||
func (x *Honor) Requirement() string {
|
||||
var list []string
|
||||
if x.RequiresAnyMeleeOrRangedSkill {
|
||||
list = append(list, "attaining sufficent skill with a weapon or technique")
|
||||
}
|
||||
if x.RequiredSkill != HonorRequiredSkill_Unknown {
|
||||
list = append(list, "attaining enough skill with the "+x.RequiredSkill.String())
|
||||
}
|
||||
if x.RequiredBattles == 1 {
|
||||
list = append(list, "serving in combat")
|
||||
}
|
||||
if x.RequiredBattles > 1 {
|
||||
list = append(list, fmt.Sprintf("participating in %d battles", x.RequiredBattles))
|
||||
}
|
||||
if x.RequiredYears >= 1 {
|
||||
list = append(list, fmt.Sprintf("%d years of membership", x.RequiredYears))
|
||||
}
|
||||
if x.RequiredKills >= 1 {
|
||||
list = append(list, fmt.Sprintf("slaying %d enemies", x.RequiredKills))
|
||||
}
|
||||
|
||||
return " after " + andList(list)
|
||||
}
|
||||
|
||||
func (w *WrittenContent) Name() string {
|
||||
return w.Title
|
||||
}
|
||||
|
||||
type HistoricalEventDetails interface {
|
||||
RelatedToEntity(int) bool
|
||||
RelatedToHf(int) bool
|
||||
RelatedToArtifact(int) bool
|
||||
RelatedToSite(int) bool
|
||||
RelatedToRegion(int) bool
|
||||
Html(*context) string
|
||||
Type() string
|
||||
}
|
||||
|
||||
type HistoricalEventCollectionDetails interface {
|
||||
}
|
||||
|
||||
type EventList struct {
|
||||
Events []*HistoricalEvent
|
||||
Context *context
|
||||
}
|
||||
|
||||
func filter(f func(HistoricalEventDetails) bool) []*HistoricalEvent {
|
||||
var list []*HistoricalEvent
|
||||
for _, e := range world.HistoricalEvents {
|
||||
if f(e.Details) {
|
||||
list = append(list, e)
|
||||
}
|
||||
}
|
||||
sort.Slice(list, func(a, b int) bool { return list[a].Id_ < list[b].Id_ })
|
||||
return list
|
||||
}
|
||||
|
||||
func NewEventList(obj any) *EventList {
|
||||
el := EventList{
|
||||
Context: &context{HfId: -1},
|
||||
}
|
||||
|
||||
switch x := obj.(type) {
|
||||
case *Entity:
|
||||
el.Events = filter(func(d HistoricalEventDetails) bool { return d.RelatedToEntity(x.Id()) })
|
||||
case *HistoricalFigure:
|
||||
el.Context.HfId = x.Id()
|
||||
el.Events = filter(func(d HistoricalEventDetails) bool { return d.RelatedToHf(x.Id()) })
|
||||
case *Artifact:
|
||||
el.Events = filter(func(d HistoricalEventDetails) bool { return d.RelatedToArtifact(x.Id()) })
|
||||
case *Site:
|
||||
el.Events = filter(func(d HistoricalEventDetails) bool { return d.RelatedToSite(x.Id()) })
|
||||
case *Region:
|
||||
el.Events = filter(func(d HistoricalEventDetails) bool { return d.RelatedToRegion(x.Id()) })
|
||||
case []*HistoricalEvent:
|
||||
el.Events = x
|
||||
default:
|
||||
fmt.Printf("unknown type %T\n", obj)
|
||||
}
|
||||
|
||||
return &el
|
||||
}
|
||||
|
||||
type context struct {
|
||||
HfId int
|
||||
Story bool
|
||||
}
|
||||
|
||||
func (c *context) hf(id int) string {
|
||||
if c.HfId != -1 {
|
||||
if c.HfId == id {
|
||||
return hfShort(id)
|
||||
} else {
|
||||
return hfRelated(id, c.HfId)
|
||||
}
|
||||
}
|
||||
return hf(id)
|
||||
}
|
||||
|
||||
func (c *context) hfShort(id int) string {
|
||||
return hfShort(id)
|
||||
}
|
||||
|
||||
func (c *context) hfRelated(id, to int) string {
|
||||
if c.HfId != -1 {
|
||||
if c.HfId == id {
|
||||
return hfShort(id)
|
||||
} else {
|
||||
return hfRelated(id, c.HfId)
|
||||
}
|
||||
}
|
||||
return hfRelated(id, to)
|
||||
}
|
||||
|
||||
func (c *context) hfList(ids []int) string {
|
||||
return andList(util.Map(ids, func(id int) string { return c.hf(id) }))
|
||||
}
|
||||
|
||||
func (c *context) hfListRelated(ids []int, to int) string {
|
||||
return andList(util.Map(ids, func(id int) string { return c.hfRelated(id, to) }))
|
||||
}
|
||||
|
||||
func containsInt(list []int, id int) bool {
|
||||
for _, v := range list {
|
||||
if v == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var world *DfWorld
|
||||
|
||||
func andList(list []string) string {
|
||||
if len(list) > 1 {
|
||||
return strings.Join(list[:len(list)-1], ", ") + " and " + list[len(list)-1]
|
||||
}
|
||||
return strings.Join(list, ", ")
|
||||
}
|
||||
|
||||
func articled(s string) string {
|
||||
if ok, _ := regexp.MatchString("^([aeio]|un|ul).*", s); ok {
|
||||
return "an " + s
|
||||
}
|
||||
return "a " + s
|
||||
}
|
||||
|
||||
func artifact(id int) string {
|
||||
if x, ok := world.Artifacts[id]; ok {
|
||||
return fmt.Sprintf(`<a class="artifact" href="/artifact/%d">%s</a>`, x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN ARTIFACT"
|
||||
}
|
||||
|
||||
func entity(id int) string {
|
||||
if x, ok := world.Entities[id]; ok {
|
||||
return fmt.Sprintf(`<a class="entity" href="/entity/%d">%s</a>`, x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN ENTITY"
|
||||
}
|
||||
|
||||
func entityList(ids []int) string {
|
||||
return andList(util.Map(ids, entity))
|
||||
}
|
||||
|
||||
func position(entityId, positionId, hfId int) string {
|
||||
if e, ok := world.Entities[entityId]; ok {
|
||||
if h, ok := world.HistoricalFigures[hfId]; ok {
|
||||
return e.Position(positionId).GenderName(h)
|
||||
}
|
||||
}
|
||||
return "UNKNOWN POSITION"
|
||||
}
|
||||
|
||||
func siteCiv(siteCivId, civId int) string {
|
||||
if siteCivId == civId {
|
||||
return entity(civId)
|
||||
}
|
||||
return util.If(siteCivId != -1, entity(siteCivId), "") + util.If(civId != -1 && siteCivId != -1, " of ", "") + util.If(civId != -1, entity(civId), "")
|
||||
}
|
||||
|
||||
func siteStructure(siteId, structureId int, prefix string) string {
|
||||
if siteId == -1 {
|
||||
return ""
|
||||
}
|
||||
return " " + prefix + " " + util.If(structureId != -1, structure(siteId, structureId)+" in ", "") + site(siteId, "")
|
||||
}
|
||||
|
||||
func hf(id int) string {
|
||||
if x, ok := world.HistoricalFigures[id]; ok {
|
||||
return fmt.Sprintf(`the %s <a class="hf" href="/hf/%d">%s</a>`, x.Race+util.If(x.Deity, " deity", "")+util.If(x.Force, " force", ""), x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN HISTORICAL FIGURE"
|
||||
}
|
||||
|
||||
func hfShort(id int) string {
|
||||
if x, ok := world.HistoricalFigures[id]; ok {
|
||||
return fmt.Sprintf(`<a class="hf" href="/hf/%d">%s</a>`, x.Id(), util.Title(x.FirstName()))
|
||||
}
|
||||
return "UNKNOWN HISTORICAL FIGURE"
|
||||
}
|
||||
|
||||
func hfRelated(id, to int) string {
|
||||
if x, ok := world.HistoricalFigures[id]; ok {
|
||||
if t, ok := world.HistoricalFigures[to]; ok {
|
||||
if y, ok := util.Find(t.HfLink, func(l *HfLink) bool { return l.Hfid == id }); ok {
|
||||
return fmt.Sprintf(`%s %s <a class="hf" href="/hf/%d">%s</a>`, t.PossesivePronoun(), y.LinkType, x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf(`the %s <a class="hf" href="/hf/%d">%s</a>`, x.Race+util.If(x.Deity, " deity", "")+util.If(x.Force, " force", ""), x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN HISTORICAL FIGURE"
|
||||
}
|
||||
|
||||
func hfLink(id, to int) string {
|
||||
if x, ok := world.HistoricalFigures[id]; ok {
|
||||
if y, ok := util.Find(x.HfLink, func(l *HfLink) bool { return l.Hfid == to }); ok {
|
||||
return y.LinkType.String()
|
||||
}
|
||||
}
|
||||
return "UNKNOWN LINK"
|
||||
}
|
||||
|
||||
func pronoun(id int) string {
|
||||
if x, ok := world.HistoricalFigures[id]; ok {
|
||||
return x.Pronoun()
|
||||
}
|
||||
return "he"
|
||||
}
|
||||
|
||||
func posessivePronoun(id int) string {
|
||||
if x, ok := world.HistoricalFigures[id]; ok {
|
||||
return x.PossesivePronoun()
|
||||
}
|
||||
return "his"
|
||||
}
|
||||
|
||||
func site(id int, prefix string) string {
|
||||
if x, ok := world.Sites[id]; ok {
|
||||
return fmt.Sprintf(`%s <a class="site" href="/site/%d">%s</a>`, prefix, x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN SITE"
|
||||
}
|
||||
|
||||
func structure(siteId, structureId int) string {
|
||||
if x, ok := world.Sites[siteId]; ok {
|
||||
if y, ok := x.Structures[structureId]; ok {
|
||||
return fmt.Sprintf(`<a class="structure" href="/site/%d/structure/%d">%s</a>`, siteId, structureId, util.Title(y.Name()))
|
||||
}
|
||||
}
|
||||
return "UNKNOWN STRUCTURE"
|
||||
}
|
||||
|
||||
func property(siteId, propertyId int) string {
|
||||
if x, ok := world.Sites[siteId]; ok {
|
||||
if y, ok := x.SiteProperties[propertyId]; ok {
|
||||
if y.StructureId != -1 {
|
||||
return structure(siteId, y.StructureId)
|
||||
}
|
||||
return articled(y.Type.String())
|
||||
}
|
||||
}
|
||||
return "UNKNOWN PROPERTY"
|
||||
}
|
||||
|
||||
func region(id int) string {
|
||||
if x, ok := world.Regions[id]; ok {
|
||||
return fmt.Sprintf(`<a class="region" href="/region/%d">%s</a>`, x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN REGION"
|
||||
}
|
||||
|
||||
func location(siteId int, sitePrefix string, regionId int, regionPrefix string) string {
|
||||
if siteId != -1 {
|
||||
return site(siteId, sitePrefix)
|
||||
}
|
||||
if regionId != -1 {
|
||||
return regionPrefix + " " + region(regionId)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func place(structureId, siteId int, sitePrefix string, regionId int, regionPrefix string) string {
|
||||
if siteId != -1 {
|
||||
return siteStructure(siteId, structureId, sitePrefix)
|
||||
}
|
||||
if regionId != -1 {
|
||||
return regionPrefix + " " + region(regionId)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func mountain(id int) string {
|
||||
if x, ok := world.MountainPeaks[id]; ok {
|
||||
return fmt.Sprintf(`<a class="mountain" href="/site/%d">%s</a>`, x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN MOUNTAIN"
|
||||
}
|
||||
|
||||
func identity(id int) string {
|
||||
if x, ok := world.Identities[id]; ok {
|
||||
return fmt.Sprintf(`<a class="identity" href="/region/%d">%s</a>`, x.Id(), util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN IDENTITY"
|
||||
}
|
||||
|
||||
func fullIdentity(id int) string {
|
||||
if x, ok := world.Identities[id]; ok {
|
||||
return fmt.Sprintf(`"the %s <a class="identity" href="/region/%d">%s</a> of %s"`, x.Profession.String(), x.Id(), util.Title(x.Name()), entity(x.EntityId))
|
||||
}
|
||||
return "UNKNOWN IDENTITY"
|
||||
}
|
||||
|
||||
func feature(x *Feature) string {
|
||||
switch x.Type {
|
||||
case FeatureType_DancePerformance:
|
||||
return "a perfomance of " + danceForm(x.Reference)
|
||||
case FeatureType_Images:
|
||||
if x.Reference != -1 {
|
||||
return "images of " + hf(x.Reference)
|
||||
}
|
||||
return "images"
|
||||
case FeatureType_MusicalPerformance:
|
||||
return "a perfomance of " + musicalForm(x.Reference)
|
||||
case FeatureType_PoetryRecital:
|
||||
return "a recital of " + poeticForm(x.Reference)
|
||||
case FeatureType_Storytelling:
|
||||
if x.Reference != -1 {
|
||||
if e, ok := world.HistoricalEvents[x.Reference]; ok {
|
||||
return "a telling of the story of " + e.Details.Html(&context{Story: true}) + " in " + Time(e.Year, e.Seconds72)
|
||||
}
|
||||
}
|
||||
return "a story recital"
|
||||
default:
|
||||
return strcase.ToDelimited(x.Type.String(), ' ')
|
||||
}
|
||||
}
|
||||
|
||||
func schedule(x *Schedule) string {
|
||||
switch x.Type {
|
||||
case ScheduleType_DancePerformance:
|
||||
return "a perfomance of " + danceForm(x.Reference)
|
||||
case ScheduleType_MusicalPerformance:
|
||||
return "a perfomance of " + musicalForm(x.Reference)
|
||||
case ScheduleType_PoetryRecital:
|
||||
return "a recital of " + poeticForm(x.Reference)
|
||||
case ScheduleType_Storytelling:
|
||||
if x.Reference != -1 {
|
||||
if e, ok := world.HistoricalEvents[x.Reference]; ok {
|
||||
return "the story of " + e.Details.Html(&context{Story: true}) + " in " + Time(e.Year, e.Seconds72)
|
||||
}
|
||||
}
|
||||
return "a story recital"
|
||||
default:
|
||||
return strcase.ToDelimited(x.Type.String(), ' ')
|
||||
}
|
||||
}
|
||||
|
||||
func ShortTime(year, seconds int) string {
|
||||
if year == -1 {
|
||||
return "a time before time"
|
||||
}
|
||||
return fmt.Sprintf("%d", year)
|
||||
}
|
||||
|
||||
func Time(year, seconds int) string {
|
||||
if year == -1 {
|
||||
return "a time before time"
|
||||
}
|
||||
if seconds == -1 {
|
||||
return fmt.Sprintf("%d", year)
|
||||
}
|
||||
return fmt.Sprintf("%s of %d", Season(seconds), year)
|
||||
}
|
||||
|
||||
func Season(seconds int) string {
|
||||
r := ""
|
||||
month := seconds % 100800
|
||||
if month <= 33600 {
|
||||
r += "early "
|
||||
} else if month <= 67200 {
|
||||
r += "mid"
|
||||
} else if month <= 100800 {
|
||||
r += "late "
|
||||
}
|
||||
|
||||
season := seconds % 403200
|
||||
if season < 100800 {
|
||||
r += "spring"
|
||||
} else if season < 201600 {
|
||||
r += "summer"
|
||||
} else if season < 302400 {
|
||||
r += "autumn"
|
||||
} else if season < 403200 {
|
||||
r += "winter"
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func danceForm(id int) string {
|
||||
if x, ok := world.DanceForms[id]; ok {
|
||||
return fmt.Sprintf(`<a class="artform" href="/danceForm/%d">%s</a>`, id, util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN DANCE FORM"
|
||||
}
|
||||
|
||||
func musicalForm(id int) string {
|
||||
if x, ok := world.MusicalForms[id]; ok {
|
||||
return fmt.Sprintf(`<a class="artform" href="/musicalForm/%d">%s</a>`, id, util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN MUSICAL FORM"
|
||||
}
|
||||
|
||||
func poeticForm(id int) string {
|
||||
if x, ok := world.PoeticForms[id]; ok {
|
||||
return fmt.Sprintf(`<a class="artform" href="/poeticForm/%d">%s</a>`, id, util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN POETIC FORM"
|
||||
}
|
||||
|
||||
func worldConstruction(id int) string {
|
||||
if x, ok := world.WorldConstructions[id]; ok {
|
||||
return fmt.Sprintf(`<a class="artform" href="/wc/%d">%s</a>`, id, util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN WORLD CONSTRUCTION"
|
||||
}
|
||||
|
||||
func writtenContent(id int) string {
|
||||
if x, ok := world.WrittenContents[id]; ok {
|
||||
return fmt.Sprintf(`<a class="artform" href="/writtenContent/%d">%s</a>`, id, util.Title(x.Name()))
|
||||
}
|
||||
return "UNKNOWN WORLD CONSTRUCTION"
|
||||
}
|
||||
|
||||
var LinkHf = func(id int) template.HTML { return template.HTML(hf(id)) }
|
||||
var LinkEntity = func(id int) template.HTML { return template.HTML(entity(id)) }
|
||||
var LinkSite = func(id int) template.HTML { return template.HTML(site(id, "")) }
|
||||
var LinkRegion = func(id int) template.HTML { return template.HTML(region(id)) }
|
||||
|
||||
func equipmentLevel(level int) string {
|
||||
switch level {
|
||||
case 1:
|
||||
return "well-crafted"
|
||||
case 2:
|
||||
return "finely-crafted"
|
||||
case 3:
|
||||
return "superior quality"
|
||||
case 4:
|
||||
return "exceptional"
|
||||
case 5:
|
||||
return "masterwork"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
94
backend/model/functions.go
Normal file
94
backend/model/functions.go
Normal file
|
@ -0,0 +1,94 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var LinkHf = func(w *DfWorld, id int) template.HTML { return template.HTML((&context{world: w}).hf(id)) }
|
||||
var LinkEntity = func(w *DfWorld, id int) template.HTML { return template.HTML((&context{world: w}).entity(id)) }
|
||||
var LinkSite = func(w *DfWorld, id int) template.HTML { return template.HTML((&context{world: w}).site(id, "")) }
|
||||
var LinkRegion = func(w *DfWorld, id int) template.HTML { return template.HTML((&context{world: w}).region(id)) }
|
||||
|
||||
func andList(list []string) string {
|
||||
if len(list) > 1 {
|
||||
return strings.Join(list[:len(list)-1], ", ") + " and " + list[len(list)-1]
|
||||
}
|
||||
return strings.Join(list, ", ")
|
||||
}
|
||||
|
||||
func articled(s string) string {
|
||||
if ok, _ := regexp.MatchString("^([aeio]|un|ul).*", s); ok {
|
||||
return "an " + s
|
||||
}
|
||||
return "a " + s
|
||||
}
|
||||
|
||||
func ShortTime(year, seconds int) string {
|
||||
if year == -1 {
|
||||
return "a time before time"
|
||||
}
|
||||
return fmt.Sprintf("%d", year)
|
||||
}
|
||||
|
||||
func Time(year, seconds int) string {
|
||||
if year == -1 {
|
||||
return "a time before time"
|
||||
}
|
||||
if seconds == -1 {
|
||||
return fmt.Sprintf("%d", year)
|
||||
}
|
||||
return fmt.Sprintf("%s of %d", Season(seconds), year)
|
||||
}
|
||||
|
||||
func Season(seconds int) string {
|
||||
r := ""
|
||||
month := seconds % 100800
|
||||
if month <= 33600 {
|
||||
r += "early "
|
||||
} else if month <= 67200 {
|
||||
r += "mid"
|
||||
} else if month <= 100800 {
|
||||
r += "late "
|
||||
}
|
||||
|
||||
season := seconds % 403200
|
||||
if season < 100800 {
|
||||
r += "spring"
|
||||
} else if season < 201600 {
|
||||
r += "summer"
|
||||
} else if season < 302400 {
|
||||
r += "autumn"
|
||||
} else if season < 403200 {
|
||||
r += "winter"
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func equipmentLevel(level int) string {
|
||||
switch level {
|
||||
case 1:
|
||||
return "well-crafted"
|
||||
case 2:
|
||||
return "finely-crafted"
|
||||
case 3:
|
||||
return "superior quality"
|
||||
case 4:
|
||||
return "exceptional"
|
||||
case 5:
|
||||
return "masterwork"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func containsInt(list []int, id int) bool {
|
||||
for _, v := range list {
|
||||
if v == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -70,6 +70,8 @@ func Parse(file string) (*DfWorld, error) {
|
|||
}
|
||||
defer xmlFile.Close()
|
||||
|
||||
world := &DfWorld{}
|
||||
|
||||
BaseLoop:
|
||||
for {
|
||||
t, n, err := p.Token()
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"HistoricalEventAddHfEntityLink": {
|
||||
"LinkType": "Link"
|
||||
},
|
||||
"HistoricalEventAddHfSiteLink": {
|
||||
"Site": "SiteId"
|
||||
},
|
||||
"HistoricalEventAgreementMade": {
|
||||
"Destination": "SiteId",
|
||||
"Site": "SiteId",
|
||||
"Source": "SiteId"
|
||||
},
|
||||
"HistoricalEventAgreementRejected": {
|
||||
"Destination": "SiteId",
|
||||
"Site": "SiteId",
|
||||
"Source": "SiteId"
|
||||
},
|
||||
"HistoricalEventDiplomatLost": {
|
||||
"Entity": "SiteId",
|
||||
"Involved": "SiteId",
|
||||
"Site": "SiteId"
|
||||
},
|
||||
"HistoricalEventHfDied": {
|
||||
"DeathCause": "Cause",
|
||||
"ShooterItemSubtype": "Cause",
|
||||
"ShooterItemType": "Cause"
|
||||
},
|
||||
"HistoricalEventPeaceAccepted": {
|
||||
"Site": "SiteId"
|
||||
},
|
||||
"HistoricalEventPeaceRejected": {
|
||||
"Site": "SiteId"
|
||||
},
|
||||
"HistoricalEventRemoveHfEntityLink": {
|
||||
"LinkType": "Link"
|
||||
},
|
||||
"HistoricalEventRemoveHfSiteLink": {
|
||||
"Site": "SiteId"
|
||||
}
|
||||
}
|
|
@ -1,39 +1,30 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/model"
|
||||
)
|
||||
|
||||
type Info struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func RegisterResource[T model.Named](router *mux.Router, resourceName string, resources map[int]T) {
|
||||
list := func(w http.ResponseWriter, r *http.Request) {
|
||||
values := make([]Info, 0, len(resources))
|
||||
for _, v := range resources {
|
||||
values = append(values, Info{Id: v.Id(), Name: v.Name()})
|
||||
|
||||
}
|
||||
json.NewEncoder(w).Encode(values)
|
||||
}
|
||||
type Parms map[string]string
|
||||
|
||||
func (srv *DfServer) RegisterPage(path string, template string, accessor func(Parms) any) {
|
||||
get := func(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.Atoi(mux.Vars(r)["id"])
|
||||
err := srv.templates.Render(w, template, accessor(mux.Vars(r)))
|
||||
if err != nil {
|
||||
fmt.Fprintln(w, err)
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(resources[id])
|
||||
}
|
||||
|
||||
router.HandleFunc(fmt.Sprintf("/api/%s", resourceName), list).Methods("GET")
|
||||
router.HandleFunc(fmt.Sprintf("/api/%s/{id}", resourceName), get).Methods("GET")
|
||||
srv.router.HandleFunc(path, get).Methods("GET")
|
||||
}
|
||||
|
||||
func (srv *DfServer) RegisterResourcePage(path string, template string, accessor func(int) any) {
|
||||
srv.RegisterPage(path, template, func(params Parms) any {
|
||||
id, _ := strconv.Atoi(params["id"])
|
||||
return accessor(id)
|
||||
})
|
||||
}
|
||||
|
|
90
backend/server/server.go
Normal file
90
backend/server/server.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/model"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/templates"
|
||||
)
|
||||
|
||||
type DfServer struct {
|
||||
router *mux.Router
|
||||
templates *templates.Template
|
||||
world *model.DfWorld
|
||||
}
|
||||
|
||||
func StartServer(world *model.DfWorld, static embed.FS) {
|
||||
srv := &DfServer{
|
||||
router: mux.NewRouter().StrictSlash(true),
|
||||
world: world,
|
||||
}
|
||||
srv.LoadTemplates()
|
||||
|
||||
srv.RegisterResourcePage("/entity/{id}", "entity.html", func(id int) any { return srv.world.Entities[id] })
|
||||
srv.RegisterResourcePage("/hf/{id}", "hf.html", func(id int) any { return srv.world.HistoricalFigures[id] })
|
||||
srv.RegisterResourcePage("/region/{id}", "region.html", func(id int) any { return srv.world.Regions[id] })
|
||||
srv.RegisterResourcePage("/site/{id}", "site.html", func(id int) any { return srv.world.Sites[id] })
|
||||
srv.RegisterResourcePage("/artifact/{id}", "artifact.html", func(id int) any { return srv.world.Artifacts[id] })
|
||||
srv.RegisterPage("/events", "eventTypes.html", func(p Parms) any { return srv.world.AllEventTypes() })
|
||||
srv.RegisterPage("/events/{type}", "eventType.html", func(p Parms) any { return srv.world.EventsOfType(p["type"]) })
|
||||
|
||||
spa := spaHandler{staticFS: static, staticPath: "static", indexPath: "index.html"}
|
||||
srv.router.PathPrefix("/").Handler(spa)
|
||||
|
||||
OpenBrowser("http://localhost:8080")
|
||||
|
||||
fmt.Println("Serving at :8080")
|
||||
http.ListenAndServe(":8080", srv.router)
|
||||
}
|
||||
|
||||
type spaHandler struct {
|
||||
staticFS embed.FS
|
||||
staticPath string
|
||||
indexPath string
|
||||
}
|
||||
|
||||
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// get the absolute path to prevent directory traversal
|
||||
path := r.URL.Path
|
||||
// if err != nil {
|
||||
// // if we failed to get the absolute path respond with a 400 bad request and stop
|
||||
// http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
// return
|
||||
// }
|
||||
// prepend the path with the path to the static directory
|
||||
path = h.staticPath + path
|
||||
fmt.Println(r.URL, "->", path)
|
||||
|
||||
_, err := h.staticFS.Open(path)
|
||||
if os.IsNotExist(err) {
|
||||
// file does not exist, serve index.html
|
||||
fmt.Println(path)
|
||||
index, err := h.staticFS.ReadFile(h.staticPath + "/" + h.indexPath)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
w.Write(index)
|
||||
return
|
||||
} else if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// get the subdirectory of the static dir
|
||||
statics, err := fs.Sub(h.staticFS, h.staticPath)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise, use http.FileServer to serve the static dir
|
||||
http.FileServer(http.FS(statics)).ServeHTTP(w, r)
|
||||
}
|
38
backend/server/templates.go
Normal file
38
backend/server/templates.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/model"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/templates"
|
||||
"github.com/robertjanetzko/LegendsBrowser2/backend/util"
|
||||
)
|
||||
|
||||
func (srv *DfServer) LoadTemplates() {
|
||||
functions := template.FuncMap{
|
||||
"json": util.Json,
|
||||
"check": func(condition bool, v any) any {
|
||||
if condition {
|
||||
return v
|
||||
}
|
||||
return nil
|
||||
},
|
||||
"title": util.Title,
|
||||
"hf": func(id int) template.HTML { return model.LinkHf(srv.world, id) },
|
||||
"getHf": func(id int) *model.HistoricalFigure { return srv.world.HistoricalFigures[id] },
|
||||
"entity": func(id int) template.HTML { return model.LinkEntity(srv.world, id) },
|
||||
"getEntity": func(id int) *model.Entity { return srv.world.Entities[id] },
|
||||
"site": func(id int) template.HTML { return model.LinkSite(srv.world, id) },
|
||||
"getSite": func(id int) *model.Site { return srv.world.Sites[id] },
|
||||
"region": func(id int) template.HTML { return model.LinkRegion(srv.world, id) },
|
||||
"getRegion": func(id int) *model.Region { return srv.world.Regions[id] },
|
||||
"events": func(obj any) *model.EventList { return model.NewEventList(srv.world, obj) },
|
||||
"season": model.Season,
|
||||
"time": model.Time,
|
||||
"html": func(value any) template.HTML {
|
||||
return template.HTML(fmt.Sprint(value))
|
||||
},
|
||||
}
|
||||
srv.templates = templates.New(functions)
|
||||
}
|
27
backend/server/util.go
Normal file
27
backend/server/util.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func OpenBrowser(url string) {
|
||||
var err error
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
err = exec.Command("xdg-open", url).Start()
|
||||
case "windows":
|
||||
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||
case "darwin":
|
||||
err = exec.Command("open", url).Start()
|
||||
default:
|
||||
err = fmt.Errorf("unsupported platform")
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("navigate to http://localhost:8080 in your browser")
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue