This commit is contained in:
Robert Janetzko 2022-04-30 07:45:31 +00:00
parent 25ce6c179b
commit 8b7fa14258
16 changed files with 251 additions and 188 deletions

125
backend/server/loading.go Normal file
View File

@ -0,0 +1,125 @@
package server
import (
"encoding/json"
"io/fs"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/robertjanetzko/LegendsBrowser2/backend/model"
"github.com/robertjanetzko/LegendsBrowser2/backend/util"
"github.com/shirou/gopsutil/disk"
)
type loadHandler struct {
server *DfServer
}
type loadProgress struct {
Msg string `json:"msg"`
Progress float64 `json:"progress"`
Done bool `json:"done"`
}
func (h loadHandler) Progress() *loadProgress {
percent := 0.0
p := h.server.context.progress
if p.ProgressBar != nil {
percent = float64(p.ProgressBar.Current()*100) / float64(p.ProgressBar.Total())
}
return &loadProgress{
Msg: h.server.context.progress.Message,
Progress: percent,
Done: h.server.context.world != nil,
}
}
func (h loadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/load/progress" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(h.Progress())
return
}
var partitions []string
if runtime.GOOS == "windows" {
ps, _ := disk.Partitions(false)
partitions = util.Map(ps, func(p disk.PartitionStat) string { return p.Mountpoint + `\` })
} else {
partitions = append(partitions, "/")
}
path := r.URL.Query().Get("p")
p := &paths{
Partitions: partitions,
Current: path,
}
if p.Current == "" {
p.Current = "."
}
var err error
p.Current, err = filepath.Abs(p.Current)
if err != nil {
httpError(w, err)
return
}
if f, err := os.Stat(p.Current); err == nil {
if f.IsDir() {
p.List, err = ioutil.ReadDir(p.Current)
if err != nil {
httpError(w, err)
return
}
err = h.server.templates.Render(w, "load.html", p)
if err != nil {
httpError(w, err)
}
return
} else {
h.server.context.isLoading = true
h.server.context.world = nil
go loadWorld(h.server, p.Current)
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
}
http.Redirect(w, r, "/load", http.StatusSeeOther)
}
func isLegendsXml(f fs.FileInfo) bool {
return strings.HasSuffix(f.Name(), "-legends.xml")
}
func loadWorld(server *DfServer, file string) {
runtime.GC()
wrld, _ := model.Parse(file, server.context.progress)
server.context.world = wrld
server.context.isLoading = false
}
type paths struct {
Current string
List []fs.FileInfo
Partitions []string
}
func (srv *DfServer) renderLoading(w http.ResponseWriter, r *http.Request) {
if srv.context.isLoading {
err := srv.templates.Render(w, "loading.html", srv.loader.Progress())
if err != nil {
httpError(w, err)
}
} else {
http.Redirect(w, r, "/load", http.StatusSeeOther)
}
}

View File

@ -3,6 +3,7 @@ package server
import (
"fmt"
"net/http"
"reflect"
"strconv"
"github.com/gorilla/mux"
@ -10,18 +11,6 @@ import (
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) {
// err := srv.templates.Render(w, template, accessor(mux.Vars(r)))
// if err != nil {
// fmt.Fprintln(w, err)
// fmt.Println(err)
// }
// }
// srv.router.HandleFunc(path, get).Methods("GET")
// }
func (srv *DfServer) RegisterWorldPage(path string, template string, accessor func(Parms) any) {
get := func(w http.ResponseWriter, r *http.Request) {
if srv.context.world == nil {
@ -29,7 +18,13 @@ func (srv *DfServer) RegisterWorldPage(path string, template string, accessor fu
return
}
err := srv.templates.Render(w, template, accessor(mux.Vars(r)))
data := accessor(mux.Vars(r))
if reflect.ValueOf(data).IsNil() {
srv.notFound(w)
return
}
err := srv.templates.Render(w, template, data)
if err != nil {
fmt.Fprintln(w, err)
fmt.Println(err)

63
backend/server/search.go Normal file
View File

@ -0,0 +1,63 @@
package server
import (
"encoding/json"
"fmt"
"net/http"
"sort"
"strings"
"github.com/robertjanetzko/LegendsBrowser2/backend/model"
"github.com/robertjanetzko/LegendsBrowser2/backend/util"
)
type searchHandler struct {
server *DfServer
}
type SearchResult struct {
Label string `json:"label"`
Value string `json:"value"`
}
func (h searchHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if h.server.context.world == nil {
h.server.renderLoading(w, r)
return
}
term := r.URL.Query().Get("term")
var results []SearchResult
results = seachMap(term, h.server.context.world.HistoricalFigures, results, "/hf")
results = seachMap(term, h.server.context.world.Entities, results, "/entity")
results = seachMap(term, h.server.context.world.Sites, results, "/site")
results = seachMap(term, h.server.context.world.Regions, results, "/region")
results = seachMap(term, h.server.context.world.Artifacts, results, "/artifavt")
results = seachMap(term, h.server.context.world.WorldConstructions, results, "/worldconstruction")
results = seachMap(term, h.server.context.world.DanceForms, results, "/danceForm")
results = seachMap(term, h.server.context.world.MusicalForms, results, "/musicalForm")
results = seachMap(term, h.server.context.world.PoeticForms, results, "/poeticForm")
results = seachMap(term, h.server.context.world.WrittenContents, results, "/writtencontent")
results = seachMap(term, h.server.context.world.Landmasses, results, "/landmass")
results = seachMap(term, h.server.context.world.MountainPeaks, results, "/mountain")
sort.Slice(results, func(i, j int) bool { return results[i].Label < results[j].Label })
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(results)
}
func seachMap[T model.Named](s string, input map[int]T, output []SearchResult, baseUrl string) []SearchResult {
for id, v := range input {
if strings.Contains(v.Name(), s) {
output = append(output, SearchResult{
Label: util.Title(v.Name()),
Value: fmt.Sprintf("%s/%d", baseUrl, id),
})
}
}
return output
}

View File

@ -2,22 +2,17 @@ package server
import (
"embed"
"encoding/json"
"fmt"
"io/fs"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"strconv"
"github.com/gorilla/mux"
"github.com/robertjanetzko/LegendsBrowser2/backend/model"
"github.com/robertjanetzko/LegendsBrowser2/backend/templates"
"github.com/robertjanetzko/LegendsBrowser2/backend/util"
"github.com/shirou/gopsutil/disk"
)
type DfServerContext struct {
@ -57,7 +52,20 @@ func StartServer(world *model.DfWorld, static embed.FS) {
srv.RegisterWorldPage("/structures", "structures.html", func(p Parms) any {
return flatGrouped(srv.context.world.Sites, func(s *model.Site) []*model.Structure { return util.Values(s.Structures) })
})
srv.RegisterWorldResourcePage("/structure/{id}", "site.html", func(id int) any { return srv.context.world.Sites[id/100].Structures[id%100] })
srv.RegisterWorldPage("/site/{siteId}/structure/{id}", "structure.html", func(p Parms) any {
siteId, err := strconv.Atoi(p["siteId"])
if err != nil {
return nil
}
structureId, err := strconv.Atoi(p["id"])
if err != nil {
return nil
}
if site, ok := srv.context.world.Sites[siteId]; ok {
return site.Structures[structureId]
}
return nil
})
srv.RegisterWorldPage("/worldconstructions", "worldconstructions.html", func(p Parms) any { return grouped(srv.context.world.WorldConstructions) })
srv.RegisterWorldResourcePage("/worldconstruction/{id}", "worldconstruction.html", func(id int) any { return srv.context.world.WorldConstructions[id] })
@ -66,7 +74,7 @@ func StartServer(world *model.DfWorld, static embed.FS) {
srv.RegisterWorldResourcePage("/artifact/{id}", "artifact.html", func(id int) any { return srv.context.world.Artifacts[id] })
srv.RegisterWorldPage("/artforms", "artforms.html", func(p Parms) any {
return struct {
return &struct {
DanceForms map[string][]*model.DanceForm
MusicalForms map[string][]*model.MusicalForm
PoeticForms map[string][]*model.PoeticForm
@ -123,10 +131,7 @@ func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Println(path)
index, err := h.staticFS.ReadFile(h.staticPath + "/" + h.indexPath)
if err != nil {
err = h.server.templates.Render(w, "notFound.html", nil)
if err != nil {
httpError(w, err)
}
h.server.notFound(w)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
@ -149,112 +154,10 @@ func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.FileServer(http.FS(statics)).ServeHTTP(w, r)
}
type loadHandler struct {
server *DfServer
}
type loadProgress struct {
Msg string `json:"msg"`
Progress float64 `json:"progress"`
Done bool `json:"done"`
}
func (h loadHandler) Progress() *loadProgress {
percent := 0.0
p := h.server.context.progress
if p.ProgressBar != nil {
percent = float64(p.ProgressBar.Current()*100) / float64(p.ProgressBar.Total())
}
return &loadProgress{
Msg: h.server.context.progress.Message,
Progress: percent,
Done: h.server.context.world != nil,
}
}
func (h loadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/load/progress" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(h.Progress())
return
}
var partitions []string
if runtime.GOOS == "windows" {
ps, _ := disk.Partitions(false)
partitions = util.Map(ps, func(p disk.PartitionStat) string { return p.Mountpoint + `\` })
} else {
partitions = append(partitions, "/")
}
path := r.URL.Query().Get("p")
p := &paths{
Partitions: partitions,
Current: path,
}
if p.Current == "" {
p.Current = "."
}
var err error
p.Current, err = filepath.Abs(p.Current)
func (srv *DfServer) notFound(w http.ResponseWriter) {
err := srv.templates.Render(w, "notFound.html", nil)
if err != nil {
httpError(w, err)
return
}
if f, err := os.Stat(p.Current); err == nil {
if f.IsDir() {
p.List, err = ioutil.ReadDir(p.Current)
if err != nil {
httpError(w, err)
return
}
err = h.server.templates.Render(w, "load.html", p)
if err != nil {
httpError(w, err)
}
return
} else {
h.server.context.isLoading = true
h.server.context.world = nil
go loadWorld(h.server, p.Current)
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
}
http.Redirect(w, r, "/load", http.StatusSeeOther)
}
func isLegendsXml(f fs.FileInfo) bool {
return strings.HasSuffix(f.Name(), "-legends.xml")
}
func loadWorld(server *DfServer, file string) {
runtime.GC()
wrld, _ := model.Parse(file, server.context.progress)
server.context.world = wrld
server.context.isLoading = false
}
type paths struct {
Current string
List []fs.FileInfo
Partitions []string
}
func (srv *DfServer) renderLoading(w http.ResponseWriter, r *http.Request) {
if srv.context.isLoading {
err := srv.templates.Render(w, "loading.html", srv.loader.Progress())
if err != nil {
httpError(w, err)
}
} else {
http.Redirect(w, r, "/load", http.StatusSeeOther)
}
}
@ -303,49 +206,3 @@ func grouped[K comparable, T namedTyped](input map[K]T) map[string][]T {
return output
}
type searchHandler struct {
server *DfServer
}
type SearchResult struct {
Label string `json:"label"`
Value string `json:"value"`
}
func (h searchHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
term := r.URL.Query().Get("term")
var results []SearchResult
results = seachMap(term, h.server.context.world.HistoricalFigures, results, "/hf")
results = seachMap(term, h.server.context.world.Entities, results, "/entity")
results = seachMap(term, h.server.context.world.Sites, results, "/site")
results = seachMap(term, h.server.context.world.Regions, results, "/region")
results = seachMap(term, h.server.context.world.Artifacts, results, "/artifavt")
results = seachMap(term, h.server.context.world.WorldConstructions, results, "/worldconstruction")
results = seachMap(term, h.server.context.world.DanceForms, results, "/danceForm")
results = seachMap(term, h.server.context.world.MusicalForms, results, "/musicalForm")
results = seachMap(term, h.server.context.world.PoeticForms, results, "/poeticForm")
results = seachMap(term, h.server.context.world.WrittenContents, results, "/writtencontent")
results = seachMap(term, h.server.context.world.Landmasses, results, "/landmass")
results = seachMap(term, h.server.context.world.MountainPeaks, results, "/mountain")
sort.Slice(results, func(i, j int) bool { return results[i].Label < results[j].Label })
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(results)
}
func seachMap[T model.Named](s string, input map[int]T, output []SearchResult, baseUrl string) []SearchResult {
for id, v := range input {
if strings.Contains(v.Name(), s) {
output = append(output, SearchResult{
Label: util.Title(v.Name()),
Value: fmt.Sprintf("%s/%d", baseUrl, id),
})
}
}
return output
}

View File

@ -2,6 +2,14 @@ a {
text-decoration: none;
}
.object-table td {
white-space: nowrap;
}
td.object {
white-space: nowrap;
}
.hf {
color: #3366CC;
}

View File

@ -25,7 +25,7 @@
<div class="tab-content" id="nav-tabContent">
{{- range $t, $v := .DanceForms }}
<div class="tab-pane active" id="nav-{{kebab $t}}" role="tabpanel" aria-labelledby="nav-home-tab">
<table>
<table class="table table-hover table-sm table-borderless">
<tr>
<th width="100%">Name</th>
<th>Size</th>
@ -41,7 +41,7 @@
{{- end}}
{{- range $t, $v := .MusicalForms }}
<div class="tab-pane" id="nav-{{kebab $t}}" role="tabpanel" aria-labelledby="nav-home-tab">
<table>
<table class="table table-hover table-sm table-borderless">
<tr>
<th width="100%">Name</th>
<th>Size</th>
@ -57,7 +57,7 @@
{{- end}}
{{- range $t, $v := .PoeticForms }}
<div class="tab-pane" id="nav-{{kebab $t}}" role="tabpanel" aria-labelledby="nav-home-tab">
<table>
<table class="table table-hover table-sm table-borderless object-table">
<tr>
<th width="100%">Name</th>
<th>Size</th>

View File

@ -16,7 +16,7 @@
<div class="tab-content" id="nav-tabContent">
{{- range $t, $v := . }}
<div class="tab-pane{{ ifFirst $ $t " active" }}" id="nav-{{kebab $t}}" role="tabpanel" aria-labelledby="nav-home-tab">
<table>
<table class="table table-hover table-sm table-borderless object-table">
<tr>
<th width="100%">Name</th>
<th>Type</th>

View File

@ -16,7 +16,7 @@
<div class="tab-content" id="nav-tabContent">
{{- range $t, $v := . }}
<div class="tab-pane{{ ifFirst $ $t " active" }}" id="nav-{{kebab $t}}" role="tabpanel" aria-labelledby="nav-home-tab">
<table>
<table class="table table-hover table-sm table-borderless object-table">
<tr>
<th width="100%">Name</th>
<th>Race</th>

View File

@ -10,7 +10,7 @@
<table>
{{- range .Sites }}
<tr>
<td> {{ site . }}</td>
<td class="object" style="vertical-align: top;"> {{ site . }}</td>
<td> {{ template "events.html" events (history .) }}</td>
</tr>
{{- end }}

View File

@ -1,4 +1,4 @@
<ul>
<ul class="mb-0">
{{- range $event := .Events }}
<li>
[{{ $event.Id }}] In {{ time $event.Year $event.Seconds72 }}, {{

View File

@ -16,7 +16,7 @@
<div class="tab-content" id="nav-tabContent">
{{- range $t, $v := . }}
<div class="tab-pane{{ ifFirst $ $t " active" }}" id="nav-{{kebab $t}}" role="tabpanel" aria-labelledby="nav-home-tab">
<table>
<table class="table table-hover table-sm table-borderless object-table">
<tr>
<th width="100%">Name</th>
<th>Size</th>

View File

@ -16,7 +16,7 @@
<div class="tab-content" id="nav-tabContent">
{{- range $t, $v := . }}
<div class="tab-pane{{ ifFirst $ $t " active" }}" id="nav-{{kebab $t}}" role="tabpanel" aria-labelledby="nav-home-tab">
<table>
<table class="table table-hover table-sm table-borderless object-table">
<tr>
<th width="100%">Name</th>
<th>Size</th>

View File

@ -0,0 +1,13 @@
{{template "layout.html" .}}
{{define "title"}}{{ title .Name }}{{end}}
{{define "content"}}
<h3>{{ title .Name }}</h3>
{{ .Type_ }}
<h5 class="mt-3">Events</h5>
{{ template "events.html" events . }}
<p>{{ json . }}</p>
{{- end }}

View File

@ -16,13 +16,15 @@
<div class="tab-content" id="nav-tabContent">
{{- range $t, $v := . }}
<div class="tab-pane{{ ifFirst $ $t " active" }}" id="nav-{{kebab $t}}" role="tabpanel" aria-labelledby="nav-home-tab">
<table>
<table class="table table-hover table-sm table-borderless object-table">
<tr>
<th width="100%">Name</th>
<th>Name</th>
<th width="100%">Site</th>
</tr>
{{- range $v }}{{- if not (eq .Name "") }}
<tr>
<td>{{ structure .SiteId .Id }}</td>
<td>{{ site .SiteId }}</td>
</tr>
{{- end}}{{- end}}
</table>

View File

@ -16,7 +16,7 @@
<div class="tab-content" id="nav-tabContent">
{{- range $t, $v := . }}
<div class="tab-pane{{ ifFirst $ $t " active" }}" id="nav-{{kebab $t}}" role="tabpanel" aria-labelledby="nav-home-tab">
<table>
<table class="table table-hover table-sm table-borderless object-table">
<tr>
<th width="100%">Name</th>
<th>Size</th>

View File

@ -16,7 +16,7 @@
<div class="tab-content" id="nav-tabContent">
{{- range $t, $v := . }}
<div class="tab-pane{{ ifFirst $ $t " active" }}" id="nav-{{kebab $t}}" role="tabpanel" aria-labelledby="nav-home-tab">
<table>
<table class="table table-hover table-sm table-borderless object-table">
<tr>
<th width="100%">Name</th>
<th>Size</th>