big work on templating, added middleware
108
assets/dk.css
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
In general, we don't want to stray too far from the original appearance.
|
||||
We'll optimize the layout for modern CSS and use better styles and fonts
|
||||
for legibility and design, but keep the soul of the original project.
|
||||
*/
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
color: #222;
|
||||
font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 1rem;
|
||||
background-image: url("/assets/images/backgrounds/snowstorm.jpg");
|
||||
}
|
||||
|
||||
div#container {
|
||||
width: 90vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0px auto;
|
||||
}
|
||||
|
||||
header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
section#game {
|
||||
display: grid;
|
||||
grid-template-columns: 180px 1fr 180px;
|
||||
margin: 1rem 0;
|
||||
border-top: 2px solid #000;
|
||||
|
||||
& > aside#left {
|
||||
grid-column: 1;
|
||||
border-right: 2px solid #000;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
& > main {
|
||||
grid-column: 2;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
& > aside#right {
|
||||
grid-column: 3;
|
||||
border-left: 2px solid #000;
|
||||
padding: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: #663300;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
|
||||
&:hover {
|
||||
color: #330000;
|
||||
}
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.light {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.title {
|
||||
border: solid 1px black;
|
||||
background-color: #eeeeee;
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
padding: 0.5rem 0;
|
||||
|
||||
& > div {
|
||||
flex: 0 0 25%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
BIN
assets/images/backgrounds/background.gif
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
assets/images/backgrounds/classic.jpg
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
assets/images/backgrounds/snowstorm.jpg
Normal file
After Width: | Height: | Size: 386 KiB |
BIN
assets/images/bars_green.gif
Normal file
After Width: | Height: | Size: 94 B |
BIN
assets/images/bars_red.gif
Normal file
After Width: | Height: | Size: 94 B |
BIN
assets/images/bars_yellow.gif
Normal file
After Width: | Height: | Size: 94 B |
BIN
assets/images/button_character.gif
Normal file
After Width: | Height: | Size: 527 B |
BIN
assets/images/button_fastspells.gif
Normal file
After Width: | Height: | Size: 575 B |
BIN
assets/images/button_functions.gif
Normal file
After Width: | Height: | Size: 561 B |
BIN
assets/images/button_help.gif
Normal file
After Width: | Height: | Size: 402 B |
BIN
assets/images/button_inventory.gif
Normal file
After Width: | Height: | Size: 551 B |
BIN
assets/images/button_location.gif
Normal file
After Width: | Height: | Size: 486 B |
BIN
assets/images/button_login.gif
Normal file
After Width: | Height: | Size: 474 B |
BIN
assets/images/button_logout.gif
Normal file
After Width: | Height: | Size: 500 B |
BIN
assets/images/button_register.gif
Normal file
After Width: | Height: | Size: 523 B |
BIN
assets/images/button_shoutbox.gif
Normal file
After Width: | Height: | Size: 565 B |
BIN
assets/images/button_spells.gif
Normal file
After Width: | Height: | Size: 469 B |
BIN
assets/images/button_status.gif
Normal file
After Width: | Height: | Size: 469 B |
BIN
assets/images/button_towns.gif
Normal file
After Width: | Height: | Size: 461 B |
BIN
assets/images/compass.webp
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
assets/images/dkforumsbutton.gif
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
assets/images/help_exploring.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
assets/images/help_fighting.jpg
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
assets/images/icon_armor.gif
Normal file
After Width: | Height: | Size: 147 B |
BIN
assets/images/icon_shield.gif
Normal file
After Width: | Height: | Size: 121 B |
BIN
assets/images/icon_weapon.gif
Normal file
After Width: | Height: | Size: 112 B |
BIN
assets/images/logo.gif
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
assets/images/map.gif
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
assets/images/title_exploring.gif
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/images/title_fighting.gif
Normal file
After Width: | Height: | Size: 1005 B |
BIN
assets/images/town_1.gif
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/images/town_2.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/images/town_3.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/images/town_4.gif
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/images/town_5.gif
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
assets/images/town_6.gif
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/images/town_7.gif
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/images/town_8.gif
Normal file
After Width: | Height: | Size: 1.7 KiB |
4
internal/middleware/doc.go
Normal file
@ -0,0 +1,4 @@
|
||||
// Package middleware provides reusable HTTP middleware for the Dragon Knight server.
|
||||
// Middleware functions wrap request handlers to add cross-cutting functionality
|
||||
// like timing, logging, authentication, and request processing.
|
||||
package middleware
|
49
internal/middleware/timing.go
Normal file
@ -0,0 +1,49 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"dk/internal/router"
|
||||
)
|
||||
|
||||
const RequestTimerKey = "request_start_time"
|
||||
|
||||
// Timing adds request timing functionality
|
||||
func Timing() router.Middleware {
|
||||
return func(next router.Handler) router.Handler {
|
||||
return func(ctx router.Ctx, params []string) {
|
||||
startTime := time.Now()
|
||||
ctx.SetUserValue(RequestTimerKey, startTime)
|
||||
|
||||
next(ctx, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetRequestTime returns the total request processing time in seconds (formatted)
|
||||
func GetRequestTime(ctx router.Ctx) string {
|
||||
startTime, ok := ctx.UserValue(RequestTimerKey).(time.Time)
|
||||
if !ok {
|
||||
return "0"
|
||||
}
|
||||
|
||||
duration := time.Since(startTime)
|
||||
seconds := duration.Seconds()
|
||||
|
||||
if seconds < 0.001 {
|
||||
return "0"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.3f", seconds)
|
||||
}
|
||||
|
||||
// GetRequestDuration returns the raw duration
|
||||
func GetRequestDuration(ctx router.Ctx) time.Duration {
|
||||
startTime, ok := ctx.UserValue(RequestTimerKey).(time.Time)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
return time.Since(startTime)
|
||||
}
|
@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"dk/internal/middleware"
|
||||
"dk/internal/router"
|
||||
"dk/internal/template"
|
||||
|
||||
@ -23,9 +24,12 @@ func Start(port string) error {
|
||||
// Initialize router
|
||||
r := router.New()
|
||||
|
||||
// Add timing middleware
|
||||
r.Use(middleware.Timing())
|
||||
|
||||
// Hello world endpoint
|
||||
r.Get("/", func(ctx router.Ctx, params []string) {
|
||||
tmpl, err := templateCache.Load("hello.html")
|
||||
tmpl, err := templateCache.Load("layout.html")
|
||||
if err != nil {
|
||||
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
|
||||
fmt.Fprintf(ctx, "Template error: %v", err)
|
||||
@ -33,8 +37,12 @@ func Start(port string) error {
|
||||
}
|
||||
|
||||
data := map[string]any{
|
||||
"title": "Dragon Knight",
|
||||
"message": "Hello World!",
|
||||
"title": "Dragon Knight",
|
||||
"content": "Hello World!",
|
||||
"totaltime": middleware.GetRequestTime(ctx),
|
||||
"numqueries": "0", // Placeholder for now
|
||||
"version": "1.0.0",
|
||||
"build": "dev",
|
||||
}
|
||||
|
||||
tmpl.WriteTo(ctx, data)
|
||||
@ -46,7 +54,7 @@ func Start(port string) error {
|
||||
// Static file server for /assets
|
||||
fs := &fasthttp.FS{
|
||||
Root: assetsDir,
|
||||
Compress: true,
|
||||
Compress: false,
|
||||
}
|
||||
assetsHandler := fs.NewRequestHandler()
|
||||
|
||||
|
43
templates/layout.html
Normal file
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{title}</title>
|
||||
|
||||
<link rel="stylesheet" href="/assets/dk.css">
|
||||
|
||||
<script>
|
||||
function open_char_popup()
|
||||
{
|
||||
window.open("/showchar", "", "width=210,height=500,scrollbars")
|
||||
}
|
||||
|
||||
function open_map_popup()
|
||||
{
|
||||
window.open("/showmap", "", "width=520,height=520,scrollbars")
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<header>
|
||||
<div><img src="/assets/images/logo.gif" alt="Dragon Knight" title="Dragon Knight"></div>
|
||||
<nav>{topnav}</nav>
|
||||
</header>
|
||||
|
||||
<section id="game">
|
||||
<aside id="left">{leftside}</aside>
|
||||
<main>{content}</main>
|
||||
<aside id="right">{rightside}</aside>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div>Powered by <a href="/">Dragon Knight</a></div>
|
||||
<div>© 2025 Sharkk</div>
|
||||
<div>{totaltime} Seconds, {numqueries} Queries</div>
|
||||
<div>Version {version} {build}</div>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|