Compare commits

...
Sign in to create a new pull request.

8 commits

Author SHA1 Message Date
2a47aa90a3 edited formating of templates 2024-02-15 16:17:58 +01:00
Patryk Hegenberg
ff0f06c0b9 frontend: backend: fix formatting errors and errors from tests 2023-12-15 18:43:38 +01:00
Patryk Hegenberg
6db6d78ad9 added checkboxes and check for save attributes 2023-12-15 18:34:16 +01:00
Patryk Hegenberg
88cfe035e0 backend: frontend: add file load feature
added a feature to load existing json files and load the containing monsters
2023-12-15 15:38:43 +01:00
Patryk Hegenberg
32a37eea32 frontend: backend: clean up code for better readability 2023-12-15 09:27:55 +01:00
Patryk Hegenberg
f84ee2a466 added check if skills are included 2023-12-15 08:22:34 +01:00
Patryk Hegenberg
aba6e13663 backend: frontend: added calculation for save values 2023-12-14 20:00:10 +01:00
Patryk Hegenberg
a0d6ff1cff frontend: backend: add logging, comments and cleanup code 2023-12-13 08:03:57 +01:00
15 changed files with 625 additions and 482 deletions

5
go.mod
View file

@ -2,7 +2,10 @@ module ddServer
go 1.21.4 go 1.21.4
require github.com/stretchr/testify v1.8.4 require (
github.com/stretchr/testify v1.8.4
golang.org/x/text v0.14.0
)
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect

2
go.sum
View file

@ -4,6 +4,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -2,10 +2,14 @@ package handlers
import ( import (
"ddServer/model" "ddServer/model"
"fmt"
"log" "log"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
) )
// AddMonster is a http.HandlerFunc that adds a new monster to the Monsters slice. // AddMonster is a http.HandlerFunc that adds a new monster to the Monsters slice.
@ -94,32 +98,32 @@ func parseMonster(r *http.Request) model.Monster {
Wis: parseInt(r.FormValue("wis")), Wis: parseInt(r.FormValue("wis")),
Cha: parseInt(r.FormValue("cha")), Cha: parseInt(r.FormValue("cha")),
Save: model.Save{ Save: model.Save{
Dex: r.FormValue("saveDex"), Dex: checkCheckbox("savedex", r),
Con: r.FormValue("saveCon"), Con: checkCheckbox("savecon", r),
Wis: r.FormValue("saveWis"), Wis: checkCheckbox("savewis", r),
Str: r.FormValue("saveStr"), Str: checkCheckbox("savestr", r),
Cha: r.FormValue("saveCha"), Cha: checkCheckbox("savecha", r),
Int: r.FormValue("saveInt"), Int: checkCheckbox("saveint", r),
}, },
Skill: model.Skill{ Skill: model.Skill{
Perception: r.FormValue("perception"), Perception: checkCheckbox("perception", r),
Stealth: r.FormValue("stealth"), Stealth: checkCheckbox("stealth", r),
Acrobatics: r.FormValue("acrobatics"), Acrobatics: checkCheckbox("acrobatics", r),
AnimalHandling: r.FormValue("animalHandling"), AnimalHandling: checkCheckbox("animalhandling", r),
Arcana: r.FormValue("arcana"), Arcana: checkCheckbox("arcana", r),
Athletics: r.FormValue("athletics"), Athletics: checkCheckbox("athletics", r),
Deception: r.FormValue("deception"), Deception: checkCheckbox("deception", r),
History: r.FormValue("history"), History: checkCheckbox("history", r),
Insight: r.FormValue("insight"), Insight: checkCheckbox("insight", r),
Intimidation: r.FormValue("intimidation"), Intimidation: checkCheckbox("intimidation", r),
Investigation: r.FormValue("investigation"), Investigation: checkCheckbox("investigation", r),
Medicine: r.FormValue("medicine"), Medicine: checkCheckbox("medicine", r),
Nature: r.FormValue("nature"), Nature: checkCheckbox("nature", r),
Performance: r.FormValue("performance"), Performance: checkCheckbox("performance", r),
Persuasion: r.FormValue("persuasion"), Persuasion: checkCheckbox("persuasion", r),
SleightOfHand: r.FormValue("sleightOfHand"), SleightOfHand: checkCheckbox("sleightofhand", r),
Survival: r.FormValue("survival"), Survival: checkCheckbox("survival", r),
Religion: r.FormValue("religion"), Religion: checkCheckbox("religion", r),
}, },
Resist: []string{r.FormValue("resist")}, Resist: []string{r.FormValue("resist")},
ConditionImmune: []string{r.FormValue("conditionImmune")}, ConditionImmune: []string{r.FormValue("conditionImmune")},
@ -142,3 +146,10 @@ func parseMonster(r *http.Request) model.Monster {
}, },
} }
} }
func checkCheckbox(field string, r *http.Request) string {
if r.FormValue(fmt.Sprintf("check%v", cases.Caser(cases.Title(language.Und)).String(field))) == "on" {
return r.FormValue(field)
}
return ""
}

View file

