Add templating

This commit is contained in:
2025-10-15 09:32:32 +02:00
parent 3b24e64131
commit f9c5ccc378
8 changed files with 596 additions and 60 deletions

View File

@@ -3,6 +3,7 @@ package server
import (
"fmt"
"html"
"html/template"
"io/fs"
"log"
"net/http"
@@ -36,7 +37,7 @@ type Config struct {
// Server hosts the Luxtools HTTP handlers.
type Server struct {
mux *http.ServeMux
indexPage []byte
templates *template.Template
staticFS fs.FS
mediaFS http.FileSystem
@@ -60,9 +61,10 @@ func New(cfg Config) (*Server, error) {
return nil, fmt.Errorf("media directory must be a directory: %s", mediaDir)
}
indexPage, err := webbundle.Content.ReadFile("templates/index.html")
// Parse all templates
tmpl, err := template.ParseFS(webbundle.Content, "templates/*.html")
if err != nil {
return nil, fmt.Errorf("failed to read index template: %w", err)
return nil, fmt.Errorf("failed to parse templates: %w", err)
}
staticFS, err := fs.Sub(webbundle.Content, "static")
@@ -72,7 +74,7 @@ func New(cfg Config) (*Server, error) {
s := &Server{
mux: http.NewServeMux(),
indexPage: indexPage,
templates: tmpl,
staticFS: staticFS,
mediaFS: http.Dir(mediaDir),
}
@@ -103,7 +105,12 @@ func (s *Server) registerRoutes() {
func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/":
s.sendHTML(w, http.StatusOK, s.indexPage)
data := DefaultMenuBar()
s.renderTemplate(w, http.StatusOK, "index.html", data)
case "/admin":
data := AdminMenuBar()
data.Title = "Admin Panel"
s.renderTemplate(w, http.StatusOK, "admin.html", data)
case "/counter":
s.handleCounter(w, r)
case "/time":
@@ -127,6 +134,16 @@ func (s *Server) handleCounter(w http.ResponseWriter, r *http.Request) {
s.sendHTMLString(w, http.StatusOK, renderCounter(count))
}
func (s *Server) renderTemplate(w http.ResponseWriter, status int, name string, data interface{}) {
w.Header().Set("Content-Type", htmlContentType)
w.WriteHeader(status)
if err := s.templates.ExecuteTemplate(w, name, data); err != nil {
log.Printf("template execution failed: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}
func (s *Server) sendHTML(w http.ResponseWriter, status int, body []byte) {
w.Header().Set("Content-Type", htmlContentType)
w.WriteHeader(status)

View File

@@ -0,0 +1,86 @@
package server
import "html/template"
// This file defines the data structures and helper functions for the template system.
//
// The template system uses Go's html/template package to provide:
// - Dynamic menu bars that can be customized per page
// - Template inheritance via layout.html
// - Type-safe data passing via structs
//
// To create a new page with a custom menu:
// 1. Create a PageData struct with your menu configuration
// 2. Call s.renderTemplate(w, status, "yourpage.html", data)
// 3. In yourpage.html, use {{template "layout" .}} and define content blocks
//
// See TEMPLATE_GUIDE.md for detailed examples and usage patterns.
// PageData holds the data passed to page templates.
type PageData struct {
Title string
MenuGroups []MenuGroup
ShowClock bool
}
// MenuGroup represents a dropdown menu in the navbar.
type MenuGroup struct {
LabelHTML template.HTML
Items []MenuItem
}
// MenuItem represents a single menu item.
type MenuItem struct {
LabelHTML template.HTML
URL string
IsDivider bool
}
// DefaultMenuBar returns the standard menu configuration.
func DefaultMenuBar() PageData {
return PageData{
ShowClock: true,
MenuGroups: []MenuGroup{
{
LabelHTML: template.HTML(`<span class="red-168-text">F</span>ile`),
Items: []MenuItem{
{LabelHTML: template.HTML(`<span class="red-168-text">N</span>ew`), URL: "#!"},
{LabelHTML: template.HTML(`<span class="red-168-text">O</span>pen`), URL: "#!"},
{LabelHTML: template.HTML(`<span class="red-168-text">S</span>ave`), URL: "#!"},
{LabelHTML: template.HTML(`Save <span class="red-168-text">A</span>s`), URL: "#!"},
{IsDivider: true},
{LabelHTML: template.HTML(`<span class="red-168-text">E</span>xit`), URL: "#!"},
},
},
{
LabelHTML: template.HTML(`<span class="red-168-text">E</span>dit`),
Items: []MenuItem{
{LabelHTML: template.HTML(`<span class="red-168-text">C</span>ut`), URL: "#!"},
{LabelHTML: template.HTML(`C<span class="red-168-text">o</span>py`), URL: "#!"},
{LabelHTML: template.HTML(`<span class="red-168-text">P</span>aste`), URL: "#!"},
},
},
{
LabelHTML: template.HTML(`<span class="red-168-text">H</span>elp`),
Items: []MenuItem{
{LabelHTML: template.HTML(`<span class="red-168-text">D</span>ocumentation`), URL: "#!"},
{LabelHTML: template.HTML(`<span class="red-168-text">A</span>bout`), URL: "#!"},
},
},
},
}
}
// AdminMenuBar returns a menu configuration with admin options.
func AdminMenuBar() PageData {
data := DefaultMenuBar()
data.MenuGroups = append(data.MenuGroups, MenuGroup{
LabelHTML: template.HTML(`<span class="red-168-text">A</span>dmin`),
Items: []MenuItem{
{LabelHTML: template.HTML(`<span class="red-168-text">U</span>sers`), URL: "/admin/users"},
{LabelHTML: template.HTML(`<span class="red-168-text">S</span>ettings`), URL: "/admin/settings"},
{LabelHTML: template.HTML(`<span class="red-168-text">L</span>ogs`), URL: "/admin/logs"},
},
})
return data
}