diff --git a/README.md b/README.md new file mode 100644 index 0000000..34428ea --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# ddServer + +This is a simple Webserver, which helps zou creating monsters for a dungeons & dragons game + +## Installation +Either clone the repo and build it your self. +- You need to have go installed for that + +Or download the latest release and start your server with +```bash +./ddServer +``` diff --git a/go.mod b/go.mod index 7db1ebd..dfe5d47 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,7 @@ module ddServer go 1.21.4 -require ( - github.com/stretchr/testify v1.8.4 - golang.org/x/text v0.14.0 -) +require github.com/stretchr/testify v1.8.4 require ( github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index e228b7b..fa4b6e6 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,6 @@ 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/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/handlers/about_handler.go b/handlers/about_handler.go index 5f8a1dd..1d628e0 100644 --- a/handlers/about_handler.go +++ b/handlers/about_handler.go @@ -25,7 +25,7 @@ func AboutHandler(content embed.FS) http.HandlerFunc { } // Execute the template with the provided data - data := map[string]interface{}{ + data := map[string]any{ "Title": "Dungeons & Dragons Monster Generator", } err = tmpl.ExecuteTemplate(w, "about", data) diff --git a/handlers/add_monster_handler.go b/handlers/add_monster_handler.go index 5174c3a..38cb1a1 100644 --- a/handlers/add_monster_handler.go +++ b/handlers/add_monster_handler.go @@ -2,14 +2,10 @@ package handlers import ( "ddServer/model" - "fmt" "log" "net/http" "strconv" "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. @@ -98,32 +94,32 @@ func parseMonster(r *http.Request) model.Monster { Wis: parseInt(r.FormValue("wis")), Cha: parseInt(r.FormValue("cha")), Save: model.Save{ - Dex: checkCheckbox("savedex", r), - Con: checkCheckbox("savecon", r), - Wis: checkCheckbox("savewis", r), - Str: checkCheckbox("savestr", r), - Cha: checkCheckbox("savecha", r), - Int: checkCheckbox("saveint", r), + Dex: r.FormValue("saveDex"), + Con: r.FormValue("saveCon"), + Wis: r.FormValue("saveWis"), + Str: r.FormValue("saveStr"), + Cha: r.FormValue("saveCha"), + Int: r.FormValue("saveInt"), }, Skill: model.Skill{ - Perception: checkCheckbox("perception", r), - Stealth: checkCheckbox("stealth", r), - Acrobatics: checkCheckbox("acrobatics", r), - AnimalHandling: checkCheckbox("animalhandling", r), - Arcana: checkCheckbox("arcana", r), - Athletics: checkCheckbox("athletics", r), - Deception: checkCheckbox("deception", r), - History: checkCheckbox("history", r), - Insight: checkCheckbox("insight", r), - Intimidation: checkCheckbox("intimidation", r), - Investigation: checkCheckbox("investigation", r), - Medicine: checkCheckbox("medicine", r), - Nature: checkCheckbox("nature", r), - Performance: checkCheckbox("performance", r), - Persuasion: checkCheckbox("persuasion", r), - SleightOfHand: checkCheckbox("sleightofhand", r), - Survival: checkCheckbox("survival", r), - Religion: checkCheckbox("religion", r), + Perception: r.FormValue("perception"), + Stealth: r.FormValue("stealth"), + Acrobatics: r.FormValue("acrobatics"), + AnimalHandling: r.FormValue("animalHandling"), + Arcana: r.FormValue("arcana"), + Athletics: r.FormValue("athletics"), + Deception: r.FormValue("deception"), + History: r.FormValue("history"), + Insight: r.FormValue("insight"), + Intimidation: r.FormValue("intimidation"), + Investigation: r.FormValue("investigation"), + Medicine: r.FormValue("medicine"), + Nature: r.FormValue("nature"), + Performance: r.FormValue("performance"), + Persuasion: r.FormValue("persuasion"), + SleightOfHand: r.FormValue("sleightOfHand"), + Survival: r.FormValue("survival"), + Religion: r.FormValue("religion"), }, Resist: []string{r.FormValue("resist")}, ConditionImmune: []string{r.FormValue("conditionImmune")}, @@ -146,10 +142,3 @@ 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 "" -} diff --git a/handlers/contact_handler.go b/handlers/contact_handler.go index a01cad4..611abc2 100644 --- a/handlers/contact_handler.go +++ b/handlers/contact_handler.go @@ -23,7 +23,7 @@ func ContactHandler(content embed.FS) http.HandlerFunc { } // Execute the contact template - err = tmpl.ExecuteTemplate(w, "contact", map[string]interface{}{ + err = tmpl.ExecuteTemplate(w, "contact", map[string]any{ "Title": "Dungeons & Dragons Monster Generator", }) if err != nil { diff --git a/handlers/form_handler.go b/handlers/form_handler.go index c31cee4..1232463 100644 --- a/handlers/form_handler.go +++ b/handlers/form_handler.go @@ -42,7 +42,7 @@ func FormHandler(content embed.FS, monsters *[]model.Monster) http.HandlerFunc { } // Execute the template and render the response. - data := map[string]interface{}{ + data := map[string]any{ "Title": "Dungeons & Dragons Monster Generator", "Monsters": *monsters, } diff --git a/handlers/load_file_handler.go b/handlers/load_file_handler.go deleted file mode 100644 index cfaaac4..0000000 --- a/handlers/load_file_handler.go +++ /dev/null @@ -1,46 +0,0 @@ -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) - } -} diff --git a/handlers/main_handler.go b/handlers/main_handler.go index 19d2cdf..e4b575e 100644 --- a/handlers/main_handler.go +++ b/handlers/main_handler.go @@ -16,7 +16,7 @@ func MainHandler(content embed.FS, monsters *[]model.Monster) http.HandlerFunc { log.Print("MainHandler called") // 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", "templates/skills.html") + tmpl, err := template.ParseFS(content, "templates/main.html", "templates/monsterForm.html", "templates/monster.html", "templates/monsterTable.html", "templates/base.html") if err != nil { log.Printf("Template parsing error: %v\n", err) http.Error(w, err.Error(), http.StatusInternalServerError) @@ -28,7 +28,7 @@ func MainHandler(content embed.FS, monsters *[]model.Monster) http.HandlerFunc { defer mu.Unlock() // Execute the main template with the provided data - err = tmpl.ExecuteTemplate(w, "main", map[string]interface{}{ + err = tmpl.ExecuteTemplate(w, "main", map[string]any{ "Title": "Dungeons & Dragons Monster Generator", "Monsters": *monsters, }) diff --git a/handlers/monster_table_handler.go b/handlers/monster_table_handler.go index 4755386..f227962 100644 --- a/handlers/monster_table_handler.go +++ b/handlers/monster_table_handler.go @@ -24,7 +24,7 @@ func MonsterTableHandler(content embed.FS, monsters *[]model.Monster) http.Handl } // Execute the template and pass the necessary data - err = tmpl.ExecuteTemplate(w, "monsterTable", map[string]interface{}{ + err = tmpl.ExecuteTemplate(w, "monsterTable", map[string]any{ "Title": "Dungeons & Dragons Monster Generator", "Monsters": *monsters, }) diff --git a/handlers/skill_calculation_handler.go b/handlers/skill_calculation_handler.go index 55ce57b..48935cf 100644 --- a/handlers/skill_calculation_handler.go +++ b/handlers/skill_calculation_handler.go @@ -8,44 +8,40 @@ import ( "strconv" ) -// SkillCalculationHandler is an http.HandlerFunc triggered by htmx when the user makes entries in certain fields and then populates the skill fields. +// SkillCalculationHandler ist ein http.HandlerFunc, der von htmx getriggert wird, +// wenn der Benutzer Einträge in bestimmten Feldern macht, und dann die Skill-Felder befüllt. func SkillCalculationHandler(content embed.FS) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - // Check if the request is a POST request. + log.Print("SkillCalculationHandler called") + + // Überprüfen Sie, ob die Anfrage eine POST-Anfrage ist. if r.Method != http.MethodPost { + log.Print("Method not allowed") http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } - // Parse form data. + // Parse Formulardaten. err := r.ParseForm() if err != nil { + log.Printf("Error parsing form data: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - // Parse template files. - tmplFiles := []string{ - "templates/base.html", - "templates/header.html", - "templates/skills.html", - "templates/main.html", - "templates/footer.html", - "templates/about.html", - } + 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...) if err != nil { + log.Printf("Template parsing error: %v\n", err) http.Error(w, err.Error(), http.StatusBadRequest) return } - // Parse form field values and calculate skill values. str := parseFieldValue(r.FormValue("str")) dex := parseFieldValue(r.FormValue("dex")) int := parseFieldValue(r.FormValue("int")) cha := parseFieldValue(r.FormValue("cha")) wis := parseFieldValue(r.FormValue("wis")) - con := parseFieldValue(r.FormValue("con")) cr := parseFieldValue(r.FormValue("cr")) crBonus := calcBonus(cr) @@ -68,124 +64,79 @@ func SkillCalculationHandler(content embed.FS) http.HandlerFunc { "sleightOfHand": strconv.Itoa(calcAbilityScore(dex) + crBonus), "stealth": strconv.Itoa(calcAbilityScore(dex) + 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) if err != nil { + log.Printf("Template execution error: %v\n", err) 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 { - switch { - case cr >= 0 && cr < 5: - log.Println("Bonus calculated for credit rating:", cr) + if cr >= 0 && cr < 5 { return 2 - case cr >= 5 && cr < 9: - log.Println("Bonus calculated for credit rating:", cr) + } else if cr >= 5 && cr < 9 { return 3 - case cr >= 9 && cr < 14: - log.Println("Bonus calculated for credit rating:", cr) + } else if cr >= 9 && cr < 14 { return 4 - case cr >= 14 && cr < 18: - log.Println("Bonus calculated for credit rating:", cr) + } else if cr >= 14 && cr < 18 { return 5 - case cr >= 18 && cr < 21: - log.Println("Bonus calculated for credit rating:", cr) + } else if cr >= 18 && cr < 21 { return 6 - case cr >= 21 && cr < 25: - log.Println("Bonus calculated for credit rating:", cr) + } else if cr >= 21 && cr < 25 { return 7 - case cr >= 25 && cr < 28: - log.Println("Bonus calculated for credit rating:", cr) + } else if cr >= 25 && cr < 28 { return 8 - case cr >= 28 && cr < 31: - log.Println("Bonus calculated for credit rating:", cr) + } else if cr >= 28 && cr < 31 { return 9 - default: - log.Println("Invalid credit rating:", cr) + } else { return 0 } } -// calcAbilityScore calculates the ability score based on the given value. func calcAbilityScore(val int) int { - switch { - case val < 2: - log.Println("Ability Score: -5") + if val < 2 { return -5 - case val < 4: - log.Println("Ability Score: -4") + } else if val >= 2 && val < 4 { return -4 - case val < 6: - log.Println("Ability Score: -3") + } else if val >= 4 && val < 6 { return -3 - case val < 8: - log.Println("Ability Score: -2") + } else if val >= 6 && val < 8 { return -2 - case val < 10: - log.Println("Ability Score: -1") + } else if val >= 8 && val < 10 { return -1 - case val < 12: - log.Println("Ability Score: 0") + } else if val >= 10 && val < 12 { return 0 - case val < 14: - log.Println("Ability Score: 1") + } else if val >= 12 && val < 14 { return 1 - case val < 16: - log.Println("Ability Score: 2") + } else if val >= 14 && val < 16 { return 2 - case val < 18: - log.Println("Ability Score: 3") + } else if val >= 16 && val < 18 { return 3 - case val < 20: - log.Println("Ability Score: 4") + } else if val >= 18 && val < 20 { return 4 - case val < 22: - log.Println("Ability Score: 5") + } else if val >= 20 && val < 22 { return 5 - case val < 24: - log.Println("Ability Score: 6") + } else if val >= 22 && val < 24 { return 6 - case val < 26: - log.Println("Ability Score: 7") + } else if val >= 24 && val < 26 { return 7 - case val < 28: - log.Println("Ability Score: 8") + } else if val >= 26 && val < 28 { return 8 - case val < 30: - log.Println("Ability Score: 9") + } else if val >= 28 && val < 30 { return 9 - default: - log.Println("Ability Score: 10") + } else { 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 { - // 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) if err != nil { log.Printf("Error converting field value to integer: %v", err) 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 } diff --git a/main.go b/main.go index 5bbd46f..d163e79 100644 --- a/main.go +++ b/main.go @@ -37,7 +37,6 @@ func main() { routes.HandleFunc("/contact", handlers.ContactHandler(content)) routes.HandleFunc("/monsterTable", handlers.MonsterTableHandler(content, &Monsters)) routes.HandleFunc("/calculate-skills", handlers.SkillCalculationHandler(content)) - routes.HandleFunc("/loadFile", handlers.LoadFileHandler(&Monsters)) // Print the message indicating that 'static' has been included. log.Printf("Eingebunden is %v\n", static) diff --git a/templates/contact.html b/templates/contact.html index c8481f1..ce0085d 100644 --- a/templates/contact.html +++ b/templates/contact.html @@ -1,58 +1,57 @@ {{ define "contact" }}
-
-
-
-