@ -0,0 +1,46 @@
package handlers
import (
"ddServer/model"
"encoding/json"
"fmt"
"log"
"net/http"
)
func LoadFileHandler(monsters *[]model.Monster) http.HandlerFunc {
log.Print("LoadFileHandler called")
return func(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(10 << 20) // 10 MB limit
// Get the file from the request
file, _, err := r.FormFile("uploadFile")
if err != nil {
http.Error(w, "Error retrieving file", http.StatusBadRequest)
return
}
defer file.Close()
// Parse the file content
decoder := json.NewDecoder(file)
var loadedChars model.Character
err = decoder.Decode(&loadedChars)
if err != nil {
http.Error(w, "Error decoding file content", http.StatusInternalServerError)
return
}
// Lock the Monsters slice and append the loaded monsters, then unlock the slice
mu.Lock()
defer mu.Unlock()
// Assuming 'loadedChars' contains an array of Monster objects
for _, monster := range loadedChars.Monster {
*monsters = append(*monsters, monster)
}
fmt.Printf("%v\n", monsters)
// Send a success response
http.Redirect(w, r, "/monsterTable", http.StatusTemporaryRedirect)
}
}

View file

@ -16,7 +16,7 @@ func MainHandler(content embed.FS, monsters *[]model.Monster) http.HandlerFunc {
log.Print("MainHandler called") log.Print("MainHandler called")
// Parse the templates from the embedded file system // Parse the templates from the embedded file system
tmpl, err := template.ParseFS(content, "templates/main.html", "templates/monsterForm.html", "templates/monster.html", "templates/monsterTable.html", "templates/base.html") tmpl, err := template.ParseFS(content, "templates/main.html", "templates/monsterForm.html", "templates/monster.html", "templates/monsterTable.html", "templates/base.html", "templates/skills.html")
if err != nil { if err != nil {
log.Printf("Template parsing error: %v\n", err) log.Printf("Template parsing error: %v\n", err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)

View file

@ -8,40 +8,44 @@ import (
"strconv" "strconv"
) )
// SkillCalculationHandler ist ein http.HandlerFunc, der von htmx getriggert wird, // SkillCalculationHandler is an http.HandlerFunc triggered by htmx when the user makes entries in certain fields and then populates the skill fields.
// wenn der Benutzer Einträge in bestimmten Feldern macht, und dann die Skill-Felder befüllt.
func SkillCalculationHandler(content embed.FS) http.HandlerFunc { func SkillCalculationHandler(content embed.FS) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
log.Print("SkillCalculationHandler called") // Check if the request is a POST request.
// Überprüfen Sie, ob die Anfrage eine POST-Anfrage ist.
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
log.Print("Method not allowed")
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return return
} }
// Parse Formulardaten. // Parse form data.
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
log.Printf("Error parsing form data: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
tmplFiles := []string{"templates/base.html", "templates/header.html", "templates/skills.html", "templates/main.html", "templates/footer.html", "templates/about.html"} // Parse template files.
tmplFiles := []string{
"templates/base.html",
"templates/header.html",
"templates/skills.html",
"templates/main.html",
"templates/footer.html",
"templates/about.html",
}
tmpl, err := template.ParseFS(content, tmplFiles...) tmpl, err := template.ParseFS(content, tmplFiles...)
if err != nil { if err != nil {
log.Printf("Template parsing error: %v\n", err)
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
// Parse form field values and calculate skill values.
str := parseFieldValue(r.FormValue("str")) str := parseFieldValue(r.FormValue("str"))
dex := parseFieldValue(r.FormValue("dex")) dex := parseFieldValue(r.FormValue("dex"))
int := parseFieldValue(r.FormValue("int")) int := parseFieldValue(r.FormValue("int"))
cha := parseFieldValue(r.FormValue("cha")) cha := parseFieldValue(r.FormValue("cha"))
wis := parseFieldValue(r.FormValue("wis")) wis := parseFieldValue(r.FormValue("wis"))
con := parseFieldValue(r.FormValue("con"))
cr := parseFieldValue(r.FormValue("cr")) cr := parseFieldValue(r.FormValue("cr"))
crBonus := calcBonus(cr) crBonus := calcBonus(cr)
@ -64,79 +68,124 @@ func SkillCalculationHandler(content embed.FS) http.HandlerFunc {
"sleightOfHand": strconv.Itoa(calcAbilityScore(dex) + crBonus), "sleightOfHand": strconv.Itoa(calcAbilityScore(dex) + crBonus),
"stealth": strconv.Itoa(calcAbilityScore(dex) + crBonus), "stealth": strconv.Itoa(calcAbilityScore(dex) + crBonus),
"survival": strconv.Itoa(calcAbilityScore(wis) + crBonus), "survival": strconv.Itoa(calcAbilityScore(wis) + crBonus),
"saveStr": strconv.Itoa(calcAbilityScore(str) + crBonus),
"saveWis": strconv.Itoa(calcAbilityScore(wis) + crBonus),
"saveCon": strconv.Itoa(calcAbilityScore(con) + crBonus),
"saveInt": strconv.Itoa(calcAbilityScore(int) + crBonus),
"saveCha": strconv.Itoa(calcAbilityScore(cha) + crBonus),
"saveDex": strconv.Itoa(calcAbilityScore(dex) + crBonus),
} }
// Execute template with skill values.
err = tmpl.ExecuteTemplate(w, "skills", skillValues) err = tmpl.ExecuteTemplate(w, "skills", skillValues)
if err != nil { if err != nil {
log.Printf("Template execution error: %v\n", err)
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
} }
} }
} }
// calcBonus calculates the bonus based on the given credit rating.
// It returns the bonus value as an integer.
func calcBonus(cr int) int { func calcBonus(cr int) int {
if cr >= 0 && cr < 5 { switch {
case cr >= 0 && cr < 5:
log.Println("Bonus calculated for credit rating:", cr)
return 2 return 2
} else if cr >= 5 && cr < 9 { case cr >= 5 && cr < 9:
log.Println("Bonus calculated for credit rating:", cr)
return 3 return 3
} else if cr >= 9 && cr < 14 { case cr >= 9 && cr < 14:
log.Println("Bonus calculated for credit rating:", cr)
return 4 return 4
} else if cr >= 14 && cr < 18 { case cr >= 14 && cr < 18:
log.Println("Bonus calculated for credit rating:", cr)
return 5 return 5
} else if cr >= 18 && cr < 21 { case cr >= 18 && cr < 21:
log.Println("Bonus calculated for credit rating:", cr)
return 6 return 6
} else if cr >= 21 && cr < 25 { case cr >= 21 && cr < 25:
log.Println("Bonus calculated for credit rating:", cr)
return 7 return 7
} else if cr >= 25 && cr < 28 { case cr >= 25 && cr < 28:
log.Println("Bonus calculated for credit rating:", cr)
return 8 return 8
} else if cr >= 28 && cr < 31 { case cr >= 28 && cr < 31:
log.Println("Bonus calculated for credit rating:", cr)
return 9 return 9
} else { default:
log.Println("Invalid credit rating:", cr)
return 0 return 0
} }
} }
// calcAbilityScore calculates the ability score based on the given value.
func calcAbilityScore(val int) int { func calcAbilityScore(val int) int {
if val < 2 { switch {
case val < 2:
log.Println("Ability Score: -5")
return -5 return -5
} else if val >= 2 && val < 4 { case val < 4:
log.Println("Ability Score: -4")
return -4 return -4
} else if val >= 4 && val < 6 { case val < 6:
log.Println("Ability Score: -3")
return -3 return -3
} else if val >= 6 && val < 8 { case val < 8:
log.Println("Ability Score: -2")
return -2 return -2
} else if val >= 8 && val < 10 { case val < 10:
log.Println("Ability Score: -1")
return -1 return -1
} else if val >= 10 && val < 12 { case val < 12:
log.Println("Ability Score: 0")
return 0 return 0
} else if val >= 12 && val < 14 { case val < 14:
log.Println("Ability Score: 1")
return 1 return 1
} else if val >= 14 && val < 16 { case val < 16:
log.Println("Ability Score: 2")
return 2 return 2
} else if val >= 16 && val < 18 { case val < 18:
log.Println("Ability Score: 3")
return 3 return 3
} else if val >= 18 && val < 20 { case val < 20:
log.Println("Ability Score: 4")
return 4 return 4
} else if val >= 20 && val < 22 { case val < 22:
log.Println("Ability Score: 5")
return 5 return 5
} else if val >= 22 && val < 24 { case val < 24:
log.Println("Ability Score: 6")
return 6 return 6
} else if val >= 24 && val < 26 { case val < 26:
log.Println("Ability Score: 7")
return 7 return 7
} else if val >= 26 && val < 28 { case val < 28:
log.Println("Ability Score: 8")
return 8 return 8
} else if val >= 28 && val < 30 { case val < 30:
log.Println("Ability Score: 9")
return 9 return 9
} else { default:
log.Println("Ability Score: 10")
return 10 return 10
} }
} }
// parseFieldValue takes a string value and returns an integer.
// If the string value cannot be converted to an integer, it logs an error and returns 0.
// The function follows these rules:
// - No line is over 66 characters.
func parseFieldValue(value string) int { func parseFieldValue(value string) int {
// Convert the string value to an integer using strconv.Atoi.
// If an error occurs during the conversion, log the error and return 0.
val, err := strconv.Atoi(value) val, err := strconv.Atoi(value)
if err != nil { if err != nil {
log.Printf("Error converting field value to integer: %v", err) log.Printf("Error converting field value to integer: %v", err)
return 0 return 0
} }
// Log the converted integer value for debugging purposes.
log.Printf("Converted field value to integer: %d", val)
// Return the converted integer value.
return val return val
} }

