refactor server context
This commit is contained in:
parent
5aa0bc5a8d
commit
93e5ebb788
|
@ -1 +1,2 @@
|
||||||
resources/frontend/*
|
resources/frontend/*
|
||||||
|
same.json
|
169
backend/main.go
169
backend/main.go
|
@ -4,25 +4,16 @@ import (
|
||||||
"embed"
|
"embed"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/pkg/profile"
|
"github.com/pkg/profile"
|
||||||
"github.com/robertjanetzko/LegendsBrowser2/backend/model"
|
"github.com/robertjanetzko/LegendsBrowser2/backend/model"
|
||||||
"github.com/robertjanetzko/LegendsBrowser2/backend/templates"
|
"github.com/robertjanetzko/LegendsBrowser2/backend/server"
|
||||||
"github.com/robertjanetzko/LegendsBrowser2/backend/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var world *model.DfWorld
|
|
||||||
|
|
||||||
//go:embed static
|
//go:embed static
|
||||||
var static embed.FS
|
var static embed.FS
|
||||||
|
|
||||||
|
@ -31,34 +22,6 @@ func main() {
|
||||||
p := flag.Bool("p", false, "start profiling")
|
p := flag.Bool("p", false, "start profiling")
|
||||||
flag.Parse()
|
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 len(*f) > 0 {
|
||||||
if *p {
|
if *p {
|
||||||
defer profile.Start(profile.ProfilePath(".")).Stop()
|
defer profile.Start(profile.ProfilePath(".")).Stop()
|
||||||
|
@ -73,137 +36,9 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
world = w
|
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
|
|
||||||
RegisterResourcePage(router, "/entity/{id}", t, "entity.html", func(id int) any { return world.Entities[id] })
|
server.StartServer(w, static)
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
|
@ -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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/iancoleman/strcase"
|
|
||||||
"github.com/robertjanetzko/LegendsBrowser2/backend/util"
|
"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 {
|
func (e *Entity) Position(id int) *EntityPosition {
|
||||||
for _, p := range e.EntityPosition {
|
for _, p := range e.EntityPosition {
|
||||||
if p.Id_ == id {
|
if p.Id_ == id {
|
||||||
|
@ -56,434 +93,30 @@ func (hf *HistoricalFigure) FirstName() string {
|
||||||
return strings.Split(hf.Name_, " ")[0]
|
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 {
|
func (w *WrittenContent) Name() string {
|
||||||
return w.Title
|
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 ""
|
|
||||||
}
|
|
||||||
|
|
|
@ -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()
|
defer xmlFile.Close()
|
||||||
|
|
||||||
|
world := &DfWorld{}
|
||||||
|
|
||||||
BaseLoop:
|
BaseLoop:
|
||||||
for {
|
for {
|
||||||
t, n, err := p.Token()
|
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
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/robertjanetzko/LegendsBrowser2/backend/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Info struct {
|
type Parms map[string]string
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func (srv *DfServer) RegisterPage(path string, template string, accessor func(Parms) any) {
|
||||||
get := func(w http.ResponseWriter, r *http.Request) {
|
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 {
|
if err != nil {
|
||||||
|
fmt.Fprintln(w, err)
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(resources[id])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
router.HandleFunc(fmt.Sprintf("/api/%s", resourceName), list).Methods("GET")
|
srv.router.HandleFunc(path, get).Methods("GET")
|
||||||
router.HandleFunc(fmt.Sprintf("/api/%s/{id}", resourceName), 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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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 New Issue