Contact Us

-
-
+
+
+
+

Contact Us

+
+
-
-

Our Contact Information

-

You can reach us through the following channels:

-
    -
  • Email: example@example.com
  • -
  • Phone: +123456789
  • -
-
- -
-

Contact Form

-
-
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
-
- -
-
-
-
-
+
+

Our Contact Information

+

You can reach us through the following channels:

+
    +
  • Email: example@example.com
  • +
  • Phone: +123456789
  • +
+ +
+

Contact Form

+
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+
+
+
{{ end }} diff --git a/templates/footer.html b/templates/footer.html index 9b0881a..cf8d261 100644 --- a/templates/footer.html +++ b/templates/footer.html @@ -6,4 +6,4 @@

-{{ end }} \ No newline at end of file +{{ end }} diff --git a/templates/header.html b/templates/header.html index 46c13ec..0efea19 100644 --- a/templates/header.html +++ b/templates/header.html @@ -78,4 +78,4 @@ }); -{{ end }} \ No newline at end of file +{{ end }} diff --git a/templates/main.html b/templates/main.html index 359f1a9..ed4581f 100644 --- a/templates/main.html +++ b/templates/main.html @@ -1,131 +1,109 @@ {{ define "main" }}
-
-
-
-

Monster Form

-
-
-
-
- -
-
-
- +
+
+
+