View file

@ -37,6 +37,7 @@ func main() {
routes.HandleFunc("/contact", handlers.ContactHandler(content)) routes.HandleFunc("/contact", handlers.ContactHandler(content))
routes.HandleFunc("/monsterTable", handlers.MonsterTableHandler(content, &Monsters)) routes.HandleFunc("/monsterTable", handlers.MonsterTableHandler(content, &Monsters))
routes.HandleFunc("/calculate-skills", handlers.SkillCalculationHandler(content)) routes.HandleFunc("/calculate-skills", handlers.SkillCalculationHandler(content))
routes.HandleFunc("/loadFile", handlers.LoadFileHandler(&Monsters))
// Print the message indicating that 'static' has been included. // Print the message indicating that 'static' has been included.
log.Printf("Eingebunden is %v\n", static) log.Printf("Eingebunden is %v\n", static)

View file

@ -1,57 +1,58 @@
{{ define "contact" }} {{ define "contact" }}
<div class="tile is-parent"> <div class="tile is-parent">
<div class="tile is-child card "> <div class="tile is-child card ">
<div class="card-content"> <div class="card-content">
<div class="media-content"> <div class="media-content">
<p class="title is-4">Contact Us</p> <p class="title is-4">Contact Us</p>
</div> </div>
<div class="content"> <div class="content">
<div class="contact-info"> <div class="contact-info">
<h2>Our Contact Information</h2> <h2>Our Contact Information</h2>
<p>You can reach us through the following channels:</p> <p>You can reach us through the following channels:</p>
<ul> <ul>
<li>Email: example@example.com</li> <li>Email: example@example.com</li>
<li>Phone: +123456789</li> <li>Phone: +123456789</li>
</ul> </ul>
</div>
<div class="contact-form">
<h2>Contact Form</h2>
<form action="/submitContact" method="post">
<div class="field">
<label for="name">Your Name:</label>
<div class="control">
<input type="text" name="name" required placeholder="Your name"
class="input input-bordered w-full max-w-xs">
</div>
</div>
<div class="field">
<label for="email">Your Email:</label>
<div class="control">
<input type="email" name="email" required placeholder="Your email"
class="input input-bordered w-full max-w-xs">
</div>
</div>
<div class="field">
<label for="message">Your Message:</label>
<div class="control">
<textarea name="message" required placeholder="Type your message here"
class="textarea"></textarea>
</div>
</div>
<div class="field">
<div class="control">
<button type="submit" class="button is-primary">Send Message</button>
</div>
</div>
</form>
</div>
</div>
</div> </div>
<div class="contact-form">
<h2>Contact Form</h2>
<form action="/submitContact" method="post">
<div class="field">
<label for="name">Your Name:</label>
<div class="control">
<input type="text" name="name" required placeholder="Your name"
class="input input-bordered w-full max-w-xs">
</div>
</div>
<div class="field">
<label for="email">Your Email:</label>
<div class="control">
<input type="email" name="email" required placeholder="Your email"
class="input input-bordered w-full max-w-xs">
</div>
</div>
<div class="field">
<label for="message">Your Message:</label>
<div class="control">
<textarea name="message" required placeholder="Type your message here" class="textarea"></textarea>
</div>
</div>
<div class="field">
<div class="control">
<button type="submit" class="button is-primary">Send Message</button>
</div>
</div>
</form>
</div>
</div>
</div> </div>
</div>
</div> </div>
</div> </div>
{{ end }} {{ end }}

View file

@ -6,4 +6,4 @@
</p> </p>
</div> </div>
</footer> </footer>
{{ end }} {{ end }}

View file

@ -78,4 +78,4 @@
}); });
</script> </script>
</header> </header>
{{ end }} {{ end }}

