change to templ templates

main
DustyP 9 months ago
parent fef6edeea1
commit 46f20448db

@ -3,11 +3,12 @@ module track-gopher
go 1.24
require (
github.com/a-h/templ v0.3.833
github.com/go-chi/chi/v5 v5.2.1
go.bug.st/serial v1.6.2
)
require (
github.com/creack/goselect v0.1.2 // indirect
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect
golang.org/x/sys v0.28.0 // indirect
)

@ -1,16 +1,20 @@
github.com/a-h/templ v0.3.833 h1:L/KOk/0VvVTBegtE0fp2RJQiBm7/52Zxv5fqlEHiQUU=
github.com/a-h/templ v0.3.833/go.mod h1:cAu4AiZhtJfBjMY0HASlyzvkrtjnHWPeEsyGK2YYmfk=
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8=
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -3,7 +3,6 @@ package web
import (
"embed"
"fmt"
"html/template"
"io/fs"
"net/http"
"time"
@ -12,16 +11,16 @@ import (
"github.com/go-chi/chi/v5/middleware"
"track-gopher/derby"
"track-gopher/web/templates"
)
//go:embed templates static
//go:embed static
var content embed.FS
// Server represents the web server for the derby clock
type Server struct {
router *chi.Mux
clock *derby.DerbyClock
templates *template.Template
raceEvents chan derby.Event
clients map[chan string]bool
port int
@ -29,17 +28,10 @@ type Server struct {
// NewServer creates a new web server
func NewServer(clock *derby.DerbyClock, port int) (*Server, error) {
// Parse templates
templates, err := template.ParseFS(content, "web/templates/*.html", "web/templates/**/*.html")
if err != nil {
return nil, fmt.Errorf("failed to parse templates: %w", err)
}
// Create server
s := &Server{
router: chi.NewRouter(),
clock: clock,
templates: templates,
raceEvents: make(chan derby.Event, 10),
clients: make(map[chan string]bool),
port: port,
@ -119,7 +111,7 @@ func (s *Server) forwardEvents() {
// handleIndex handles the index page
func (s *Server) handleIndex() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
s.templates.ExecuteTemplate(w, "index.html", nil)
templates.Index().Render(r.Context(), w)
}
}

@ -0,0 +1,111 @@
package templates
import "track-gopher/derby"
templ Index() {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Derby Race Timer</title>
<link href="/static/css/tailwind.css" rel="stylesheet" />
<script src="/static/js/htmx.min.js"></script>
</head>
<body class="bg-gray-100 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="mb-8">
<h1 class="text-3xl font-bold text-center">Derby Race Timer</h1>
</header>
<div class="mb-6 flex justify-center">
<div id="race-status" class="px-4 py-2 rounded-full text-white bg-blue-600">Ready</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
{ laneComponents() }
</div>
<div class="flex justify-center space-x-4">
<button
hx-post="/api/reset"
hx-swap="none"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Reset Race
</button>
<button
hx-post="/api/force-end"
hx-swap="none"
class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">
Force End Race
</button>
</div>
</div>
<script>
// Set up SSE connection
const eventSource = new EventSource('/api/events');
eventSource.addEventListener('lane-finish', function(event) {
const laneFinishData = JSON.parse(event.data);
const lane = document.getElementById(`lane-${laneFinishData.lane}`);
if (lane) {
lane.classList.add('finished');
lane.querySelector('.time').textContent = laneFinishData.time.toFixed(4);
const placeEl = lane.querySelector('.place');
placeEl.textContent = `${getOrdinal(laneFinishData.place)} Place`;
placeEl.classList.remove('hidden');
}
});
eventSource.addEventListener('status', function(event) {
const statusData = JSON.parse(event.data);
let statusText = 'Unknown';
let statusClass = 'bg-gray-500';
if (statusData.status === 'idle') {
statusText = 'Ready';
statusClass = 'bg-blue-600';
// Reset all lanes
document.querySelectorAll('.lane').forEach(lane => {
lane.classList.remove('finished');
lane.querySelector('.time').textContent = '--.--.---';
lane.querySelector('.place').classList.add('hidden');
});
} else if (statusData.status === 'running') {
statusText = 'Race Running';
statusClass = 'bg-green-600';
} else if (statusData.status === 'finished') {
statusText = 'Race Complete';
statusClass = 'bg-purple-600';
}
document.getElementById('race-status').textContent = statusText;
document.getElementById('race-status').className = `px-4 py-2 rounded-full text-white ${statusClass}`;
});
function getOrdinal(n) {
const s = ["th", "st", "nd", "rd"];
const v = n % 100;
return n + (s[(v-20)%10] || s[v] || s[0]);
}
</script>
</body>
</html>
}
templ laneComponents() {
for i := 1; i <= 4; i++ {
@laneComponent(i)
}
}
templ laneComponent(lane int) {
<div id={ "lane-" + string(rune('0' + lane)) } class="lane bg-white p-4 rounded shadow">
<h2 class="text-xl font-semibold mb-2">Lane { string(rune('0' + lane)) }</h2>
<div class="text-3xl font-mono mb-2 time">--.--.---</div>
<div class="place text-lg font-bold text-green-600 hidden"></div>
</div>
}

@ -0,0 +1,141 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.833
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import "track-gopher/derby"
func Index() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Derby Race Timer</title><link href=\"/static/css/tailwind.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script></head><body class=\"bg-gray-100 min-h-screen\"><div class=\"container mx-auto px-4 py-8\"><header class=\"mb-8\"><h1 class=\"text-3xl font-bold text-center\">Derby Race Timer</h1></header><div class=\"mb-6 flex justify-center\"><div id=\"race-status\" class=\"px-4 py-2 rounded-full text-white bg-blue-600\">Ready</div></div><div class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(laneComponents())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/index.templ`, Line: 26, Col: 22}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div><div class=\"flex justify-center space-x-4\"><button hx-post=\"/api/reset\" hx-swap=\"none\" class=\"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded\">Reset Race</button> <button hx-post=\"/api/force-end\" hx-swap=\"none\" class=\"bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded\">Force End Race</button></div></div><script>\r\n\t\t\t// Set up SSE connection\r\n\t\t\tconst eventSource = new EventSource('/api/events');\r\n\t\t\t\r\n\t\t\teventSource.addEventListener('lane-finish', function(event) {\r\n\t\t\t\tconst laneFinishData = JSON.parse(event.data);\r\n\t\t\t\tconst lane = document.getElementById(`lane-${laneFinishData.lane}`);\r\n\t\t\t\tif (lane) {\r\n\t\t\t\t\tlane.classList.add('finished');\r\n\t\t\t\t\tlane.querySelector('.time').textContent = laneFinishData.time.toFixed(4);\r\n\t\t\t\t\t\r\n\t\t\t\t\tconst placeEl = lane.querySelector('.place');\r\n\t\t\t\t\tplaceEl.textContent = `${getOrdinal(laneFinishData.place)} Place`;\r\n\t\t\t\t\tplaceEl.classList.remove('hidden');\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\teventSource.addEventListener('status', function(event) {\r\n\t\t\t\tconst statusData = JSON.parse(event.data);\r\n\t\t\t\tlet statusText = 'Unknown';\r\n\t\t\t\tlet statusClass = 'bg-gray-500';\r\n\t\t\t\t\r\n\t\t\t\tif (statusData.status === 'idle') {\r\n\t\t\t\t\tstatusText = 'Ready';\r\n\t\t\t\t\tstatusClass = 'bg-blue-600';\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Reset all lanes\r\n\t\t\t\t\tdocument.querySelectorAll('.lane').forEach(lane => {\r\n\t\t\t\t\t\tlane.classList.remove('finished');\r\n\t\t\t\t\t\tlane.querySelector('.time').textContent = '--.--.---';\r\n\t\t\t\t\t\tlane.querySelector('.place').classList.add('hidden');\r\n\t\t\t\t\t});\r\n\t\t\t\t} else if (statusData.status === 'running') {\r\n\t\t\t\t\tstatusText = 'Race Running';\r\n\t\t\t\t\tstatusClass = 'bg-green-600';\r\n\t\t\t\t} else if (statusData.status === 'finished') {\r\n\t\t\t\t\tstatusText = 'Race Complete';\r\n\t\t\t\t\tstatusClass = 'bg-purple-600';\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tdocument.getElementById('race-status').textContent = statusText;\r\n\t\t\t\tdocument.getElementById('race-status').className = `px-4 py-2 rounded-full text-white ${statusClass}`;\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tfunction getOrdinal(n) {\r\n\t\t\t\tconst s = [\"th\", \"st\", \"nd\", \"rd\"];\r\n\t\t\t\tconst v = n % 100;\r\n\t\t\t\treturn n + (s[(v-20)%10] || s[v] || s[0]);\r\n\t\t\t}\r\n\t\t</script></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func laneComponents() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
for i := 1; i <= 4; i++ {
templ_7745c5c3_Err = laneComponent(i).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
func laneComponent(lane int) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("lane-" + string(rune('0'+lane)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/index.templ`, Line: 106, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" class=\"lane bg-white p-4 rounded shadow\"><h2 class=\"text-xl font-semibold mb-2\">Lane ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(string(rune('0' + lane)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/index.templ`, Line: 107, Col: 72}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</h2><div class=\"text-3xl font-mono mb-2 time\">--.--.---</div><div class=\"place text-lg font-bold text-green-600 hidden\"></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate
Loading…
Cancel
Save