|
|
|
@ -144,65 +144,90 @@ templ RaceManage(groups []models.Group, currentGroup models.Group, heats []model
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
<script>
|
|
|
|
// WebSocket connections
|
|
|
|
// Set up SSE connection
|
|
|
|
const timerSocket = new WebSocket(`${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws/timer`);
|
|
|
|
console.log("Setting up SSE connection...");
|
|
|
|
const adminSocket = new WebSocket(`${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws/admin`);
|
|
|
|
|
|
|
|
const eventsSocket = new WebSocket(`${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/events`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const timerDisplay = document.getElementById('timer');
|
|
|
|
const eventSource = new EventSource('/api/events');
|
|
|
|
const statusIndicator = document.getElementById('status-indicator');
|
|
|
|
|
|
|
|
const gateStatus = document.getElementById('gate-status');
|
|
|
|
|
|
|
|
const gateStatusText = document.getElementById('gate-status-text');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Timer socket handling
|
|
|
|
eventSource.onopen = function() {
|
|
|
|
timerSocket.onmessage = function(event) {
|
|
|
|
console.log("SSE connection opened");
|
|
|
|
const data = JSON.parse(event.data);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (data.type === 'time') {
|
|
|
|
eventSource.onerror = function(error) {
|
|
|
|
timerDisplay.textContent = data.time.toFixed(3);
|
|
|
|
console.error("SSE connection error:", error);
|
|
|
|
} else if (data.type === 'status') {
|
|
|
|
};
|
|
|
|
statusIndicator.textContent = data.status;
|
|
|
|
|
|
|
|
|
|
|
|
eventSource.addEventListener('debug', function(event) {
|
|
|
|
// Update status indicator color
|
|
|
|
console.log("Debug event received:", event.data);
|
|
|
|
statusIndicator.className = 'badge mb-3 ';
|
|
|
|
});
|
|
|
|
if (data.status === 'Ready') {
|
|
|
|
|
|
|
|
statusIndicator.className += 'bg-secondary';
|
|
|
|
eventSource.addEventListener('lane-finish', function(event) {
|
|
|
|
} else if (data.status === 'Running') {
|
|
|
|
console.log("Lane finish event received:", event.data);
|
|
|
|
statusIndicator.className += 'bg-success';
|
|
|
|
try {
|
|
|
|
} else if (data.status === 'Finished') {
|
|
|
|
const laneFinishData = JSON.parse(event.data);
|
|
|
|
statusIndicator.className += 'bg-primary';
|
|
|
|
const laneTimeElement = document.getElementById(`lane-${laneFinishData.lane}-time`);
|
|
|
|
|
|
|
|
|
|
|
|
// Auto-save results when race finishes
|
|
|
|
|
|
|
|
saveHeatResults();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (data.type === 'gate') {
|
|
|
|
|
|
|
|
gateStatusText.textContent = data.status;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Update gate status color
|
|
|
|
|
|
|
|
gateStatus.className = 'alert ';
|
|
|
|
|
|
|
|
if (data.status === 'Open') {
|
|
|
|
|
|
|
|
gateStatus.className += 'alert-danger';
|
|
|
|
|
|
|
|
} else if (data.status === 'Closed') {
|
|
|
|
|
|
|
|
gateStatus.className += 'alert-success';
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
gateStatus.className += 'alert-secondary';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (data.type === 'lane-time') {
|
|
|
|
|
|
|
|
// Update lane time display
|
|
|
|
|
|
|
|
const laneTimeElement = document.getElementById(`lane-${data.lane}-time`);
|
|
|
|
|
|
|
|
if (laneTimeElement) {
|
|
|
|
if (laneTimeElement) {
|
|
|
|
laneTimeElement.textContent = data.time.toFixed(3);
|
|
|
|
laneTimeElement.textContent = laneFinishData.time.toFixed(4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (data.type === 'lane-position') {
|
|
|
|
|
|
|
|
// Update lane position display
|
|
|
|
const lanePositionElement = document.getElementById(`lane-${laneFinishData.lane}-position`);
|
|
|
|
const lanePositionElement = document.getElementById(`lane-${data.lane}-position`);
|
|
|
|
|
|
|
|
if (lanePositionElement) {
|
|
|
|
if (lanePositionElement) {
|
|
|
|
lanePositionElement.textContent = data.position;
|
|
|
|
lanePositionElement.textContent = laneFinishData.place;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} 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 times and positions
|
|
|
|
|
|
|
|
document.querySelectorAll('[id^="lane-"][id$="-time"]').forEach(el => {
|
|
|
|
|
|
|
|
el.textContent = '--.-';
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.querySelectorAll('[id^="lane-"][id$="-position"]').forEach(el => {
|
|
|
|
|
|
|
|
el.textContent = '-';
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} else if (statusData.status === 'running') {
|
|
|
|
|
|
|
|
statusText = 'Race Running';
|
|
|
|
|
|
|
|
statusClass = 'bg-success';
|
|
|
|
|
|
|
|
} else if (statusData.status === 'finished') {
|
|
|
|
|
|
|
|
statusText = 'Race Complete';
|
|
|
|
|
|
|
|
statusClass = 'bg-info';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Save heat results
|
|
|
|
|
|
|
|
saveHeatResults();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const statusIndicator = document.getElementById('status-indicator');
|
|
|
|
|
|
|
|
if (statusIndicator) {
|
|
|
|
|
|
|
|
statusIndicator.textContent = statusText;
|
|
|
|
|
|
|
|
statusIndicator.className = `badge mb-3 ${statusClass}`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const timerDisplay = document.getElementById('timer');
|
|
|
|
|
|
|
|
if (timerDisplay && statusData.status === 'idle') {
|
|
|
|
|
|
|
|
timerDisplay.textContent = '0.000';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.error("Error processing status event:", error);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Admin socket for admin-specific events
|
|
|
|
|
|
|
|
const adminSocket = new WebSocket(`${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws/admin`);
|
|
|
|
|
|
|
|
|
|
|
|
// Admin socket handling
|
|
|
|
|
|
|
|
adminSocket.onmessage = function(event) {
|
|
|
|
adminSocket.onmessage = function(event) {
|
|
|
|
const data = JSON.parse(event.data);
|
|
|
|
const data = JSON.parse(event.data);
|
|
|
|
|
|
|
|
|
|
|
|
@ -212,40 +237,9 @@ templ RaceManage(groups []models.Group, currentGroup models.Group, heats []model
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Events socket handling
|
|
|
|
|
|
|
|
eventsSocket.onmessage = function(event) {
|
|
|
|
|
|
|
|
const data = JSON.parse(event.data);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (data.event === 'race_start') {
|
|
|
|
|
|
|
|
// Race has started
|
|
|
|
|
|
|
|
console.log('Race started!');
|
|
|
|
|
|
|
|
// Update UI or play sound if needed
|
|
|
|
|
|
|
|
} else if (data.event === 'lane_finish') {
|
|
|
|
|
|
|
|
// A lane has finished
|
|
|
|
|
|
|
|
console.log(`Lane ${data.lane} finished with time ${data.time}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Update the lane time and position in the UI
|
|
|
|
|
|
|
|
const laneTimeElement = document.getElementById(`lane-${data.lane}-time`);
|
|
|
|
|
|
|
|
if (laneTimeElement) {
|
|
|
|
|
|
|
|
laneTimeElement.textContent = data.time.toFixed(3);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const lanePositionElement = document.getElementById(`lane-${data.lane}-position`);
|
|
|
|
|
|
|
|
if (lanePositionElement) {
|
|
|
|
|
|
|
|
lanePositionElement.textContent = data.position;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (data.event === 'race_end') {
|
|
|
|
|
|
|
|
// Race has ended
|
|
|
|
|
|
|
|
console.log('Race ended!');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Save the heat results
|
|
|
|
|
|
|
|
saveHeatResults();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Function to reset the timer
|
|
|
|
// Function to reset the timer
|
|
|
|
function resetTimer() {
|
|
|
|
function resetTimer() {
|
|
|
|
fetch('/api/timer/reset', { method: 'POST' })
|
|
|
|
fetch('/api/reset', { method: 'POST' })
|
|
|
|
.then(response => {
|
|
|
|
.then(response => {
|
|
|
|
if (!response.ok) {
|
|
|
|
if (!response.ok) {
|
|
|
|
console.error('Failed to reset timer');
|
|
|
|
console.error('Failed to reset timer');
|
|
|
|
@ -258,7 +252,7 @@ templ RaceManage(groups []models.Group, currentGroup models.Group, heats []model
|
|
|
|
|
|
|
|
|
|
|
|
// Function to force end the current heat
|
|
|
|
// Function to force end the current heat
|
|
|
|
function forceEndHeat() {
|
|
|
|
function forceEndHeat() {
|
|
|
|
fetch('/api/timer/force-end', { method: 'POST' })
|
|
|
|
fetch('/api/force-end', { method: 'POST' })
|
|
|
|
.then(response => {
|
|
|
|
.then(response => {
|
|
|
|
if (!response.ok) {
|
|
|
|
if (!response.ok) {
|
|
|
|
console.error('Failed to force end heat');
|
|
|
|
console.error('Failed to force end heat');
|
|
|
|
@ -308,6 +302,12 @@ templ RaceManage(groups []models.Group, currentGroup models.Group, heats []model
|
|
|
|
console.error('Error:', error);
|
|
|
|
console.error('Error:', 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>
|
|
|
|
</script>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|