View file

@ -1,109 +1,131 @@
{{ define "main" }} {{ define "main" }}
<div class="tile is-parent"> <div class="tile is-parent">
<div class="tile is-child card "> <div class="tile is-child card ">
<div class="card-content"> <div class="card-content">
<div class="media-content"> <div class="media-content">
<p class="title is-4">Monster Form</p> <p class="title is-4">Monster Form</p>
</div>
<div class="content">
<form hx-post="/loadFile" hx-encoding="multipart/form-data"
_='on htmx:xhr:progress(loaded, total) set #progress.value to (loaded/total)*100'>
<div class="file">
<label class="file-label">
<input class="file-input" type="file" name="uploadFile">
<span class="file-cta">
<span class="file-icon">
<i class="fas fa-upload"></i>
</span>
<span class="file-label">
Choose a file…
</span>
</span>
</label>
</div>
<div class="field">
<div class="control">
<button class="button" type="button" hx-post="/loadFile" hx-trigger="click" hx-target="#monster-table"
hx-swap="outerHTML">Press Me</button>
</div> </div>
<div class="content"> </div>
<form action="/submit" method="post" class=""> </form>
<div class="field"> <form action="/submit" method="post" class="">
<td><label for="filename">Filename:</label></td> <div class="field">
<div class="control"><input type="text" name="filename" required placeholder="Dateiname" <td><label for="filename">Filename:</label></td>
class="input input-bordered w-full max-w-xs"> <div class="control"><input type="text" name="filename" required placeholder="Dateiname"
</div> class="input input-bordered w-full max-w-xs">
</div>
<div class="field">
<div class="control">
<button type="button" hx-post="/addMonster" hx-trigger="click" hx-target="#monster-table"
hx-swap="outerHTML" class="button is-info" hx-boost="true">Add
Monster</button>
</div>
</div>
{{ template "monsterform" . }}
<input type="hidden" name="filename" value="{{.Filename}}">
<input type="submit" value="Submit" class="button is-primary">
</form>
</div> </div>
</div> </div>
{{ template "monsterform" . }}
<div class="field">
<div class="control">
<button type="button" hx-post="/addMonster" hx-trigger="click" hx-target="#monster-table"
hx-swap="outerHTML" class="button is-info" hx-boost="true">Add
Monster</button>
</div>
</div>
<input type="hidden" name="filename" value="{{.Filename}}">
<input type="submit" value="Submit" class="button is-primary">
</form>
</div>
</div> </div>
</div>
</div> </div>
<div class="tile is-parent"> <div class="tile is-parent">
<div class="tile is-child card"> <div class="tile is-child card">
<div class="card-content"> <div class="card-content">
<div class="media-content"> <div class="media-content">
<p class="title is-4 is-centered">Existing Monsters</p> <p class="title is-4 is-centered">Existing Monsters</p>
</div> </div>
<div class="table-container"> <div class="table-container">
<table class="table"> <table class="table">
<colgroup> <colgroup>
<col style="width: 100px;"> <col style="width: 100px;">
<col style="width: 100px;"> <col style="width: 100px;">
</colgroup> </colgroup>
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Source</th> <th>Source</th>
<th>Size</th> <th>Size</th>
<th>Type</th> <th>Type</th>
<th>Alignment</th> <th>Alignment</th>
<th>AC</th> <th>AC</th>
<th>AC Form</th> <th>AC Form</th>
<th>HP Average</th> <th>HP Average</th>
<th>HP Formula</th> <th>HP Formula</th>
<th>Walk</th> <th>Walk</th>
<th>Swim</th> <th>Swim</th>
<th>Burrow</th> <th>Burrow</th>
<th>Climb</th> <th>Climb</th>
<th>Fly</th> <th>Fly</th>
<th>Str</th> <th>Str</th>
<th>Dex</th> <th>Dex</th>
<th>Con</th> <th>Con</th>
<th>Int</th> <th>Int</th>
<th>Wis</th> <th>Wis</th>
<th>Cha</th> <th>Cha</th>
<th>Save Dex</th> <th>Save Dex</th>
<th>Save Con</th> <th>Save Con</th>
<th>Save Wis</th> <th>Save Wis</th>
<th>Save Str</th> <th>Save Str</th>
<th>Save Con</th> <th>Save Int</th>
<th>Save Cha</th> <th>Save Cha</th>
<th>Perception</th> <th>Perception</th>
<th>Stealth</th> <th>Stealth</th>
<th>Acrobatics</th> <th>Acrobatics</th>
<th>AnimalHandling</th> <th>AnimalHandling</th>
<th>Arcana</th> <th>Arcana</th>
<th>Athletics</th> <th>Athletics</th>
<th>Deception</th> <th>Deception</th>
<th>History</th> <th>History</th>
<th>Insight</th> <th>Insight</th>
<th>Intimidation</th> <th>Intimidation</th>
<th>Investigation</th> <th>Investigation</th>
<th>Medicine</th> <th>Medicine</th>
<th>Nature</th> <th>Nature</th>
<th>Performance</th> <th>Performance</th>
<th>Persuasion</th> <th>Persuasion</th>
<th>SleightOfHand</th> <th>SleightOfHand</th>
<th>Survival</th> <th>Survival</th>
<th>Religion</th> <th>Religion</th>
<th>Damage Resistance</th> <th>Damage Resistance</th>
<th>Damage Immune</th> <th>Damage Immune</th>
<th>Vulnerable</th> <th>Vulnerable</th>
<th>Condition Immune</th> <th>Condition Immune</th>
<th>Senses</th> <th>Senses</th>
<th>Languages</th> <th>Languages</th>
<th>CR</th> <th>CR</th>
<th>Trait Name</th> <th>Trait Name</th>
<th>Trait Entry</th> <th>Trait Entry</th>
<th>Action Name</th> <th>Action Name</th>
<th>Action Entry</th> <th>Action Entry</th>
</tr> </tr>
</thead> </thead>
{{ template "monsterTable" }} {{ template "monsterTable" }}
</table> </table>
</div> </div>
</div>
</div> </div>
</div>
</div> </div>
</div> </div>
{{ end }} {{ end }}

