You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
144 lines
4.3 KiB
144 lines
4.3 KiB
package templates
|
|
|
|
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>
|
|
<!-- Bootstrap CSS -->
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<script src="/static/js/htmx.min.js"></script>
|
|
<!-- Bootstrap JS Bundle with Popper -->
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
|
</head>
|
|
<body class="bg-light">
|
|
<div class="container py-4">
|
|
<header class="mb-4">
|
|
<h1 class="text-center">Derby Race Timer</h1>
|
|
</header>
|
|
|
|
<div class="mb-4 text-center">
|
|
<div id="race-status" class="badge bg-primary fs-5 px-3 py-2">Ready</div>
|
|
</div>
|
|
|
|
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-4 g-4 mb-4">
|
|
@laneComponents()
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-center gap-3">
|
|
<button
|
|
hx-post="/api/reset"
|
|
hx-swap="none"
|
|
class="btn btn-primary">
|
|
Reset Race
|
|
</button>
|
|
<button
|
|
hx-post="/api/force-end"
|
|
hx-swap="none"
|
|
class="btn btn-danger">
|
|
Force End Race
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Set up SSE connection
|
|
console.log("Setting up SSE connection...");
|
|
|
|
const eventSource = new EventSource('/api/events');
|
|
|
|
eventSource.onopen = function() {
|
|
console.log("SSE connection opened");
|
|
};
|
|
|
|
eventSource.onerror = function(error) {
|
|
console.error("SSE connection error:", error);
|
|
};
|
|
|
|
eventSource.addEventListener('debug', function(event) {
|
|
console.log("Debug event received:", event.data);
|
|
});
|
|
|
|
eventSource.addEventListener('lane-finish', function(event) {
|
|
console.log("Lane finish event received:", event.data);
|
|
try {
|
|
const laneFinishData = JSON.parse(event.data);
|
|
const lane = document.getElementById(`lane-${laneFinishData.lane}`);
|
|
if (lane) {
|
|
console.log(`Updating lane ${laneFinishData.lane} with time ${laneFinishData.time}`);
|
|
lane.classList.add('bg-success-subtle');
|
|
lane.querySelector('.time').textContent = laneFinishData.time.toFixed(4);
|
|
|
|
const placeEl = lane.querySelector('.place');
|
|
placeEl.textContent = `${getOrdinal(laneFinishData.place)} Place`;
|
|
placeEl.classList.remove('d-none');
|
|
} else {
|
|
console.error(`Lane element not found for lane ${laneFinishData.lane}`);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error processing lane finish event:", error);
|
|
}
|
|
});
|
|
|
|
eventSource.addEventListener('status', function(event) {
|
|
console.log("Status event received:", event.data);
|
|
try {
|
|
const statusData = JSON.parse(event.data);
|
|
let statusText = 'Unknown';
|
|
let statusClass = 'bg-secondary';
|
|
|
|
if (statusData.status === 'idle') {
|
|
statusText = 'Ready';
|
|
statusClass = 'bg-primary';
|
|
|
|
// Reset all lanes
|
|
document.querySelectorAll('.lane').forEach(lane => {
|
|
lane.classList.remove('bg-success-subtle');
|
|
lane.querySelector('.time').textContent = '--.--.---';
|
|
lane.querySelector('.place').classList.add('d-none');
|
|
});
|
|
} else if (statusData.status === 'running') {
|
|
statusText = 'Race Running';
|
|
statusClass = 'bg-success';
|
|
} else if (statusData.status === 'finished') {
|
|
statusText = 'Race Complete';
|
|
statusClass = 'bg-info';
|
|
}
|
|
|
|
const statusEl = document.getElementById('race-status');
|
|
statusEl.textContent = statusText;
|
|
statusEl.className = `badge fs-5 px-3 py-2 ${statusClass}`;
|
|
} catch (error) {
|
|
console.error("Error processing status event:", error);
|
|
}
|
|
});
|
|
|
|
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 class="col">
|
|
<div id={ "lane-" + string(rune('0' + lane)) } class="lane card h-100">
|
|
<div class="card-body text-center">
|
|
<h5 class="card-title">Lane { string(rune('0' + lane)) }</h5>
|
|
<div class="time display-6 font-monospace my-3">--.--.---</div>
|
|
<div class="place badge bg-success fs-6 d-none"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
} |