add air config, some style fixes, finish first inn page

This commit is contained in:
Sky Johnson 2025-08-12 14:16:18 -05:00
parent ae7b4a3066
commit 56aa3afd4f
8 changed files with 120 additions and 39 deletions

52
.air.toml Normal file
View File

@ -0,0 +1,52 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ."
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", "templates"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
silent = false
time = false
[misc]
clean_on_exit = false
[proxy]
app_port = 0
enabled = false
proxy_port = 0
[screen]
clear_on_rebuild = false
keep_scroll = true

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
/dk.db
/dk.db-*
/sessions.json
/tmp

View File

@ -245,16 +245,16 @@ form.standard {
}
div.town {
& > div:not(:last-child) {
& > section:not(:last-child) {
margin-bottom: 2rem;
}
& > div.split {
& > section.split {
width: 100%;
display: flex;
gap: 1rem;
& > div {
& > section {
width: 100%;
}
}

View File

@ -13,6 +13,7 @@ func RegisterTownRoutes(r *router.Router) {
group.Use(middleware.RequireTown())
group.Get("/", showTown)
group.Get("/inn", showInn)
}
func showTown(ctx router.Ctx, _ []string) {
@ -23,3 +24,10 @@ func showTown(ctx router.Ctx, _ []string) {
"whosonline": components.GenerateTownWhosOnline(),
})
}
func showInn(ctx router.Ctx, _ []string) {
town := ctx.UserValue("town").(*towns.Town)
components.RenderPage(ctx, town.Name+" Inn", "town/inn.html", map[string]any{
"town": town,
})
}

View File

@ -43,15 +43,15 @@ func RightAside(ctx router.Ctx) map[string]any {
}
hpPct := helpers.ClampPct(float64(user.HP), float64(user.MaxHP), 0, 100)
data["hpPct"] = hpPct
data["mpPct"] = helpers.ClampPct(float64(user.MP), float64(user.MaxMP), 0, 100)
data["tpPct"] = helpers.ClampPct(float64(user.TP), float64(user.MaxTP), 0, 100)
data["hppct"] = hpPct
data["mppct"] = helpers.ClampPct(float64(user.MP), float64(user.MaxMP), 0, 100)
data["tppct"] = helpers.ClampPct(float64(user.TP), float64(user.MaxTP), 0, 100)
data["hpColor"] = ""
data["hpcolor"] = ""
if hpPct < 35 {
data["hpColor"] = "danger"
data["hpcolor"] = "danger"
} else if hpPct < 75 {
data["hpColor"] = "warning"
data["hpcolor"] = "warning"
}
// Build known healing spells list

View File

@ -140,8 +140,8 @@ func (t *Template) RenderNamed(data map[string]any) string {
result = t.processLoops(result, data)
result = t.processConditionals(result, data)
// Process yield before variable substitution
result = t.processYield(result, blocks)
// Process yield with conditionals in blocks
result = t.processYield(result, blocks, data)
// Apply data substitutions
for key, value := range data {
@ -273,6 +273,20 @@ func (t *Template) getStructField(obj any, fieldName string) any {
return field.Interface()
}
func (t *Template) getLength(value any) int {
if value == nil {
return 0
}
rv := reflect.ValueOf(value)
switch rv.Kind() {
case reflect.Slice, reflect.Array, reflect.Map, reflect.String:
return rv.Len()
default:
return 0
}
}
func (t *Template) WriteTo(ctx *fasthttp.RequestCtx, data any) {
var result string
@ -371,12 +385,16 @@ func (t *Template) processBlocks(content string, blocks map[string]string) strin
}
// processYield handles {yield} directives for template inheritance
func (t *Template) processYield(content string, blocks map[string]string) string {
func (t *Template) processYield(content string, blocks map[string]string, data map[string]any) string {
result := content
for blockName, blockContent := range blocks {
// Process conditionals and loops in block content before yielding
processedBlock := t.processLoops(blockContent, data)
processedBlock = t.processConditionals(processedBlock, data)
yieldPlaceholder := fmt.Sprintf("{yield \"%s\"}", blockName)
result = strings.ReplaceAll(result, yieldPlaceholder, blockContent)
result = strings.ReplaceAll(result, yieldPlaceholder, processedBlock)
}
// Replace any remaining {yield} with empty string
@ -714,20 +732,6 @@ func (t *Template) isTruthy(value any) bool {
}
}
func (t *Template) getLength(value any) int {
if value == nil {
return 0
}
rv := reflect.ValueOf(value)
switch rv.Kind() {
case reflect.Slice, reflect.Array, reflect.Map, reflect.String:
return rv.Len()
default:
return 0
}
}
// RenderToContext is a simplified helper that renders a template and writes it to the request context
// with error handling. Returns true if successful, false if an error occurred (error is written to response).
func RenderToContext(ctx *fasthttp.RequestCtx, templateName string, data map[string]any) bool {

View File

@ -1,3 +1,19 @@
{include "layout.html"}
{block "content"}
<div class="town inn">
</div>
<div class="title"><h3>{town.Name} Inn</h3></div>
{if user.Gold < town.InnCost}
<p>You do not have enough gold to stay at this Inn tonight.</p>
<p>You may return to <a href="/town">town</a>, or use the compass on the left to explore.</p>
{else}
Resting at the inn will refill your current HP, MP, and TP to their maximum levels.<br><br>
A night's sleep at this Inn will cost you <b>{town.InnCost} gold</b>. Is that ok?<br><br>
<form action="/town/inn" method="post">
<button class="btn" type="submit">Yes</button>
<a href="/town"><button class="btn">No</button></a>
</form>
{/if}
</div>
{/block}

View File

@ -2,7 +2,7 @@
{block "content"}
<div class="town">
<div class="options">
<section class="options">
<div class="title"><img src="/assets/images/town_{town.ID}.gif" alt="Welcome to {town.Name}" title="Welcome to {town.Name}"></div>
<b>Town Options</b><br>
<ul class="unstyled">
@ -10,21 +10,21 @@
<li><a href="/town/shop">Browse the Shop</a></li>
<li><a href="/town/maps">Buy Maps</a></li>
</ul>
</div>
</section>
<div class="news">
<section class="news">
{newscontent}
</div>
</section>
<div class="split">
<div class="whos-online">
<section class="split">
<section class="whos-online">
{whosonline}
</div>
</section>
<div class="babblebox">
<section class="babblebox">
<div class="title">Babblebox</div>
@TODO
</div>
</div>
</section>
</section>
</div>
{/block}