View file

@ -24,7 +24,7 @@
<td>{{.Save.Con}}</td> <td>{{.Save.Con}}</td>
<td>{{.Save.Wis}}</td> <td>{{.Save.Wis}}</td>
<td>{{.Save.Str}}</td> <td>{{.Save.Str}}</td>
<td>{{.Save.Con}}</td> <td>{{.Save.Int}}</td>
<td>{{.Save.Cha}}</td> <td>{{.Save.Cha}}</td>
<td>{{.Skill.Perception}}</td> <td>{{.Skill.Perception}}</td>
<td>{{.Skill.Stealth}}</td> <td>{{.Skill.Stealth}}</td>
@ -56,4 +56,4 @@
<td>{{range .Actions}}{{.Name}}{{end}}</td> <td>{{range .Actions}}{{.Name}}{{end}}</td>
<td>{{range .Actions}}{{range .Entries}}{{.}}{{end}}{{end}}</td> <td>{{range .Actions}}{{range .Entries}}{{.}}{{end}}{{end}}</td>
</tr> </tr>
{{ end }} {{ end }}

View file

@ -87,61 +87,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card tile is-ancestor is-vertical">
<header class="card-header">
<p class="card-header-title">
Speed
</p>
</header>
<div class="card-content">
<div class="content">
<div class="tile is-ancestor">
<div class="tile is-parent">
<div class="tile is-child field">
<label for="walk">Walk:</label>
<div class="control">
<input type="number" name="walk" required class="input input-bordered w-full max-w-xs">
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="burrow">Burrow:</label>
<div class="control">
<input type="number" name="burrow" required class="input input-bordered w-full max-w-xs">
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="climb">Climb:</label>
<div class="control">
<input type="number" name="climb" required class="input input-bordered w-full max-w-xs">
</div>
</div>
</div>
</div>
<div class="tile is-ancestor">
<div class="tile is-parent">
<div class="tile is-child field">
<label for="fly">Fly:</label>
<div class="control">
<input type="number" name="fly" required class="input input-bordered w-full max-w-xs">
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="swim">Swim:</label>
<div class="control">
<input type="number" name="swim" required class="input input-bordered w-full max-w-xs">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tile is-ancestor"> <div class="tile is-ancestor">
<div class="tile is-parent"> <div class="tile is-parent">
<div class="tile is-child field"> <div class="tile is-child field">
@ -209,20 +154,14 @@
</div> </div>
</div> </div>
</div> </div>
<div class="tile is-parent"> </div>
<div class="tile is-child field"> <div id="skills" class="tile is-ancestor is-vertical">
<label for="passive">Passive:</label> {{ template "skills" }}
<div class="control">
<input type="number" name="passive" required class="input input-bordered w-full max-w-xs"
hx-post="/calculate-skills" hx-trigger="keyup changed delay:100ms" hx-target="#skills" hx-swap="innerHTML">
</div>
</div>
</div>
</div> </div>
<div class="card tile is-ancestor is-vertical"> <div class="card tile is-ancestor is-vertical">
<header class="card-header"> <header class="card-header">
<p class="card-header-title"> <p class="card-header-title">
Save Speed
</p> </p>
</header> </header>
<div class="card-content"> <div class="card-content">
@ -230,61 +169,50 @@
<div class="tile is-ancestor"> <div class="tile is-ancestor">
<div class="tile is-parent gap"> <div class="tile is-parent gap">
<div class="tile is-child field"> <div class="tile is-child field">
<label for="saveDex">Dex:</label> <label for="walk">Walk:</label>
<div class="control"> <div class="control">
<input type="text" name="saveDex" required class="input input-bordered w-full max-w-xs"> <input type="number" name="walk" required class="input input-bordered w-full max-w-xs">
</div> </div>
</div> </div>
</div> </div>
<div class="tile is-parent gap"> <div class="tile is-parent gap">
<div class="tile is-child field"> <div class="tile is-child field">
<label for="saveCon">Con:</label> <label for="burrow">Burrow:</label>
<div class="control"> <div class="control">
<input type="text" name="saveCon" required class="input input-bordered w-full max-w-xs"> <input type="number" name="burrow" required class="input input-bordered w-full max-w-xs">
</div> </div>
</div> </div>
</div> </div>
<div class="tile is-parent gap"> <div class="tile is-parent gap">
<div class="tile is-child field"> <div class="tile is-child field">
<label for="saveWis">Wis:</label> <label for="climb">Climb:</label>
<div class="control"> <div class="control">
<input type="text" name="saveWis" required class="input input-bordered w-full max-w-xs"> <input type="number" name="climb" required class="input input-bordered w-full max-w-xs">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="tile is-ancestor">
<div class="tile is-ancestor"> <div class="tile is-parent gap">
<div class="tile is-parent gap"> <div class="tile is-child field">
<div class="tile is-child field"> <label for="fly">Fly:</label>
<label for="saveCha">Cha:</label> <div class="control">
<div class="control"> <input type="number" name="fly" required class="input input-bordered w-full max-w-xs">
<input type="text" name="saveCha" required class="input input-bordered w-full max-w-xs"> </div>
</div> </div>
</div> </div>
</div> <div class="tile is-parent gap">
<div class="tile is-parent gap"> <div class="tile is-child field">
<div class="tile is-child field"> <label for="swim">Swim:</label>
<label for="saveInt">Int:</label> <div class="control">
<div class="control"> <input type="number" name="swim" required class="input input-bordered w-full max-w-xs">
<input type="text" name="saveInt" required class="input input-bordered w-full max-w-xs"> </div>
</div>
</div>
</div>
<div class="tile is-parent gap">
<div class="tile is-child field">
<label for="saveStr">Str:</label>
<div class="control">
<input type="text" name="saveStr" required class="input input-bordered w-full max-w-xs">
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div id="skills" class="card tile is-ancestor is-vertical">
{{ template "skills" }}
</div>
<div class="tile is-ancestor"> <div class="tile is-ancestor">
<div class="tile is-parent"> <div class="tile is-parent">
<div class="tile is-child field"> <div class="tile is-child field">
@ -375,4 +303,4 @@
</div> </div>
</div> </div>
</div> </div>
{{end}} {{end}}