Monster Form

-
- -
-
- -
+
+ +
+ +
+
+
+
+
+ +
+
+ {{ template "monsterform" . }} + + +
-
- {{ template "monsterform" . }} -
-
- -
-
- - - -
+
-
-
-
-
-

Existing Monsters

-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ template "monsterTable" }} -
NameSourceSizeTypeAlignmentACAC FormHP AverageHP FormulaWalkSwimBurrowClimbFlyStrDexConIntWisChaSave DexSave ConSave WisSave StrSave IntSave ChaPerceptionStealthAcrobaticsAnimalHandlingArcanaAthleticsDeceptionHistoryInsightIntimidationInvestigationMedicineNaturePerformancePersuasionSleightOfHandSurvivalReligionDamage ResistanceDamage ImmuneVulnerableCondition ImmuneSensesLanguagesCRTrait NameTrait EntryAction NameAction Entry
-
+
+
+
+

Existing Monsters

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ template "monsterTable" }} +
NameSourceSizeTypeAlignmentACAC FormHP AverageHP FormulaWalkSwimBurrowClimbFlyStrDexConIntWisChaSave DexSave ConSave WisSave StrSave ConSave ChaPerceptionStealthAcrobaticsAnimalHandlingArcanaAthleticsDeceptionHistoryInsightIntimidationInvestigationMedicineNaturePerformancePersuasionSleightOfHandSurvivalReligionDamage ResistanceDamage ImmuneVulnerableCondition ImmuneSensesLanguagesCRTrait NameTrait EntryAction NameAction Entry
+
+
-
-{{ end }} \ No newline at end of file +{{ end }} diff --git a/templates/monster.html b/templates/monster.html index 0053cea..d3a59d6 100644 --- a/templates/monster.html +++ b/templates/monster.html @@ -24,7 +24,7 @@ {{.Save.Con}} {{.Save.Wis}} {{.Save.Str}} - {{.Save.Int}} + {{.Save.Con}} {{.Save.Cha}} {{.Skill.Perception}} {{.Skill.Stealth}} @@ -56,4 +56,4 @@ {{range .Actions}}{{.Name}}{{end}} {{range .Actions}}{{range .Entries}}{{.}}{{end}}{{end}} -{{ end }} \ No newline at end of file +{{ end }} diff --git a/templates/monsterForm.html b/templates/monsterForm.html index 5903e0d..ca69873 100644 --- a/templates/monsterForm.html +++ b/templates/monsterForm.html @@ -87,6 +87,61 @@
+
+
+