View file

@ -4,4 +4,4 @@
{{ template "monster" . }} {{ template "monster" . }}
</tbody> </tbody>
{{ end }} {{ end }}
{{ end }} {{ end }}

View file

@ -1,185 +1,265 @@
{{ define "skills" }} {{ define "skills" }}
<header class="card-header"> <div class="tile is-parent">
<p class="card-header-title"> <div class="card tile is-child is-ancestor is-vertical">
Skill <header class="card-header">
</p> <p class="card-header-title">
</header> Save
<div class="card-content"> </p>
<div class="content"> </header>
<div class="tile is-ancestor"> <div class="card-content">
<div class="tile is-parent"> <div class="content">
<div class="tile is-child field"> <div class="tile is-ancestor">
<label for="acrobatics">Acrobatics:</label> <div class="tile is-parent gap">
<div class="control"> <div class="tile is-child field">
<input type="text" name="acrobatics" required class="input input-bordered w-full max-w-xs" value="{{ <label class="checkbox" for="savedex"><input type="checkbox" name="checkSavedex">Dex:</label>
.acrobatics }}" readonly> <div class="control">
<input type="text" name="savedex" required class="input input-bordered w-full max-w-xs"
value="{{.saveDex}}" readonly>
</div>
</div>
</div>
<div class="tile is-parent gap">
<div class="tile is-child field">
<label class="checkbox" for="savecon"><input type="checkbox" name="checkSavecon">Con:</label>
<div class="control">
<input type="text" name="savecon" required class="input input-bordered w-full max-w-xs"
value="{{.saveCon}}" readonly>
</div>
</div>
</div>
<div class="tile is-parent gap">
<div class="tile is-child field">
<label class="checkbox" for="savewis"><input type="checkbox" name="checkSavewis">Wis:</label>
<div class="control">
<input type="text" name="savewis" required class="input input-bordered w-full max-w-xs"
value="{{.saveWis}}" readonly>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="tile is-parent"> <div class="tile is-ancestor">
<div class="tile is-child field"> <div class="tile is-parent gap">
<label for="animalHandling">Animal Handling:</label> <div class="tile is-child field">
<div class="control"> <label class="checkbox" for="savecha"><input type="checkbox" name="checkSavecha">Cha:</label>
<input type="text" name="animalHandling" required class="input input-bordered w-full max-w-xs" <div class="control">
value="{{ .animalHandling }}" readonly> <input type="text" name="savecha" required class="input input-bordered w-full max-w-xs"
value="{{.saveCha}}" readonly>
</div>
</div> </div>
</div> </div>
</div> <div class="tile is-parent gap">
<div class="tile is-parent"> <div class="tile is-child field">
<div class="tile is-child field"> <label class="checkbox" for="saveint"><input type="checkbox" name="checkSaveint">Int:</label>
<label for="arcana">Arcana:</label> <div class="control">
<div class="control"> <input type="text" name="saveint" required class="input input-bordered w-full max-w-xs"
<input type="text" name="arcana" required class="input input-bordered w-full max-w-xs" value="{{ .arcana }}" value="{{.saveInt}}" readonly>
readonly> </div>
</div> </div>
</div> </div>
</div> <div class="tile is-parent gap">
</div> <div class="tile is-child field">
<div class="tile is-ancestor"> <label class="checkbox" for="savestr"><input type="checkbox" name="checkSavestr">Str:</label>
<div class="tile is-parent"> <div class="control">
<div class="tile is-child field"> <input type="text" name="savestr" required class="input input-bordered w-full max-w-xs"
<label for="athletics">Athletics:</label> value="{{.saveStr}}" readonly>
<div class="control"> </div>
<input type="text" name="athletics" required class="input input-bordered w-full max-w-xs"
value="{{ .athletics }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="deception">Deception:</label>
<div class="control">
<input type="text" name="deception" required class="input input-bordered w-full max-w-xs"
value="{{ .deception }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="history">History:</label>
<div class="control">
<input type="text" name="history" required class="input input-bordered w-full max-w-xs"
value="{{ .history }}" readonly>
</div>
</div>
</div>
</div>
<div class="tile is-ancestor">
<div class="tile is-parent">
<div class="tile is-child field">
<label for="insight">Insight:</label>
<div class="control">
<input type="text" name="insight" required class="input input-bordered w-full max-w-xs"
value="{{ .insight }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="intimidation">Intimidation:</label>
<div class="control">
<input type="text" name="intimidation" required class="input input-bordered w-full max-w-xs"
value="{{ .intimidation }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="investigation">Investigation:</label>
<div class="control">
<input type="text" name="investigation" required class="input input-bordered w-full max-w-xs"
value="{{ .investigation }}" readonly>
</div>
</div>
</div>
</div>
<div class="tile is-ancestor">
<div class="tile is-parent">
<div class="tile is-child field">
<label for="medicine">Medicine:</label>
<div class="control">
<input type="text" name="medicine" required class="input input-bordered w-full max-w-xs"
value="{{ .medicine }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="nature">Nature:</label>
<div class="control">
<input type="text" name="nature" required class="input input-bordered w-full max-w-xs" value="{{ .nature }}"
readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="performance">Performance:</label>
<div class="control">
<input type="text" name="performance" required class="input input-bordered w-full max-w-xs"
value="{{ .performance }}" readonly>
</div>
</div>
</div>
</div>
<div class="tile is-ancestor">
<div class="tile is-parent">
<div class="tile is-child field">
<label for="perception">Perception:</label>
<div class="control">
<input type="text" name="perception" required class="input input-bordered w-full max-w-xs"
value="{{ .perception }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="stealth">Stealth:</label>
<div class="control">
<input type="text" name="stealth" required class="input input-bordered w-full max-w-xs"
value="{{ .stealth }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="persuasion">Persuasion:</label>
<div class="control">
<input type="text" name="persuasion" required class="input input-bordered w-full max-w-xs"
value="{{ .persuasion }}" readonly>
</div>
</div>
</div>
</div>
<div class="tile is-ancestor">
<div class="tile is-parent">
<div class="tile is-child field">
<label for="sleightOfHand">Sleight of Hand:</label>
<div class="control">
<input type="text" name="sleightOfHand" required class="input input-bordered w-full max-w-xs"
value="{{.sleightOfHand }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="religion">Religion:</label>
<div class="control">
<input type="text" name="religion" required class="input input-bordered w-full max-w-xs"
value="{{ .religion }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="survival">Survival:</label>
<div class="control">
<input type="text" name="survival" required class="input input-bordered w-full max-w-xs"
value="{{ .survival }}" readonly>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{{ end }} <div class="tile is-parent">
<div class="card tile is-child is-ancestor is-vertical">
<header class="card-header">
<p class="card-header-title">
Skill
</p>
</header>
<div class="card-content">
<div class="content">
<div class="tile is-ancestor">
<div class="tile is-parent">
<div class="tile is-child field">
<label class="checkbox" for="acrobatics"><input type="checkbox" name="checkAcrobatics">Acrobatics:</label>
<div class="control">
<input type="text" name="acrobatics" required class="input input-bordered w-full max-w-xs" value="{{
.acrobatics }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="animalhandling" class="checkbox"><input type="checkbox" name="checkAnimalhandling">Animal
Handling:</label>
<div class="control">
<input type="text" name="animalhandling" required class="input input-bordered w-full max-w-xs"
value="{{ .animalHandling }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="arcana" class="ceckbox"><input type="checkbox" name="checkArcana">Arcana:</label>
<div class="control">
<input type="text" name="arcana" required class="input input-bordered w-full max-w-xs"
value="{{ .arcana }}" readonly>
</div>
</div>
</div>
</div>
<div class="tile is-ancestor">
<div class="tile is-parent">
<div class="tile is-child field">
<label for="athletics" class="checkbox"><input type="checkbox" name="checkAthletics">Athletics:</label>
<div class="control">
<input type="text" name="athletics" required class="input input-bordered w-full max-w-xs"
value="{{ .athletics }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="deception" class="checkbox"><input type="checkbox" name="checkDeception">Deception:</label>
<div class="control">
<input type="text" name="deception" required class="input input-bordered w-full max-w-xs"
value="{{ .deception }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="history" class="checkbox"><input type="checkbox" name="checkHistory">History:</label>
<div class="control">
<input type="text" name="history" required class="input input-bordered w-full max-w-xs"
value="{{ .history }}" readonly>
</div>
</div>
</div>
</div>
<div class="tile is-ancestor">
<div class="tile is-parent">
<div class="tile is-child field">
<label for="insight" class="checkbox"><input type="checkbox" name="checkInsight">Insight:</label>
<div class="control">
<input type="text" name="insight" required class="input input-bordered w-full max-w-xs"
value="{{ .insight }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="intimidation" class="checkbox"><input type="checkbox"
name="checkIntimidation">Intimidation:</label>
<div class="control">
<input type="text" name="intimidation" required class="input input-bordered w-full max-w-xs"
value="{{ .intimidation }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="investigation" class="checkbox"><input type="checkbox"
name="checkInvestigation">Investigation:</label>
<div class="control">
<input type="text" name="investigation" required class="input input-bordered w-full max-w-xs"
value="{{ .investigation }}" readonly>
</div>
</div>
</div>
</div>
<div class="tile is-ancestor">
<div class="tile is-parent">
<div class="tile is-child field">
<label for="medicine" class="checkbox"><input type="checkbox" name="checkMedicine">Medicine:</label>
<div class="control">
<input type="text" name="medicine" required class="input input-bordered w-full max-w-xs"
value="{{ .medicine }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="nature" class="checkbox"><input type="checkbox" name="checkNature">Nature:</label>
<div class="control">
<input type="text" name="nature" required class="input input-bordered w-full max-w-xs"
value="{{ .nature }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="performance" class="checkbox"><input type="checkbox"
name="checkPerformance">Performance:</label>
<div class="control">
<input type="text" name="performance" required class="input input-bordered w-full max-w-xs"
value="{{ .performance }}" readonly>
</div>
</div>
</div>
</div>
<div class="tile is-ancestor">
<div class="tile is-parent">
<div class="tile is-child field">
<label for="perception" class="checkbox"><input type="checkbox" name="checkPerception">Perception:</label>
<div class="control">
<input type="text" name="perception" required class="input input-bordered w-full max-w-xs"
value="{{ .perception }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="stealth" class="checkbox"><input type="checkbox" name="checkStealth">Stealth:</label>
<div class="control">
<input type="text" name="stealth" required class="input input-bordered w-full max-w-xs"
value="{{ .stealth }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="persuasion" class="checkbox"><input type="checkbox" name="checkPersuasion">Persuasion:</label>
<div class="control">
<input type="text" name="persuasion" required class="input input-bordered w-full max-w-xs"
value="{{ .persuasion }}" readonly>
</div>
</div>
</div>
</div>
<div class="tile is-ancestor">
<div class="tile is-parent">
<div class="tile is-child field">
<label for="sleightofhand" class="checkbox"><input type="checkbox" name="checkSleightofhand">Sleight of
Hand:</label>
<div class="control">
<input type="text" name="sleightofhand" required class="input input-bordered w-full max-w-xs"
value="{{.sleightOfHand }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="religion" class="checkbox"><input type="checkbox" name="checkReligion">Religion:</label>
<div class="control">
<input type="text" name="religion" required class="input input-bordered w-full max-w-xs"
value="{{ .religion }}" readonly>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child field">
<label for="survival" class="checkbox"><input type="checkbox" name="checkSurvival">Survival:</label>
<div class="control">
<input type="text" name="survival" required class="input input-bordered w-full max-w-xs"
value="{{ .survival }}" readonly>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{ end }}