+ Speed +

+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+
@@ -154,14 +209,20 @@
- -
- {{ template "skills" }} +
+
+ +
+ +
+
+

- Speed + Save

@@ -169,50 +230,61 @@
- +
- +
- +
- +
- +
- -
-
-
-
-
-
-
- -
- -
-
-
-
-
- -
- +
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ {{ template "skills" }} +
@@ -303,4 +375,4 @@
-{{end}} \ No newline at end of file +{{end}} diff --git a/templates/monsterTable.html b/templates/monsterTable.html index 9eb1b75..d5aef50 100644 --- a/templates/monsterTable.html +++ b/templates/monsterTable.html @@ -4,4 +4,4 @@ {{ template "monster" . }} {{ end }} -{{ end }} \ No newline at end of file +{{ end }} diff --git a/templates/skills.html b/templates/skills.html index acd9da9..9322260 100644 --- a/templates/skills.html +++ b/templates/skills.html @@ -1,265 +1,185 @@ {{ define "skills" }} -
-
-
-

- Save -

-
-
-
-
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
-
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
-
-
-
-
-
-

- Skill -

-
-
-
-
-
-
- -
- +

+ Skill +

+ +
+
+
+
+
+ +
+ -
-
-
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
- -
- -
-
+
+
+
+ +
+
-
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
- -
- -
-
+
+
+
+ +
+
-
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
- -
- -
-
+
+
+
+
+
+ +
+
-
-
-
- -
- -
-
-
-
-
- -
- -
-
-
-
-
- -
- -
-
+
+
+
+ +
+
-
-
-
- -
- -
-
+
+
+
+ +
+
-
-
- -
- -
-
+
+
+
+
+
+
+ +
+
-
-
- -
- -
-
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+
-{{ end }} \ No newline at end of file +{{ end }}